From 29d3c7670be216f704f001ee6e28efc0adbe2e83 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 25 Jun 2023 23:26:45 -0400 Subject: [PATCH 1/8] Restructure Win32PlatformOptions options --- samples/ControlCatalog.NetCore/Program.cs | 3 +- src/Windows/Avalonia.Win32/Win32GlManager.cs | 37 ++++-- src/Windows/Avalonia.Win32/Win32Platform.cs | 74 ------------ .../Avalonia.Win32/Win32PlatformOptions.cs | 114 ++++++++++++++++++ .../WinUiCompositedWindowSurface.cs | 2 +- .../Composition/WinUiCompositionShared.cs | 1 + .../Composition/WinUiCompositorConnection.cs | 31 ++--- 7 files changed, 160 insertions(+), 102 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/Win32PlatformOptions.cs diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 5249a4fb41..99d507c817 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -110,8 +110,7 @@ namespace ControlCatalog.NetCore { builder.With(new Win32PlatformOptions() { - UseLowLatencyDxgiSwapChain = true, - UseWindowsUIComposition = false + CompositionMode = Win32CompositionMode.LowLatencyDxgiSwapChain }); return builder.StartWithClassicDesktopLifetime(args); } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index c26ce5fd45..49eb369149 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Win32.DirectX; @@ -23,15 +24,19 @@ namespace Avalonia.Win32 private static IPlatformGraphics? InitializeCore() { - var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions(); - if (opts.UseWgl) + + var winVersion = Win32Platform.WindowsVersion; + var renderingMode = opts.RenderingMode ?? + (winVersion > PlatformConstants.Windows7 ? Win32RenderingMode.AngleEgl : Win32RenderingMode.Software); + + if (renderingMode == Win32RenderingMode.Wgl) { var wgl = WglPlatformOpenGlInterface.TryCreate(); return wgl; } - if (opts.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7) + if (renderingMode == Win32RenderingMode.AngleEgl) { var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService() ?? new()); @@ -40,14 +45,26 @@ namespace Avalonia.Win32 { AvaloniaLocator.CurrentMutable.Bind() .ToConstant(egl); - - if (opts.UseWindowsUIComposition) - { - WinUiCompositorConnection.TryCreateAndRegister(); - } - else if (opts.UseLowLatencyDxgiSwapChain) + + var compositionMode = opts.CompositionMode ?? + (WinUiCompositorConnection.IsSupported() ? Win32CompositionMode.WinUIComposition + //: DirectCompositionConnection.IsSupported() ? Win32CompositionMode.DirectComposition + : Win32CompositionMode.RedirectionSurface); + + switch (compositionMode) { - DxgiConnection.TryCreateAndRegister(); + case Win32CompositionMode.WinUIComposition: + if (!WinUiCompositorConnection.TryCreateAndRegister()) + { + //goto case Win32CompositionMode.DirectComposition; + } + break; + //case Win32CompositionMode.DirectComposition: + //DirectCompositionConnection.TryCreateAndRegister(); + //break; + case Win32CompositionMode.LowLatencyDxgiSwapChain: + DxgiConnection.TryCreateAndRegister(); + break; } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index e9019803be..aaf6fed624 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -10,7 +9,6 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; -using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; @@ -31,78 +29,6 @@ namespace Avalonia "Win32"); } } - - /// - /// Platform-specific options which apply to Windows. - /// - public class Win32PlatformOptions - { - /// - /// Enables ANGLE for Windows. For every Windows version that is above Windows 7, the default is true otherwise it's false. - /// - /// - /// GPU rendering will not be enabled if this is set to false. - /// - public bool? AllowEglInitialization { get; set; } - - /// - /// Embeds popups to the window when set to true. The default value is false. - /// - public bool OverlayPopups { get; set; } - - /// - /// Avalonia would try to use native Widows OpenGL when set to true. The default value is false. - /// - public bool UseWgl { get; set; } - - public IList WglProfiles { get; set; } = new List - { - new GlVersion(GlProfileType.OpenGL, 4, 0), - new GlVersion(GlProfileType.OpenGL, 3, 2), - }; - - /// - /// Render Avalonia to a Texture inside the Windows.UI.Composition tree. - /// This setting is true by default. - /// - /// - /// Supported on Windows 10 build 16299 and above. Ignored on other versions. - /// This is recommended if you need to use AcrylicBlur or acrylic in your applications. - /// - public bool UseWindowsUIComposition { get; set; } = true; - - /// - /// When enabled, create rounded corner blur brushes - /// If set to null the brushes will be created using default settings (sharp corners) - /// This can be useful when you need a rounded-corner blurred Windows 10 app, or borderless Windows 11 app - /// - public float? CompositionBackdropCornerRadius { get; set; } - - /// - /// When is active, renders Avalonia through a low-latency Dxgi Swapchain. - /// Requires Feature Level 11_3 to be active, Windows 8.1+ Any Subversion. - /// This is only recommended if low input latency is desirable, and there is no need for the transparency - /// and stylings / blurrings offered by
- /// This is mutually exclusive with - /// which if active will override this setting. - /// This setting is false by default. - ///
- public bool UseLowLatencyDxgiSwapChain { get; set; } - - /// - /// Render directly on the UI thread instead of using a dedicated render thread. - /// Only applicable if both and - /// are false. - /// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF. - /// This setting is false by default. - /// - public bool ShouldRenderOnUIThread { get; set; } - - /// - /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu - /// - public IPlatformGraphics? CustomPlatformGraphics { get; set; } - } } namespace Avalonia.Win32 diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs new file mode 100644 index 0000000000..aaebedb8c5 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -0,0 +1,114 @@ +using System.Collections.Generic; +using Avalonia.OpenGL; +using Avalonia.Platform; + +namespace Avalonia; + +public enum Win32RenderingMode +{ + /// + /// Avalonia is rendered into a framebuffer. + /// + Software = 1, + + /// + /// Enables ANGLE EGL for Windows with GPU rendering. + /// + AngleEgl = 2, + + /// + /// Avalonia would try to use native Widows OpenGL with GPU rendering. + /// + Wgl +} + +public enum Win32CompositionMode +{ + /// + /// Render Avalonia to a texture inside the Windows.UI.Composition tree. + /// + /// + /// Supported on Windows 10 build 17134 and above. Ignored on other versions. + /// This is recommended option, as it allows window acrylic effects and high refresh rate rendering. + /// + WinUIComposition = 1, + + // /// + // /// Render Avalonia to a texture inside the DirectComposition tree. + // /// + // /// + // /// Supported on Windows 8 and above. Ignored on other versions. + // /// + // DirectComposition = 2, + + /// + /// When is active, renders Avalonia through a low-latency Dxgi Swapchain. + /// Requires Feature Level 11_3 to be active, Windows 8.1+ Any Subversion. + /// This is only recommended if low input latency is desirable, and there is no need for the transparency + /// and styling / blurring offered by
. + ///
+ LowLatencyDxgiSwapChain = 3, + + /// + /// The window renders to a redirection surface. + /// + /// + /// This option is kept only for compatibility with older systems. Some Avalonia features might not work. + /// + RedirectionSurface, +} + +/// +/// Platform-specific options which apply to Windows. +/// +public class Win32PlatformOptions +{ + /// + /// Embeds popups to the window when set to true. The default value is false. + /// + public bool OverlayPopups { get; set; } + + /// + /// Avalonia window rendering mode. + /// On Windows 8 and newer default value is , + /// otherwise. + /// + public Win32RenderingMode? RenderingMode { get; set; } + + /// + /// Avalonia window composition mode. + /// On Windows 8 and newer default value is , + /// otherwise. + /// + public Win32CompositionMode? CompositionMode { get; set; } + + /// + /// When is set to , create rounded corner blur brushes + /// If set to null the brushes will be created using default settings (sharp corners) + /// This can be useful when you need a rounded-corner blurred Windows 10 app, or borderless Windows 11 app. + /// + public float? WinUICompositionBackdropCornerRadius { get; set; } + + /// + /// Render directly on the UI thread instead of using a dedicated render thread. + /// Only applicable if is set to . + /// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF. + /// This setting is false by default. + /// + public bool ShouldRenderOnUIThread { get; set; } + + /// + /// Windows OpenGL profiles used when is set to . + /// This setting is 4.0 and 3.2 by default. + /// + public IList WglProfiles { get; set; } = new List + { + new(GlProfileType.OpenGL, 4, 0), new(GlProfileType.OpenGL, 3, 2) + }; + + /// + /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu. + /// When this property set and are completely ignored. + /// + public IPlatformGraphics? CustomPlatformGraphics { get; set; } +} diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index 95ef338f08..8da154dc83 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs @@ -23,7 +23,7 @@ namespace Avalonia.Win32.WinRT.Composition public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice) { var cornerRadius = AvaloniaLocator.Current.GetService() - ?.CompositionBackdropCornerRadius; + ?.WinUICompositionBackdropCornerRadius; _window ??= new WinUiCompositedWindow(_info, _shared, cornerRadius); _window.SetBlur(_blurEffect); diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs index f17805fba3..b3a328d097 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs @@ -12,6 +12,7 @@ internal class WinUiCompositionShared : IDisposable public ICompositionBrush? MicaBrush { get; } public object SyncRoot { get; } = new(); + public static readonly Version MinWinCompositionVersion = new(10, 0, 17134); public static readonly Version MinAcrylicVersion = new(10, 0, 15063); public static readonly Version MinHostBackdropVersion = new(10, 0, 22000); diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs index 754af86c06..3e5384715c 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs @@ -112,35 +112,36 @@ internal class WinUiCompositorConnection : IRenderTimer } } - public static void TryCreateAndRegister() + public static bool IsSupported() { - const int majorRequired = 10; - const int buildRequired = 17134; - - var majorInstalled = Win32Platform.WindowsVersion.Major; - var buildInstalled = Win32Platform.WindowsVersion.Build; - - if (majorInstalled >= majorRequired && - buildInstalled >= buildRequired) + return Win32Platform.WindowsVersion >= WinUiCompositionShared.MinWinCompositionVersion; + } + + public static bool TryCreateAndRegister() + { + if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinWinCompositionVersion) { try { TryCreateAndRegisterCore(); - return; + return true; } catch (Exception e) { Logger.TryGet(LogEventLevel.Error, "WinUIComposition") ?.Log(null, "Unable to initialize WinUI compositor: {0}", e); - } } + else + { + var osVersionNotice = + $"Windows {WinUiCompositionShared.MinWinCompositionVersion} is required. Your machine has Windows {Win32Platform.WindowsVersion} installed."; - var osVersionNotice = - $"Windows {majorRequired} Build {buildRequired} is required. Your machine has Windows {majorInstalled} Build {buildInstalled} installed."; + Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null, + $"Unable to initialize WinUI compositor: {osVersionNotice}"); + } - Logger.TryGet(LogEventLevel.Warning, "WinUIComposition")?.Log(null, - $"Unable to initialize WinUI compositor: {osVersionNotice}"); + return false; } public WinUiCompositedWindowSurface CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new(_shared, info); From effb12f9e04343437f2e2ac172c9951881f2bbfa Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 25 Jun 2023 23:43:07 -0400 Subject: [PATCH 2/8] Throw an exception when CustomPlatformGraphics is used with unsupported Win32CompositionMode --- src/Windows/Avalonia.Win32/Win32Platform.cs | 21 ++++++++++++++++--- .../Avalonia.Win32/Win32PlatformOptions.cs | 5 +++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index aaf6fed624..5082c76d0c 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -99,9 +99,24 @@ namespace Avalonia.Win32 .Bind().ToConstant(NonPumpingWaitHelperImpl.Instance) .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()) .Bind().ToConstant(s_instance); - - var platformGraphics = options.CustomPlatformGraphics - ?? Win32GlManager.Initialize(); + + IPlatformGraphics? platformGraphics; + if (options.CustomPlatformGraphics is not null) + { + if (options.CompositionMode.HasValue + && options.CompositionMode != Win32CompositionMode.RedirectionSurface) + { + throw new InvalidOperationException( + $"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CustomPlatformGraphics)} is only " + + $"compatible with {nameof(Win32CompositionMode)}.{nameof(Win32CompositionMode.RedirectionSurface)}"); + } + + platformGraphics = options.CustomPlatformGraphics; + } + else + { + platformGraphics = Win32GlManager.Initialize(); + } if (OleContext.Current != null) AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs index aaebedb8c5..3c7e32f774 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -91,7 +91,7 @@ public class Win32PlatformOptions /// /// Render directly on the UI thread instead of using a dedicated render thread. - /// Only applicable if is set to . + /// Only applicable if is set to . /// This setting is only recommended for interop with systems that must render on the UI thread, such as WPF. /// This setting is false by default. /// @@ -108,7 +108,8 @@ public class Win32PlatformOptions /// /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu. - /// When this property set and are completely ignored. + /// When this property set is ignored + /// and only accepts null or . /// public IPlatformGraphics? CustomPlatformGraphics { get; set; } } From 33f70d9fca7912510ba0d6708cabc19e5c15f50a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 25 Jun 2023 21:18:55 -0700 Subject: [PATCH 3/8] Update src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs Co-authored-by: jp2masa --- .../WinRT/Composition/WinUiCompositorConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs index 3e5384715c..596c94d30b 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs @@ -119,7 +119,7 @@ internal class WinUiCompositorConnection : IRenderTimer public static bool TryCreateAndRegister() { - if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinWinCompositionVersion) + if (IsSupported()) { try { From a390f9f50cbf1db5d670b23243ec6328c3e90658 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 26 Jun 2023 23:18:34 -0400 Subject: [PATCH 4/8] Make RenderingMode and CompositionMode lists with fallbacks --- samples/ControlCatalog.NetCore/Program.cs | 2 +- .../Avalonia.Win32/DirectX/DxgiConnection.cs | 4 +- src/Windows/Avalonia.Win32/Win32GlManager.cs | 112 ++++++++++-------- src/Windows/Avalonia.Win32/Win32Platform.cs | 4 +- .../Avalonia.Win32/Win32PlatformOptions.cs | 43 +++++-- 5 files changed, 102 insertions(+), 63 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 99d507c817..5e3e301461 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -110,7 +110,7 @@ namespace ControlCatalog.NetCore { builder.With(new Win32PlatformOptions() { - CompositionMode = Win32CompositionMode.LowLatencyDxgiSwapChain + CompositionMode = new [] { Win32CompositionMode.LowLatencyDxgiSwapChain } }); return builder.StartWithClassicDesktopLifetime(args); } diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs index 07fb3169cb..bac2a73dd9 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs @@ -28,16 +28,18 @@ namespace Avalonia.Win32.DirectX _syncLock = syncLock; } - public static void TryCreateAndRegister() + public static bool TryCreateAndRegister() { try { TryCreateAndRegisterCore(); + return true; } catch (Exception ex) { Logger.TryGet(LogEventLevel.Error, LogArea) ?.Log(null, "Unable to establish Dxgi: {0}", ex); + return false; } } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index 49eb369149..b3760a37e1 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics.Tracing; +using System.Linq; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Win32.DirectX; @@ -6,72 +8,88 @@ using Avalonia.Win32.OpenGl; using Avalonia.Win32.OpenGl.Angle; using Avalonia.Win32.WinRT.Composition; -namespace Avalonia.Win32 +namespace Avalonia.Win32; + +static class Win32GlManager { - static class Win32GlManager + public static IPlatformGraphics? Initialize() { - public static IPlatformGraphics? Initialize() - { - var gl = InitializeCore(); - - if (gl is not null) - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); - } + var gl = InitializeCore(); - return gl; + if (gl is not null) + { + AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); } + + return gl; + } - private static IPlatformGraphics? InitializeCore() + private static IPlatformGraphics? InitializeCore() + { + var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions(); + if (opts.RenderingMode is null || !opts.RenderingMode.Any()) { - var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions(); + throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.RenderingMode)} must not be empty or null"); + } - var winVersion = Win32Platform.WindowsVersion; - var renderingMode = opts.RenderingMode ?? - (winVersion > PlatformConstants.Windows7 ? Win32RenderingMode.AngleEgl : Win32RenderingMode.Software); - - if (renderingMode == Win32RenderingMode.Wgl) + foreach (var renderingMode in opts.RenderingMode) + { + if (renderingMode == Win32RenderingMode.Software) { - var wgl = WglPlatformOpenGlInterface.TryCreate(); - return wgl; + return null; } - + if (renderingMode == Win32RenderingMode.AngleEgl) { - var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService() ?? - new()); + var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService() ?? new()); if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11) { - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(egl); - - var compositionMode = opts.CompositionMode ?? - (WinUiCompositorConnection.IsSupported() ? Win32CompositionMode.WinUIComposition - //: DirectCompositionConnection.IsSupported() ? Win32CompositionMode.DirectComposition - : Win32CompositionMode.RedirectionSurface); + TryRegisterComposition(opts); + return egl; + } + } - switch (compositionMode) - { - case Win32CompositionMode.WinUIComposition: - if (!WinUiCompositorConnection.TryCreateAndRegister()) - { - //goto case Win32CompositionMode.DirectComposition; - } - break; - //case Win32CompositionMode.DirectComposition: - //DirectCompositionConnection.TryCreateAndRegister(); - //break; - case Win32CompositionMode.LowLatencyDxgiSwapChain: - DxgiConnection.TryCreateAndRegister(); - break; - } + if (renderingMode == Win32RenderingMode.Wgl) + { + if (WglPlatformOpenGlInterface.TryCreate() is { } wgl) + { + return wgl; } + } + } + + throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied."); + } + + private static void TryRegisterComposition(Win32PlatformOptions opts) + { + if (opts.CompositionMode is null || !opts.CompositionMode.Any()) + { + throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CompositionMode)} must not be empty or null"); + } - return egl; + foreach (var compositionMode in opts.CompositionMode) + { + if (compositionMode == Win32CompositionMode.RedirectionSurface) + { + return; } - return null; + if (compositionMode == Win32CompositionMode.WinUIComposition + && WinUiCompositorConnection.IsSupported() + && WinUiCompositorConnection.TryCreateAndRegister()) + { + return; + } + + if (compositionMode == Win32CompositionMode.LowLatencyDxgiSwapChain + && DxgiConnection.TryCreateAndRegister()) + { + return; + } } + + throw new InvalidOperationException($"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CompositionMode)} has a value of \"{string.Join(", ", opts.CompositionMode)}\", but no options were applied."); } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 5082c76d0c..6e727ba4d4 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using Avalonia.Reactive; using System.Runtime.InteropServices; using System.Threading; @@ -103,8 +104,7 @@ namespace Avalonia.Win32 IPlatformGraphics? platformGraphics; if (options.CustomPlatformGraphics is not null) { - if (options.CompositionMode.HasValue - && options.CompositionMode != Win32CompositionMode.RedirectionSurface) + if (options.CompositionMode?.Contains(Win32CompositionMode.RedirectionSurface) == false) { throw new InvalidOperationException( $"{nameof(Win32PlatformOptions)}.{nameof(Win32PlatformOptions.CustomPlatformGraphics)} is only " + diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs index 3c7e32f774..9eeb36b7d1 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -29,7 +29,8 @@ public enum Win32CompositionMode /// /// /// Supported on Windows 10 build 17134 and above. Ignored on other versions. - /// This is recommended option, as it allows window acrylic effects and high refresh rate rendering. + /// This is recommended option, as it allows window acrylic effects and high refresh rate rendering.
+ /// Can only be applied with =. ///
WinUIComposition = 1, @@ -37,16 +38,20 @@ public enum Win32CompositionMode // /// Render Avalonia to a texture inside the DirectComposition tree. // /// // /// - // /// Supported on Windows 8 and above. Ignored on other versions. + // /// Supported on Windows 8 and above. Ignored on other versions.
+ // /// Can only be applied with =. // ///
// DirectComposition = 2, /// /// When is active, renders Avalonia through a low-latency Dxgi Swapchain. + /// + /// /// Requires Feature Level 11_3 to be active, Windows 8.1+ Any Subversion. /// This is only recommended if low input latency is desirable, and there is no need for the transparency - /// and styling / blurring offered by
. - /// + /// and styling / blurring offered by .
+ /// Can only be applied with =. + ///
LowLatencyDxgiSwapChain = 3, /// @@ -69,18 +74,32 @@ public class Win32PlatformOptions public bool OverlayPopups { get; set; } /// - /// Avalonia window rendering mode. - /// On Windows 8 and newer default value is , - /// otherwise. + /// Gets or sets Avalonia rendering modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . /// - public Win32RenderingMode? RenderingMode { get; set; } + /// + /// If application should work on as wide range of devices as possible, at least add as a fallback value. + /// + /// Thrown if no values were matched. + public IReadOnlyList RenderingMode { get; set; } = new[] + { + Win32RenderingMode.AngleEgl, Win32RenderingMode.Software + }; /// - /// Avalonia window composition mode. - /// On Windows 8 and newer default value is , - /// otherwise. + /// Gets or sets Avalonia composition modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . /// - public Win32CompositionMode? CompositionMode { get; set; } + /// + /// If application should work on as wide range of devices as possible, at least add as a fallback value. + /// + /// Thrown if no values were matched. + public IReadOnlyList CompositionMode { get; set; } = new[] + { + Win32CompositionMode.WinUIComposition, Win32CompositionMode.RedirectionSurface + }; /// /// When is set to , create rounded corner blur brushes From 6a50fc5a92648c8975d307b76be0adca4b5981b8 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 26 Jun 2023 23:43:33 -0400 Subject: [PATCH 5/8] Replace UseGpu and UseEgl parameters with consistent RenderingMode enum on every platform --- .../Avalonia.Android/AndroidPlatform.cs | 71 ++++++++++++++-- .../Egl/EglPlatformGraphics.cs | 7 -- src/Avalonia.X11/Glx/GlxPlatformFeature.cs | 12 --- src/Avalonia.X11/X11Platform.cs | 82 +++++++++++++++---- .../Avalonia.Win32/Win32PlatformOptions.cs | 2 +- 5 files changed, 130 insertions(+), 44 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index d5d5f211e9..6ea067d8b7 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Avalonia.Controls; using Avalonia.Android; using Avalonia.Android.Platform; @@ -22,6 +24,36 @@ namespace Avalonia .UseSkia(); } } + + public enum AndroidRenderingMode + { + /// + /// Avalonia is rendered into a framebuffer. + /// + Software = 1, + + /// + /// Enables android EGL rendering. + /// + Egl = 2 + } + + public sealed class AndroidPlatformOptions + { + /// + /// Gets or sets Avalonia rendering modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . + /// + /// + /// If application should work on as wide range of devices as possible, at least add as a fallback value. + /// + /// Thrown if no values were matched. + public IReadOnlyList RenderingMode { get; set; } = new[] + { + AndroidRenderingMode.Egl, AndroidRenderingMode.Software + }; + } } namespace Avalonia.Android @@ -47,18 +79,39 @@ namespace Avalonia.Android .Bind().ToConstant(new ChoreographerTimer()) .Bind().ToSingleton(); - if (Options.UseGpu) + var graphics = InitializeGraphics(Options); + if (graphics is not null) { - EglPlatformGraphics.TryInitialize(); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(graphics); } - - Compositor = new Compositor(AvaloniaLocator.Current.GetService()); + + Compositor = new Compositor(graphics); } - } + + private static IPlatformGraphics InitializeGraphics(AndroidPlatformOptions opts) + { + if (opts.RenderingMode is null || !opts.RenderingMode.Any()) + { + throw new InvalidOperationException($"{nameof(AndroidPlatformOptions)}.{nameof(AndroidPlatformOptions.RenderingMode)} must not be empty or null"); + } - public sealed class AndroidPlatformOptions - { - public bool UseDeferredRendering { get; set; } = false; - public bool UseGpu { get; set; } = true; + foreach (var renderingMode in opts.RenderingMode) + { + if (renderingMode == AndroidRenderingMode.Software) + { + return null; + } + + if (renderingMode == AndroidRenderingMode.Egl) + { + if (EglPlatformGraphics.TryCreate() is { } egl) + { + return egl; + } + } + } + + throw new InvalidOperationException($"{nameof(AndroidPlatformOptions)}.{nameof(AndroidPlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied."); + } } } diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs index 07e304febe..cf81999095 100644 --- a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs +++ b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs @@ -16,13 +16,6 @@ namespace Avalonia.OpenGL.Egl _display = display; } - public static void TryInitialize() - { - var feature = TryCreate(); - if (feature != null) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); - } - public static EglPlatformGraphics? TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions { Egl = new EglInterface(), diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs index 06766e0963..58336522c1 100644 --- a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs +++ b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs @@ -15,18 +15,6 @@ namespace Avalonia.X11.Glx IPlatformGraphicsContext IPlatformGraphics.CreateContext() => Display.CreateContext(); public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException(); - - public static bool TryInitialize(X11Info x11, IList glProfiles) - { - var feature = TryCreate(x11, glProfiles); - if (feature != null) - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); - return true; - } - - return false; - } public static GlxPlatformGraphics TryCreate(X11Info x11, IList glProfiles) { diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index a880e4ba1a..7324481aaa 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -93,17 +93,13 @@ namespace Avalonia.X11 XI2 = xi2; } - if (options.UseGpu) + var graphics = InitializeGraphics(options, Info); + if (graphics is not null) { - if (options.UseEGL) - EglPlatformGraphics.TryInitialize(); - else - GlxPlatformGraphics.TryInitialize(Info, Options.GlProfiles); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(graphics); } - var gl = AvaloniaLocator.Current.GetService(); - - Compositor = new Compositor(gl); + Compositor = new Compositor(graphics); } public IntPtr DeferredDisplay { get; set; } @@ -185,25 +181,81 @@ namespace Avalonia.X11 return false; } + + private static IPlatformGraphics InitializeGraphics(X11PlatformOptions opts, X11Info info) + { + if (opts.RenderingMode is null || !opts.RenderingMode.Any()) + { + throw new InvalidOperationException($"{nameof(X11PlatformOptions)}.{nameof(X11PlatformOptions.RenderingMode)} must not be empty or null"); + } + + foreach (var renderingMode in opts.RenderingMode) + { + if (renderingMode == X11RenderingMode.Software) + { + return null; + } + + if (renderingMode == X11RenderingMode.Glx) + { + if (GlxPlatformGraphics.TryCreate(info, opts.GlProfiles) is { } glx) + { + return glx; + } + } + + if (renderingMode == X11RenderingMode.Egl) + { + if (EglPlatformGraphics.TryCreate() is { } egl) + { + return egl; + } + } + } + + throw new InvalidOperationException($"{nameof(X11PlatformOptions)}.{nameof(X11PlatformOptions.RenderingMode)} has a value of \"{string.Join(", ", opts.RenderingMode)}\", but no options were applied."); + } } } namespace Avalonia { + public enum X11RenderingMode + { + /// + /// Avalonia is rendered into a framebuffer. + /// + Software = 1, + + /// + /// Enables Glx rendering. + /// + Glx = 2, + + /// + /// Enables native Linux EGL rendering. + /// + Egl = 3 + } + /// /// Platform-specific options which apply to Linux. /// public class X11PlatformOptions { /// - /// Enables native Linux EGL when set to true. The default value is false. + /// Gets or sets Avalonia rendering modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . /// - public bool UseEGL { get; set; } - - /// - /// Determines whether to use GPU for rendering in your project. The default value is true. - /// - public bool UseGpu { get; set; } = true; + /// + /// If application should work on as wide range of devices as possible, at least add as a fallback value. + /// + /// Thrown if no values were matched. + public IReadOnlyList RenderingMode { get; set; } = new[] + { + X11RenderingMode.Glx, X11RenderingMode.Software + }; /// /// Embeds popups to the window when set to true. The default value is false. diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs index 9eeb36b7d1..f4765a558f 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -19,7 +19,7 @@ public enum Win32RenderingMode /// /// Avalonia would try to use native Widows OpenGL with GPU rendering. /// - Wgl + Wgl = 3 } public enum Win32CompositionMode From d42a40fda7d47d6f88e9819068626f8c2f54945c Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 26 Jun 2023 23:43:50 -0400 Subject: [PATCH 6/8] Remove AvaloniaNativePlatformOptions.UseGpu as it's not supported in 11.0 (always uses GPU) --- src/Avalonia.Native/AvaloniaNativePlatform.cs | 24 +++++++++---------- .../AvaloniaNativePlatformExtensions.cs | 6 +---- src/Avalonia.Native/WindowImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 5 ++-- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 5d5e17839f..03f0129200 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -123,20 +123,18 @@ namespace Avalonia.Native hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); AvaloniaLocator.CurrentMutable.Bind().ToConstant(hotkeys); - - if (_options.UseGpu) + + // TODO: add software and metal support via RenderingMode options param + try + { + _platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(_platformGl); + + } + catch (Exception) { - try - { - _platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(_platformGl); - - } - catch (Exception) - { - // ignored - } + // ignored } Compositor = new Compositor(_platformGl, true); diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 2b989ce733..7e01e03c9c 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Native; @@ -30,11 +31,6 @@ namespace Avalonia /// public class AvaloniaNativePlatformOptions { - /// - /// Determines whether to use GPU for rendering in your project. The default value is true. - /// - public bool UseGpu { get; set; } = true; - /// /// Embeds popups to the window when set to true. The default value is false. /// diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 817fe3d080..ba96dd401a 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native private bool _canResize = true; internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature) + AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, glFeature) { _opts = opts; _glFeature = glFeature; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 760816643e..053957f89f 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -67,11 +67,10 @@ namespace Avalonia.Native private PlatformBehaviorInhibition _platformBehaviorInhibition; private WindowTransparencyLevel _transparencyLevel = WindowTransparencyLevel.None; - internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - AvaloniaNativeGlPlatformGraphics glFeature) + internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativeGlPlatformGraphics glFeature) { _factory = factory; - _gpu = opts.UseGpu && glFeature != null; + _gpu = glFeature != null; _keyboard = AvaloniaLocator.Current.GetService(); _mouse = new MouseDevice(); From 290fd5caf38b140c976bcc2cd20edf7cffb9a9d7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 26 Jun 2023 23:46:33 -0400 Subject: [PATCH 7/8] Add missing docs --- src/Android/Avalonia.Android/AndroidPlatform.cs | 3 +++ src/Avalonia.X11/X11Platform.cs | 3 +++ src/Windows/Avalonia.Win32/Win32PlatformOptions.cs | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 6ea067d8b7..144909db4b 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -25,6 +25,9 @@ namespace Avalonia } } + /// + /// Represents the rendering mode for platform graphics. + /// public enum AndroidRenderingMode { /// diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 7324481aaa..c70c17d523 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -220,6 +220,9 @@ namespace Avalonia.X11 namespace Avalonia { + /// + /// Represents the rendering mode for platform graphics. + /// public enum X11RenderingMode { /// diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs index f4765a558f..bbb4c37d7e 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -4,6 +4,9 @@ using Avalonia.Platform; namespace Avalonia; +/// +/// Represents the rendering mode for platform graphics. +/// public enum Win32RenderingMode { /// @@ -22,6 +25,9 @@ public enum Win32RenderingMode Wgl = 3 } +/// +/// Represents the Win32 window composition mode. +/// public enum Win32CompositionMode { /// From e5098cfd6211672cbc2841056bd1b50ea4136af5 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 27 Jun 2023 00:00:21 -0400 Subject: [PATCH 8/8] Fix build --- src/Avalonia.Native/PopupImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 6b7f7e8883..6cef7ea578 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Native public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativeGlPlatformGraphics glFeature, - IWindowBaseImpl parent) : base(factory, opts, glFeature) + IWindowBaseImpl parent) : base(factory, glFeature) { _opts = opts; _glFeature = glFeature;