diff --git a/build/update-avalonia.csx b/build/update-avalonia.csx index 83ea4c2..34465d6 100644 --- a/build/update-avalonia.csx +++ b/build/update-avalonia.csx @@ -142,6 +142,7 @@ CopyFile ("Avalonia.Controls/AcrylicPlatformCompensationLevels.cs", "AcrylicPlat CopyFile ("Avalonia.Native/AvaloniaNativeApplicationPlatform.cs", "Avalonia.Mac/AvaloniaNativeApplicationPlatform.cs"); CopyFile ("Avalonia.Native/AvaloniaNativePlatform.cs", "Avalonia.Mac/AvaloniaNativePlatform.cs"); CopyFile ("Avalonia.Native/AvaloniaNativePlatformExtensions.cs", "Avalonia.Mac/AvaloniaNativePlatformExtensions.cs"); +CopyFile ("Avalonia.Native/AvnDispatcher.cs", "Avalonia.Mac/AvnDispatcher.cs"); CopyFile ("Avalonia.Native/CallbackBase.cs", "Avalonia.Mac/CallbackBase.cs"); CopyFile ("Avalonia.Native/ClipboardImpl.cs", "Avalonia.Mac/ClipboardImpl.cs"); CopyFile ("Avalonia.Native/Cursor.cs", "Avalonia.Mac/Cursor.cs"); @@ -178,6 +179,7 @@ CopyFile ("Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs", "Avalonia.Win CopyFile ("Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs", "Avalonia.Win32/WindowsMouseDevice.cs"); CopyFile ("Windows/Avalonia.Win32/WinScreen.cs", "Avalonia.Win32/WinScreen.cs"); CopyFile ("Windows/Avalonia.Win32/Win32DispatcherImpl.cs", "Avalonia.Win32/Win32DispatcherImpl.cs"); +CopyFile ("Windows/Avalonia.Win32/Win32PlatformOptions.cs", "Avalonia.Win32/Win32PlatformOptions.cs"); CopyFile ("Windows/Avalonia.Win32/Win32PlatformSettings.cs", "Avalonia.Win32/Win32PlatformSettings.cs"); CopyFile ("Windows/Avalonia.Win32/Win32StorageProvider.cs", "Avalonia.Win32/Win32StorageProvider.cs"); CopyFile ("Windows/Avalonia.Win32/Win32TypeExtensions.cs", "Avalonia.Win32/Win32TypeExtensions.cs"); diff --git a/src/Modern.WindowKit/Avalonia.Mac/AvaloniaNativePlatform.cs b/src/Modern.WindowKit/Avalonia.Mac/AvaloniaNativePlatform.cs index 7b3e977..1b48ad3 100644 --- a/src/Modern.WindowKit/Avalonia.Mac/AvaloniaNativePlatform.cs +++ b/src/Modern.WindowKit/Avalonia.Mac/AvaloniaNativePlatform.cs @@ -19,7 +19,7 @@ partial class AvaloniaNativePlatform : IWindowingPlatform { private readonly IAvaloniaNativeFactory _factory; private AvaloniaNativePlatformOptions? _options; - //private AvaloniaNativeGlPlatformGraphics? _platformGl; + //private IPlatformGraphics? _platformGraphics; [DllImport("libAvaloniaNative")] static extern IntPtr CreateAvaloniaNative(); @@ -93,7 +93,7 @@ void DoInitialize(AvaloniaNativePlatformOptions options) if (_factory.MacOptions != null) _factory.MacOptions.SetDisableAppDelegate(macOpts.DisableAvaloniaAppDelegate ? 1 : 0); - _factory.Initialize(new GCHandleDeallocator(), applicationPlatform); + _factory.Initialize(new GCHandleDeallocator(), applicationPlatform, new AvnDispatcher()); if (_factory.MacOptions != null) { @@ -124,22 +124,45 @@ void DoInitialize(AvaloniaNativePlatformOptions options) //AvaloniaLocator.CurrentMutable.Bind().ToConstant(hotkeys); - //if (_options.UseGpu) + //foreach (var mode in _options.RenderingMode) //{ - // try + // if (mode == AvaloniaNativeRenderingMode.OpenGl) // { - // _platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); - // AvaloniaLocator.CurrentMutable - // .Bind().ToConstant(_platformGl); - + // try + // { + // _platformGraphics = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); + // break; // } // catch (Exception) // { // // ignored // } //} +#pragma warning disable CS0618 + // else if (mode == AvaloniaNativeRenderingMode.Metal) +#pragma warning restore CS0618 + // { + // try + // { + // var metal = new MetalPlatformGraphics(_factory); + // metal.CreateContext().Dispose(); + // _platformGraphics = metal; + // } + // catch + // { + // // Ignored + // } + // } + // else if (mode == AvaloniaNativeRenderingMode.Software) + // break; + //} + + //if (_platformGraphics != null) + // AvaloniaLocator.CurrentMutable + // .Bind().ToConstant(_platformGraphics); - //Compositor = new Compositor(_platformGl, true); + + //Compositor = new Compositor(_platformGraphics, true); } //public ITrayIconImpl CreateTrayIcon() @@ -149,7 +172,7 @@ void DoInitialize(AvaloniaNativePlatformOptions options) //public IWindowImpl CreateWindow() //{ - // return new WindowImpl(_factory, _options, _platformGl); + // return new WindowImpl(_factory, _options); //} public IWindowImpl CreateEmbeddableWindow() diff --git a/src/Modern.WindowKit/Avalonia.Mac/AvaloniaNativePlatformExtensions.cs b/src/Modern.WindowKit/Avalonia.Mac/AvaloniaNativePlatformExtensions.cs index cd43f73..25ee07c 100644 --- a/src/Modern.WindowKit/Avalonia.Mac/AvaloniaNativePlatformExtensions.cs +++ b/src/Modern.WindowKit/Avalonia.Mac/AvaloniaNativePlatformExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Modern.WindowKit.Controls; using Modern.WindowKit.Native; @@ -25,16 +26,44 @@ public static class AvaloniaNativePlatformExtensions //} } + public enum AvaloniaNativeRenderingMode + { + /// + /// Avalonia would try to use native OpenGL with GPU rendering. + /// + OpenGl = 1, + /// + /// Avalonia is rendered into a framebuffer. + /// + Software = 2, + /// + /// Avalonia would try to use Metal with GPU rendering. + /// + [Obsolete("Experimental, unstable, not for production usage")] + Metal = 3 + } + /// /// OSX backend options. /// public class AvaloniaNativePlatformOptions { /// - /// Determines whether to use GPU for rendering in your project. The default value is true. + /// Gets or sets Avalonia rendering modes with fallbacks. + /// The first element in the array has the highest priority. + /// The default value is: , . /// - 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[] + { + AvaloniaNativeRenderingMode.OpenGl, + AvaloniaNativeRenderingMode.Software + }; + /// /// Embeds popups to the window when set to true. The default value is false. /// diff --git a/src/Modern.WindowKit/Avalonia.Mac/AvnDispatcher.cs b/src/Modern.WindowKit/Avalonia.Mac/AvnDispatcher.cs new file mode 100644 index 0000000..09b905f --- /dev/null +++ b/src/Modern.WindowKit/Avalonia.Mac/AvnDispatcher.cs @@ -0,0 +1,19 @@ +using Modern.WindowKit.Mac.Interop; +using Modern.WindowKit.Threading; +using Modern.WindowKit.MicroCom; + +namespace Modern.WindowKit.Native; + +class AvnDispatcher : NativeCallbackBase, IAvnDispatcher +{ + public void Post(IAvnActionCallback cb) + { + var callback = cb.CloneReference(); + Dispatcher.UIThread.Post(() => + { + using (callback) + callback.Run(); + }, DispatcherPriority.Send); + } +} + diff --git a/src/Modern.WindowKit/Avalonia.Mac/DeferredFramebuffer.cs b/src/Modern.WindowKit/Avalonia.Mac/DeferredFramebuffer.cs index 2493083..0914a81 100644 --- a/src/Modern.WindowKit/Avalonia.Mac/DeferredFramebuffer.cs +++ b/src/Modern.WindowKit/Avalonia.Mac/DeferredFramebuffer.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Modern.WindowKit.Controls.Platform.Surfaces; using Modern.WindowKit.Mac.Interop; using Modern.WindowKit.Platform; @@ -7,11 +8,13 @@ namespace Modern.WindowKit.Native { internal unsafe class DeferredFramebuffer : ILockedFramebuffer { - private readonly Func, bool> _lockWindow; - - public DeferredFramebuffer(Func, bool> lockWindow, + private readonly IAvnSoftwareRenderTarget _renderTarget; + private readonly Action> _lockWindow; + + public DeferredFramebuffer(IAvnSoftwareRenderTarget renderTarget, Action> lockWindow, int width, int height, Vector dpi) { + _renderTarget = renderTarget; _lockWindow = lockWindow; Address = Marshal.AllocHGlobal(width * height * 4); Size = new PixelSize(width, height); @@ -27,54 +30,28 @@ public DeferredFramebuffer(Func, bool> lockWindow, public Vector Dpi { get; set; } public PixelFormat Format { get; set; } - class Disposer : NativeCallbackBase - { - private IntPtr _ptr; - - public Disposer(IntPtr ptr) - { - _ptr = ptr; - } - - protected override void Destroyed() - { - if(_ptr != IntPtr.Zero) - { - Marshal.FreeHGlobal(_ptr); - _ptr = IntPtr.Zero; - } - } - } - public void Dispose() { if (Address == IntPtr.Zero) return; - if (!_lockWindow(win => + _lockWindow(win => { var fb = new AvnFramebuffer { Data = Address.ToPointer(), - Dpi = new AvnVector - { - X = Dpi.X, - Y = Dpi.Y - }, + Dpi = new AvnVector { X = Dpi.X, Y = Dpi.Y }, Width = Size.Width, Height = Size.Height, PixelFormat = (AvnPixelFormat)Format.FormatEnum, Stride = RowBytes }; - using (var d = new Disposer(Address)) - { - win.ThreadSafeSetSwRenderedFrame(&fb, d); - } - })) - { - Marshal.FreeHGlobal(Address); - } + _renderTarget.SetFrame(&fb); + + }); + + Marshal.FreeHGlobal(Address); Address = IntPtr.Zero; } diff --git a/src/Modern.WindowKit/Avalonia.Mac/PopupImpl.cs b/src/Modern.WindowKit/Avalonia.Mac/PopupImpl.cs index ccf4218..f090360 100644 --- a/src/Modern.WindowKit/Avalonia.Mac/PopupImpl.cs +++ b/src/Modern.WindowKit/Avalonia.Mac/PopupImpl.cs @@ -8,21 +8,15 @@ namespace Modern.WindowKit.Native { class PopupImpl : WindowBaseImpl, IPopupImpl { - private readonly AvaloniaNativePlatformOptions _opts; - //private readonly AvaloniaNativeGlPlatformGraphics _glFeature; private readonly IWindowBaseImpl _parent; public PopupImpl(IAvaloniaNativeFactory factory, - AvaloniaNativePlatformOptions opts, - //AvaloniaNativeGlPlatformGraphics glFeature, - IWindowBaseImpl parent) : base(factory, opts) + IWindowBaseImpl parent) : base(factory) { - _opts = opts; - //_glFeature = glFeature; _parent = parent; using (var e = new PopupEvents(this)) { - Init(factory.CreatePopup(e, null), factory.CreateScreens()); + Init(factory.CreatePopup(e), factory.CreateScreens()); } PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } @@ -68,7 +62,7 @@ public override void Show(bool activate, bool isDialog) base.Show(false, isDialog); } - public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, this); + public override IPopupImpl CreatePopup() => new PopupImpl(_factory, this); public void SetWindowManagerAddShadowHint(bool enabled) { diff --git a/src/Modern.WindowKit/Avalonia.Mac/WindowImpl.cs b/src/Modern.WindowKit/Avalonia.Mac/WindowImpl.cs index 49a6e1f..0ec70ec 100644 --- a/src/Modern.WindowKit/Avalonia.Mac/WindowImpl.cs +++ b/src/Modern.WindowKit/Avalonia.Mac/WindowImpl.cs @@ -15,7 +15,7 @@ namespace Modern.WindowKit.Native internal partial class WindowImpl : WindowBaseImpl, IWindowImpl { private readonly AvaloniaNativePlatformOptions _opts; - //private readonly AvaloniaNativeGlPlatformGraphics _glFeature; + //private readonly AvaloniaNativeGlPlatformGraphics _graphics; IAvnWindow _native; private double _extendTitleBarHeight = -1; //private DoubleClickHelper _doubleClickHelper; @@ -23,16 +23,14 @@ internal partial class WindowImpl : WindowBaseImpl, IWindowImpl //private readonly AvaloniaNativeTextInputMethod _inputMethod; private bool _canResize = true; - internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts - ) : base(factory, opts) + internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(factory) { _opts = opts; - //_glFeature = glFeature; //_doubleClickHelper = new DoubleClickHelper(); using (var e = new WindowEvents(this)) { - Init(_native = factory.CreateWindow(e, null), factory.CreateScreens()); + Init(_native = factory.CreateWindow(e), factory.CreateScreens()); } //_nativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory); @@ -215,7 +213,7 @@ public void ShowTaskbarIcon(bool value) public void Move(PixelPoint point) => Position = point; //public override IPopupImpl CreatePopup() => - // _opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, _glFeature, this); + // _opts.OverlayPopups ? null : new PopupImpl(_factory, this); public Action GotInputWhenDisabled { get; set; } diff --git a/src/Modern.WindowKit/Avalonia.Mac/WindowImplBase.cs b/src/Modern.WindowKit/Avalonia.Mac/WindowImplBase.cs index 4ff27db..c0718bd 100644 --- a/src/Modern.WindowKit/Avalonia.Mac/WindowImplBase.cs +++ b/src/Modern.WindowKit/Avalonia.Mac/WindowImplBase.cs @@ -54,24 +54,20 @@ internal abstract partial class WindowBaseImpl : IWindowBaseImpl, protected IInputRoot _inputRoot; IAvnWindowBase _native; private object _syncRoot = new object(); - private bool _gpu = false; private readonly MouseDevice _mouse; private readonly IKeyboardDevice _keyboard; private readonly ICursorFactory _cursorFactory; private Size _savedLogicalSize; private Size _lastRenderedLogicalSize; private double _savedScaling; - //private GlPlatformSurface _glSurface; //private NativeControlHostImpl _nativeControlHost; private IStorageProvider _storageProvider; //private PlatformBehaviorInhibition _platformBehaviorInhibition; private WindowTransparencyLevel _transparencyLevel = WindowTransparencyLevel.None; - internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) - //AvaloniaNativeGlPlatformGraphics glFeature) + internal WindowBaseImpl(IAvaloniaNativeFactory factory) { _factory = factory; - //_gpu = opts.UseGpu && glFeature != null; _keyboard = AvaloniaNativePlatform.KeyboardDevice; _mouse = new MouseDevice(); @@ -82,9 +78,8 @@ protected void Init(IAvnWindowBase window, IAvnScreens screens) { _native = window; + Surfaces = new object[] { /*new GlPlatformSurface(window), new MetalPlatformSurface(window), */this }; Handle = new MacOSTopLevelWindowHandle(window); - //if (_gpu) - // _glSurface = new GlPlatformSurface(window); Screen = new ScreenImpl(screens); _savedLogicalSize = ClientSize; @@ -133,30 +128,56 @@ public Size? FrameSize } } - public IEnumerable Surfaces => new[] { - //(_gpu ? _glSurface : (object)null), - this - }; + public IEnumerable Surfaces { get; private set; } //public INativeControlHostImpl NativeControlHost => _nativeControlHost; - public ILockedFramebuffer Lock() + + IFramebufferRenderTarget IFramebufferPlatformSurface.CreateFramebufferRenderTarget() { - var w = _savedLogicalSize.Width * _savedScaling; - var h = _savedLogicalSize.Height * _savedScaling; - var dpi = _savedScaling * 96; - return new DeferredFramebuffer(cb => + //if (!Dispatcher.UIThread.CheckAccess()) + // throw new RenderTargetNotReadyException(); + return new FramebufferRenderTarget(this, _native.CreateSoftwareRenderTarget()); + } + + class FramebufferRenderTarget : IFramebufferRenderTarget + { + private readonly WindowBaseImpl _parent; + private IAvnSoftwareRenderTarget? _target; + + public FramebufferRenderTarget(WindowBaseImpl parent, IAvnSoftwareRenderTarget target) { - lock (_syncRoot) + _parent = parent; + _target = target; + } + + public void Dispose() { - if (_native == null) - return false; - cb(_native); - _lastRenderedLogicalSize = _savedLogicalSize; - return true; - } - }, (int)w, (int)h, new Vector(dpi, dpi)); + lock (_parent._syncRoot) + { + _target?.Dispose(); + _target = null; } + } + + public ILockedFramebuffer Lock() + { + var w = _parent._savedLogicalSize.Width * _parent._savedScaling; + var h = _parent._savedLogicalSize.Height * _parent._savedScaling; + var dpi = _parent._savedScaling * 96; + return new DeferredFramebuffer(_target, cb => + { + lock (_parent._syncRoot) + { + if (_parent._native != null && _target != null) + { + cb(_parent._native); + _parent._lastRenderedLogicalSize = _parent._savedLogicalSize; + } + } + }, (int)w, (int)h, new Vector(dpi, dpi)); + } + } public Action LostFocus { get; set; } diff --git a/src/Modern.WindowKit/Avalonia.Win32/FramebufferManager.cs b/src/Modern.WindowKit/Avalonia.Win32/FramebufferManager.cs index 0fbabd6..86e26d7 100644 --- a/src/Modern.WindowKit/Avalonia.Win32/FramebufferManager.cs +++ b/src/Modern.WindowKit/Avalonia.Win32/FramebufferManager.cs @@ -61,6 +61,8 @@ public ILockedFramebuffer Lock() } } + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); + public void Dispose() { lock (_lock) diff --git a/src/Modern.WindowKit/Avalonia.Win32/Win32Platform.cs b/src/Modern.WindowKit/Avalonia.Win32/Win32Platform.cs index 3036e68..dbbcec1 100644 --- a/src/Modern.WindowKit/Avalonia.Win32/Win32Platform.cs +++ b/src/Modern.WindowKit/Avalonia.Win32/Win32Platform.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using Modern.WindowKit.Reactive; using System.Runtime.InteropServices; using System.Threading; @@ -10,7 +10,6 @@ using Modern.WindowKit.Controls.Platform; using Modern.WindowKit.Input; using Modern.WindowKit.Input.Platform; -//using Modern.WindowKit.OpenGL; using Modern.WindowKit.Platform; //using Modern.WindowKit.Rendering; //using Modern.WindowKit.Rendering.Composition; @@ -31,78 +30,6 @@ namespace Modern.WindowKit // "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 Modern.WindowKit.Win32 @@ -174,8 +101,22 @@ public static void Initialize(Win32PlatformOptions options) // .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?.Contains(Win32CompositionMode.RedirectionSurface) == false) + // { + // 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/Modern.WindowKit/Avalonia.Win32/Win32PlatformOptions.cs b/src/Modern.WindowKit/Avalonia.Win32/Win32PlatformOptions.cs new file mode 100644 index 0000000..7bf1bf8 --- /dev/null +++ b/src/Modern.WindowKit/Avalonia.Win32/Win32PlatformOptions.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +//using Modern.WindowKit.OpenGL; +using Modern.WindowKit.Platform; + +namespace Modern.WindowKit; + +/// +/// Represents the rendering mode for platform graphics. +/// +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 = 3 +} + +/// +/// Represents the Win32 window composition mode. +/// +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.
+ /// Can only be applied with =. + ///
+ WinUIComposition = 1, + + // /// + // /// Render Avalonia to a texture inside the DirectComposition tree. + // /// + // /// + // /// 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 .
+ /// Can only be applied with =. + ///
+ 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; } + + /// + /// 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[] + { + Win32RenderingMode.AngleEgl, Win32RenderingMode.Software + }; + + /// + /// Gets or sets Avalonia composition 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 CompositionMode { get; set; } = new[] + { + Win32CompositionMode.WinUIComposition, Win32CompositionMode.RedirectionSurface + }; + + /// + /// 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 is ignored + ///// and only accepts null or . + ///// + //public IPlatformGraphics? CustomPlatformGraphics { get; set; } +} diff --git a/src/Modern.WindowKit/Avalonia.X11/X11Clipboard.cs b/src/Modern.WindowKit/Avalonia.X11/X11Clipboard.cs index 7dbd1bd..8defc05 100644 --- a/src/Modern.WindowKit/Avalonia.X11/X11Clipboard.cs +++ b/src/Modern.WindowKit/Avalonia.X11/X11Clipboard.cs @@ -224,7 +224,7 @@ private Task SendFormatRequest() private Task SendDataRequest(IntPtr format) { - if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted) + if (_requestedDataTcs == null || _requestedDataTcs.Task.IsCompleted) _requestedDataTcs = new TaskCompletionSource(); XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero); return _requestedDataTcs.Task; diff --git a/src/Modern.WindowKit/Avalonia.X11/X11CursorFactory.cs b/src/Modern.WindowKit/Avalonia.X11/X11CursorFactory.cs index cb5c961..dbe3cff 100644 --- a/src/Modern.WindowKit/Avalonia.X11/X11CursorFactory.cs +++ b/src/Modern.WindowKit/Avalonia.X11/X11CursorFactory.cs @@ -103,7 +103,7 @@ private static IntPtr GetNullCursor(IntPtr display) // _pixelSize = bitmap.PixelSize; // _blob = runtimePlatform.AllocBlob(size); - + // var image = (XcursorImage*)_blob.Address; // image->version = 1; // image->size = Marshal.SizeOf(); @@ -112,33 +112,35 @@ private static IntPtr GetNullCursor(IntPtr display) // image->xhot = hotSpot.X; // image->yhot = hotSpot.Y; // image->pixels = (IntPtr)(image + 1); - - // using (var cpuContext = platformRenderInterface.CreateBackendContext(null)) - // using (var renderTarget = cpuContext.CreateRenderTarget(new[] { this })) - // using (var ctx = renderTarget.CreateDrawingContext()) - // { - // var r = new Rect(_pixelSize.ToSize(1)); - // ctx.DrawBitmap(bitmap, 1, r, r); - // } - - // Handle = XLib.XcursorImageLoadCursor(display, _blob.Address); - //} - - //public string HandleDescriptor => "XCURSOR"; - - //public override void Dispose() - //{ - // XLib.XcursorImageDestroy(Handle); - // _blob.Dispose(); - //} - - //public ILockedFramebuffer Lock() - //{ - // return new LockedFramebuffer( - // _blob.Address + Marshal.SizeOf(), - // _pixelSize, _pixelSize.Width * 4, - // new Vector(96, 96), PixelFormat.Bgra8888, null); - //} + + // using (var cpuContext = platformRenderInterface.CreateBackendContext(null)) + // using (var renderTarget = cpuContext.CreateRenderTarget(new[] { this })) + // using (var ctx = renderTarget.CreateDrawingContext()) + // { + // var r = new Rect(_pixelSize.ToSize(1)); + // ctx.DrawBitmap(bitmap, 1, r, r); + // } + + // Handle = XLib.XcursorImageLoadCursor(display, _blob.Address); + // } + + // public string HandleDescriptor => "XCURSOR"; + + // public override void Dispose() + // { + // XLib.XcursorImageDestroy(Handle); + // _blob.Dispose(); + // } + + // public ILockedFramebuffer Lock() + // { + // return new LockedFramebuffer( + // _blob.Address + Marshal.SizeOf(), + // _pixelSize, _pixelSize.Width * 4, + // new Vector(96, 96), PixelFormat.Bgra8888, null); + // } + + // public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); //} } @@ -148,5 +150,5 @@ public CursorImpl() { } public CursorImpl(IntPtr handle) => Handle = handle; public IntPtr Handle { get; protected set; } public virtual void Dispose() { } -} + } } diff --git a/src/Modern.WindowKit/Avalonia.X11/X11FramebufferSurface.cs b/src/Modern.WindowKit/Avalonia.X11/X11FramebufferSurface.cs index 10a64f5..51410e4 100644 --- a/src/Modern.WindowKit/Avalonia.X11/X11FramebufferSurface.cs +++ b/src/Modern.WindowKit/Avalonia.X11/X11FramebufferSurface.cs @@ -27,5 +27,7 @@ public ILockedFramebuffer Lock() XUnlockDisplay(_display); return new X11Framebuffer(_display, _xid, _depth, width, height, _scaling()); } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); } } diff --git a/src/Modern.WindowKit/Avalonia.X11/X11Platform.cs b/src/Modern.WindowKit/Avalonia.X11/X11Platform.cs index 6828c75..ab92a98 100644 --- a/src/Modern.WindowKit/Avalonia.X11/X11Platform.cs +++ b/src/Modern.WindowKit/Avalonia.X11/X11Platform.cs @@ -93,17 +93,13 @@ public void Initialize(X11PlatformOptions options) 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,84 @@ private static bool ShouldUseXim() 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 Modern.WindowKit { /// - /// Platform-specific options which apply to Linux. + /// Represents the rendering mode for platform graphics. /// - public class X11PlatformOptions + public enum X11RenderingMode { /// - /// Enables native Linux EGL when set to true. The default value is false. + /// Avalonia is rendered into a framebuffer. /// - public bool UseEGL { get; set; } + Software = 1, /// - /// Determines whether to use GPU for rendering in your project. The default value is true. + /// Enables Glx rendering. /// - public bool UseGpu { get; set; } = true; + Glx = 2, + + /// + /// Enables native Linux EGL rendering. + /// + Egl = 3 + } + + /// + /// Platform-specific options which apply to Linux. + /// + public class X11PlatformOptions + { + /// + /// 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[] + { + X11RenderingMode.Glx, X11RenderingMode.Software + }; /// /// Embeds popups to the window when set to true. The default value is false. diff --git a/src/Modern.WindowKit/Avalonia.X11/X11Window.Xim.cs b/src/Modern.WindowKit/Avalonia.X11/X11Window.Xim.cs index 3b7b362..5ba1320 100644 --- a/src/Modern.WindowKit/Avalonia.X11/X11Window.Xim.cs +++ b/src/Modern.WindowKit/Avalonia.X11/X11Window.Xim.cs @@ -15,14 +15,14 @@ private class XimInputMethod //: ITextInputMethodImpl, IX11InputMethodControl private readonly X11Window _parent; private bool _windowActive, _imeActive; private Rect? _queuedCursorRect; - //private ITextInputMethodClient? _client; + //private TextInputMethodClient? _client; public XimInputMethod(X11Window parent) { _parent = parent; } - //public ITextInputMethodClient? Client => _client; + //public TextInputMethodClient? Client => _client; //public bool IsActive => _client != null; @@ -62,7 +62,7 @@ public void SetWindowActive(bool active) UpdateActive(); } - //public void SetClient(ITextInputMethodClient client) + //public void SetClient(TextInputMethodClient client) //{ // _client = client; // UpdateActive(); diff --git a/src/Modern.WindowKit/Dispatcher.Invoke.cs b/src/Modern.WindowKit/Dispatcher.Invoke.cs index 031c1c5..905c7b5 100644 --- a/src/Modern.WindowKit/Dispatcher.Invoke.cs +++ b/src/Modern.WindowKit/Dispatcher.Invoke.cs @@ -581,11 +581,6 @@ public Task InvokeAsync(Func callback, DispatcherPriority priority) /// /// A Func<Task<TResult>> delegate to invoke through the dispatcher. /// - /// - /// The priority that determines in what order the specified - /// callback is invoked relative to the other pending operations - /// in the Dispatcher. - /// /// /// An task that completes after the task returned from callback finishes /// diff --git a/src/Modern.WindowKit/IFramebufferPlatformSurface.cs b/src/Modern.WindowKit/IFramebufferPlatformSurface.cs index 487922b..aef7d8e 100644 --- a/src/Modern.WindowKit/IFramebufferPlatformSurface.cs +++ b/src/Modern.WindowKit/IFramebufferPlatformSurface.cs @@ -1,4 +1,5 @@ -using Modern.WindowKit.Metadata; +using System; +using Modern.WindowKit.Metadata; using Modern.WindowKit.Platform; namespace Modern.WindowKit.Controls.Platform.Surfaces @@ -6,12 +7,38 @@ namespace Modern.WindowKit.Controls.Platform.Surfaces [Unstable] public interface IFramebufferPlatformSurface { - /// + IFramebufferRenderTarget CreateFramebufferRenderTarget(); + } + + [Unstable] + public interface IFramebufferRenderTarget : IDisposable + { + /// /// Provides a framebuffer descriptor for drawing. /// - /// + /// /// Contents should be drawn on actual window after disposing /// ILockedFramebuffer Lock(); } + + /// + /// For simple cases when framebuffer is always available + /// + public class FuncFramebufferRenderTarget : IFramebufferRenderTarget + { + private readonly Func _lockFramebuffer; + + public FuncFramebufferRenderTarget(Func lockFramebuffer) + { + _lockFramebuffer = lockFramebuffer; + } + + public void Dispose() + { + // No-op + } + + public ILockedFramebuffer Lock() => _lockFramebuffer(); + } } diff --git a/src/Modern.WindowKit/Modern.WindowKit.csproj b/src/Modern.WindowKit/Modern.WindowKit.csproj index 2f34360..f11d25d 100644 --- a/src/Modern.WindowKit/Modern.WindowKit.csproj +++ b/src/Modern.WindowKit/Modern.WindowKit.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Modern.WindowKit/Partials/Mac/WindowImpl.cs b/src/Modern.WindowKit/Partials/Mac/WindowImpl.cs index 9b7d3c5..bb4edc7 100644 --- a/src/Modern.WindowKit/Partials/Mac/WindowImpl.cs +++ b/src/Modern.WindowKit/Partials/Mac/WindowImpl.cs @@ -5,7 +5,7 @@ namespace Modern.WindowKit.Native internal partial class WindowImpl { public override IPopupImpl CreatePopup() => - _opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, this); + _opts.OverlayPopups ? null : new PopupImpl(_factory, this); public void ShowDialog(IWindowImpl window) { diff --git a/src/Modern.WindowKit/Point.cs b/src/Modern.WindowKit/Point.cs index b0f1587..b480e78 100644 --- a/src/Modern.WindowKit/Point.cs +++ b/src/Modern.WindowKit/Point.cs @@ -164,6 +164,19 @@ public static implicit operator Vector(Point p) ///// The resulting point. //public static Point operator *(Point point, Matrix matrix) => matrix.Transform(point); + /// + /// Computes the Euclidean distance between the two given points. + /// + /// The first point. + /// The second point. + /// The Euclidean distance. + public static double Distance(Point value1, Point value2) + { + double distanceSquared = ((value2.X - value1.X) * (value2.X - value1.X)) + + ((value2.Y - value1.Y) * (value2.Y - value1.Y)); + return Math.Sqrt(distanceSquared); + } + /// /// Parses a string. /// diff --git a/src/Modern.WindowKit/Pointer.cs b/src/Modern.WindowKit/Pointer.cs index cf5e175..0b21101 100644 --- a/src/Modern.WindowKit/Pointer.cs +++ b/src/Modern.WindowKit/Pointer.cs @@ -88,7 +88,6 @@ public void Dispose() ///// Captures pointer input to the specified gesture recognizer. ///// ///// The gesture recognizer. - ///// //internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer) //{ // if (CapturedGestureRecognizer != gestureRecognizer) diff --git a/src/Modern.WindowKit/Vector.cs b/src/Modern.WindowKit/Vector.cs index e46802f..908bd1e 100644 --- a/src/Modern.WindowKit/Vector.cs +++ b/src/Modern.WindowKit/Vector.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Numerics; #if !BUILDTASK //using Modern.WindowKit.Animation.Animators; #endif @@ -353,5 +354,58 @@ public void Deconstruct(out double x, out double y) x = this._x; y = this._y; } + + internal Vector2 ToVector2() => new Vector2((float)X, (float)Y); + + internal Vector(Vector2 v) : this(v.X, v.Y) + { + + } + + /// + /// Returns a vector whose elements are the absolute values of each of the specified vector's elements. + /// + /// + public Vector Abs() => new(Math.Abs(X), Math.Abs(Y)); + + /// + /// Restricts a vector between a minimum and a maximum value. + /// + public static Vector Clamp(Vector value, Vector min, Vector max) => + Min(Max(value, min), max); + + /// + /// Returns a vector whose elements are the maximum of each of the pairs of elements in two specified vectors + /// + public static Vector Max(Vector left, Vector right) => + new(Math.Max(left.X, right.X), Math.Max(left.Y, right.Y)); + + /// + /// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors + /// + public static Vector Min(Vector left, Vector right) => + new(Math.Min(left.X, right.X), Math.Min(left.Y, right.Y)); + + /// + /// Computes the Euclidean distance between the two given points. + /// + /// The first point. + /// The second point. + /// The Euclidean distance. + public static double Distance(Vector value1, Vector value2) => Math.Sqrt(DistanceSquared(value1, value2)); + + /// + /// Returns the Euclidean distance squared between two specified points + /// + /// The first point. + /// The second point. + /// The Euclidean distance squared. + public static double DistanceSquared(Vector value1, Vector value2) + { + var difference = value1 - value2; + return Dot(difference, difference); + } + + public static implicit operator Vector(Vector2 v) => new(v); } }