From 85732bc5817e54cf5ea19fcdd1bfb676189769e5 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 16 Jun 2023 12:04:15 +0200 Subject: [PATCH 01/32] Make sure Windows InputMethod deletes current selection before preeditText is set --- .../Avalonia.Win32/Input/Imm32InputMethod.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 05074cc82e..8502f5b41a 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -324,6 +324,11 @@ namespace Avalonia.Win32.Input if (IsActive) { Client.SetPreeditText(null); + + if (Client.SupportsSurroundingText && Client.SurroundingText.AnchorOffset != Client.SurroundingText.CursorOffset) + { + KeyPress(Key.Delete); + } } IsComposing = true; @@ -393,6 +398,19 @@ namespace Avalonia.Win32.Input return (int)(ptr.ToInt64() & 0xffffffff); } + private void KeyPress(Key key) + { + if (_parent?.Input != null) + { + _parent.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _parent.Owner, + RawKeyEventType.KeyDown, key, RawInputModifiers.None)); + + _parent.Input(new RawKeyEventArgs(KeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, _parent.Owner, + RawKeyEventType.KeyUp, key, RawInputModifiers.None)); + + } + } + ~Imm32InputMethod() { _caretManager.TryDestroy(); From 87b8c63c1b7ce8e1753ee063da919819d6814434 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 18 Jun 2023 12:16:58 -0400 Subject: [PATCH 02/32] Add Distance method to Point --- src/Avalonia.Base/Point.cs | 13 +++++++++++++ src/Avalonia.Base/Vector.cs | 14 ++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Point.cs b/src/Avalonia.Base/Point.cs index 331cce4a76..e3ea21d1eb 100644 --- a/src/Avalonia.Base/Point.cs +++ b/src/Avalonia.Base/Point.cs @@ -164,6 +164,19 @@ namespace Avalonia /// 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/Avalonia.Base/Vector.cs b/src/Avalonia.Base/Vector.cs index 15722901a6..fffe206835 100644 --- a/src/Avalonia.Base/Vector.cs +++ b/src/Avalonia.Base/Vector.cs @@ -359,7 +359,7 @@ namespace Avalonia internal Vector(Vector2 v) : this(v.X, v.Y) { - + } /// @@ -379,21 +379,27 @@ namespace Avalonia /// 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; From 84c2efde2264ea488c2830c6d41af817ec2b2149 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 19 Jun 2023 22:21:10 -0400 Subject: [PATCH 03/32] Minor app builder fix --- src/Avalonia.Controls/AppBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs index 77cc9d4dcb..1cac22c8ce 100644 --- a/src/Avalonia.Controls/AppBuilder.cs +++ b/src/Avalonia.Controls/AppBuilder.cs @@ -151,7 +151,7 @@ namespace Avalonia } throw new InvalidOperationException( - $"Unable to create AppBuilder from type {entryPointType.Name}." + + $"Unable to create AppBuilder from type \"{entryPointType.FullName}\". " + $"Input type either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application type."); } From 10941d7686b3e5dbbb18bead44cfc315235513e4 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 20 Jun 2023 05:20:46 -0400 Subject: [PATCH 04/32] Fix ColorPalette TryGetResource --- .../ColorPaletteResourcesCollection.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs index 261de5497d..f26f6d8660 100644 --- a/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs +++ b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs @@ -30,11 +30,22 @@ internal class ColorPaletteResourcesCollection : AvaloniaDictionary Count > 0; public bool TryGetResource(object key, ThemeVariant? theme, out object? value) { - theme ??= ThemeVariant.Default; - if (base.TryGetValue(theme, out var paletteResources) - && paletteResources.TryGetResource(key, theme, out value)) + if (theme is not null) { - return true; + if (base.TryGetValue(theme, out var themePaletteResources) + && themePaletteResources.TryGetResource(key, theme, out value)) + { + return true; + } + } + + if (theme != ThemeVariant.Default) + { + if (base.TryGetValue(ThemeVariant.Default, out var defaultPaletteResources) + && defaultPaletteResources.TryGetResource(key, ThemeVariant.Default, out value)) + { + return true; + } } value = null; From 3c23f01839f27ad4d832281597550f41982d012e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jun 2023 15:38:03 +0200 Subject: [PATCH 05/32] fix(ListBox): Regression space handling --- src/Avalonia.Controls/ListBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 220903cc04..2f3ba33d8d 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -148,7 +148,7 @@ namespace Avalonia.Controls } else if (e.Key == Key.Space || e.Key == Key.Enter) { - e.Handled |= UpdateSelectionFromEventSource( + UpdateSelectionFromEventSource( e.Source, true, e.KeyModifiers.HasFlag(KeyModifiers.Shift), From c368048aefe8430c3b22cf228f481daffe2b5a24 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jun 2023 15:38:31 +0200 Subject: [PATCH 06/32] test(LIstBox): Space handling --- .../ListBoxTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 03497c335d..a6a2a02479 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -1247,6 +1247,51 @@ namespace Avalonia.Controls.UnitTests data.GetRealizedItems()); } + [Fact] + public void Should_Not_Handle_Space_When_TextBox_Inside_ListBoxItem() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var target = new TextBox + { + Focusable = true + }; + var listbox = new ListBox() + { + Template = ListBoxTemplate(), + Items = + { + new ListBoxItem() + { + Template = ListBoxItemTemplate(), + Content = target, + } + } + }; + + var nKeyDown = 0; + + var root = new TestRoot() + { + Width = 1000, + Height = 1000, + Child = listbox, + }; + + root.KeyDown += (s, e) => nKeyDown++; + + + listbox.ApplyTemplate(); + root.LayoutManager.ExecuteInitialLayoutPass(); + + target.Focus(); + + RaiseKeyEvent(target, Key.Space, KeyModifiers.None); + + Assert.Equal(1, nKeyDown); + } + } + private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0) { target.RaiseEvent(new KeyEventArgs From 9b66943dd2d3cab3ecb30319d2ab26c75e778c7d Mon Sep 17 00:00:00 2001 From: Trym Lund Flogard Date: Sat, 24 Jun 2023 12:55:29 +0200 Subject: [PATCH 07/32] Use widechar variant for win32 pinvokes --- .../NativeControls/Win/WinApi.cs | 2 +- .../Interop/UnmanagedMethods.cs | 42 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs b/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs index 47d368f7a4..bd0c1bf98e 100644 --- a/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs +++ b/samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs @@ -39,7 +39,7 @@ internal unsafe class WinApi [DllImport("user32.dll", SetLastError = true)] public static extern bool DestroyWindow(IntPtr hwnd); - [DllImport("kernel32.dll")] + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryW", ExactSpelling = true)] public static extern IntPtr LoadLibrary(string lib); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 39363beae3..ce42227292 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1161,7 +1161,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); - [DllImport("user32.dll", SetLastError = true)] + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateWindowExW", ExactSpelling = true)] public static extern IntPtr CreateWindowEx( int dwExStyle, uint lpClassName, @@ -1215,16 +1215,16 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern int GetMessageTime(); - [DllImport("kernel32.dll")] + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetModuleHandleW", ExactSpelling = true)] public static extern IntPtr GetModuleHandle(string? lpModuleName); [DllImport("user32.dll")] public static extern int GetSystemMetrics(SystemMetric smIndex); - [DllImport("user32.dll", SetLastError = true)] + [DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongPtrW", ExactSpelling = true)] public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex); - [DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLong")] + [DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLongW", ExactSpelling = true)] public static extern uint GetWindowLong32b(IntPtr hWnd, int nIndex); public static uint GetWindowLong(IntPtr hWnd, int nIndex) @@ -1239,10 +1239,10 @@ namespace Avalonia.Win32.Interop } } - [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")] + [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongW", ExactSpelling = true)] private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value); - [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtr")] + [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtrW", ExactSpelling = true)] private static extern IntPtr SetWindowLong64b(IntPtr hWnd, int nIndex, IntPtr value); public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value) @@ -1310,7 +1310,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent); - [DllImport("user32.dll")] + [DllImport("user32.dll", EntryPoint = "LoadCursorW", ExactSpelling = true)] public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); [DllImport("user32.dll")] @@ -1319,7 +1319,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool DestroyIcon(IntPtr hIcon); - [DllImport("user32.dll")] + [DllImport("user32.dll", EntryPoint = "PeekMessageW", ExactSpelling = true)] public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); [DllImport("user32")] @@ -1334,7 +1334,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool ReleaseCapture(); - [DllImport("user32.dll", SetLastError = true)] + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterWindowMessageW", ExactSpelling = true)] public static extern uint RegisterWindowMessage(string lpString); [DllImport("user32.dll")] @@ -1415,7 +1415,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool TranslateMessage(ref MSG lpMsg); - [DllImport("user32.dll", CharSet = CharSet.Unicode)] + [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "UnregisterClassW", ExactSpelling = true)] public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] @@ -1439,10 +1439,10 @@ namespace Avalonia.Win32.Interop [DllImport("shell32", CharSet = CharSet.Auto)] public static extern int Shell_NotifyIcon(NIM dwMessage, NOTIFYICONDATA lpData); - [DllImport("user32.dll", EntryPoint = "SetClassLongPtr")] + [DllImport("user32.dll", EntryPoint = "SetClassLongPtrW", ExactSpelling = true)] private static extern IntPtr SetClassLong64(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong); - [DllImport("user32.dll", EntryPoint = "SetClassLong")] + [DllImport("user32.dll", EntryPoint = "SetClassLongW", ExactSpelling = true)] private static extern IntPtr SetClassLong32(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong); public static IntPtr SetClassLong(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong) @@ -1463,10 +1463,10 @@ namespace Avalonia.Win32.Interop return new IntPtr(GetClassLongPtr32(hWnd, nIndex)); } - [DllImport("user32.dll", EntryPoint = "GetClassLong")] + [DllImport("user32.dll", EntryPoint = "GetClassLongW", ExactSpelling = true)] public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex); - [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")] + [DllImport("user32.dll", EntryPoint = "GetClassLongPtrW", ExactSpelling = true)] public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "SetCursor")] @@ -1527,10 +1527,10 @@ namespace Avalonia.Win32.Interop [DllImport("kernel32.dll", ExactSpelling = true)] public static extern IntPtr GlobalFree(IntPtr hMem); - [DllImport("kernel32.dll", SetLastError = true)] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryW", ExactSpelling = true)] public static extern IntPtr LoadLibrary(string fileName); - [DllImport("kernel32.dll", SetLastError = true)] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "LoadLibraryExW", ExactSpelling = true)] public static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, int flags); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)] @@ -1645,7 +1645,7 @@ namespace Avalonia.Win32.Interop [DllImport("opengl32.dll", CharSet = CharSet.Ansi)] public static extern IntPtr wglGetProcAddress(string name); - [DllImport("kernel32.dll", SetLastError = true)] + [DllImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateFileMappingW", ExactSpelling = true)] public static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpFileMappingAttributes, uint flProtect, @@ -1668,16 +1668,16 @@ namespace Avalonia.Win32.Interop [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); - [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)] + [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "GetClipboardFormatNameW", ExactSpelling = true)] public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax); - [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)] + [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "RegisterClipboardFormatW", ExactSpelling = true)] public static extern int RegisterClipboardFormat(string format); [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)] public static extern IntPtr GlobalSize(IntPtr hGlobal); - [DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)] + [DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Unicode, EntryPoint = "DragQueryFileW", ExactSpelling = true)] public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder? lpszFile, int cch); [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] @@ -1893,7 +1893,7 @@ namespace Avalonia.Win32.Interop [DllImport("imm32.dll")] public static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT lf); - [DllImport("imm32.dll", SetLastError = false, CharSet = CharSet.Unicode)] + [DllImport("imm32.dll", SetLastError = false, CharSet = CharSet.Unicode, EntryPoint = "ImmGetCompositionStringW", ExactSpelling = true)] public static extern int ImmGetCompositionString(IntPtr hIMC, GCS dwIndex, [Out, Optional] IntPtr lpBuf, uint dwBufLen); public static string? ImmGetCompositionString(IntPtr hIMC, GCS dwIndex) From 6a3925b91b67e2a3412a7d3e3e11ba24589dfeff Mon Sep 17 00:00:00 2001 From: Trym Lund Flogard Date: Sat, 24 Jun 2023 13:49:26 +0200 Subject: [PATCH 08/32] Remove support for undocumented blur #11855 --- .../Interop/UnmanagedMethods.cs | 3 -- src/Windows/Avalonia.Win32/WindowImpl.cs | 53 ++----------------- 2 files changed, 5 insertions(+), 51 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ce42227292..e58abcadf0 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1827,9 +1827,6 @@ namespace Avalonia.Win32.Interop return result; } - [DllImport("user32.dll")] - internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); - [Flags] public enum GCS : uint { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 057cdb2db0..3fc2751e43 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -371,7 +371,7 @@ namespace Avalonia.Win32 if (level == WindowTransparencyLevel.Transparent) SetTransparencyTransparent(windowsVersion); else if (level == WindowTransparencyLevel.Blur) - SetTransparencyBlur(windowsVersion); + continue; // Unsupported on all versions (unreachable) else if (level == WindowTransparencyLevel.AcrylicBlur) SetTransparencyAcrylicBlur(windowsVersion); else if (level == WindowTransparencyLevel.Mica) @@ -409,9 +409,9 @@ namespace Avalonia.Win32 if (level == WindowTransparencyLevel.Transparent) return windowsVersion >= PlatformConstants.Windows8; - // Blur only supported on Windows 8 and lower. + // Support for legacy, undocumented DWM blur has been removed. if (level == WindowTransparencyLevel.Blur) - return windowsVersion < PlatformConstants.Windows10; + return false; // Acrylic is supported on Windows >= 10.0.15063. if (level == WindowTransparencyLevel.AcrylicBlur) @@ -426,37 +426,14 @@ namespace Avalonia.Win32 private void SetTransparencyTransparent(Version windowsVersion) { - // Transparent only supported with composition on Windows 8+. - if (!_isUsingComposition || windowsVersion < PlatformConstants.Windows8) + // Transparent only supported with composition on Windows 10+. + if (!_isUsingComposition || windowsVersion < PlatformConstants.Windows10) return; - if (windowsVersion < PlatformConstants.Windows10) - { - // Some of the AccentState Enum's values have different meanings on Windows 8.x than on - // Windows 10, hence using ACCENT_ENABLE_BLURBEHIND to disable blurbehind ¯\_(ツ)_/¯. - // Hey, I'm just porting what was here before. - SetAccentState(AccentState.ACCENT_ENABLE_BLURBEHIND); - var blurInfo = new DWM_BLURBEHIND(false); - DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); - } - SetUseHostBackdropBrush(false); _blurHost?.SetBlur(BlurEffect.None); } - private void SetTransparencyBlur(Version windowsVersion) - { - // Blur only supported with composition on Windows 8 and lower. - if (!_isUsingComposition || windowsVersion >= PlatformConstants.Windows10) - return; - - // Some of the AccentState Enum's values have different meanings on Windows 8.x than on - // Windows 10. - SetAccentState(AccentState.ACCENT_DISABLED); - var blurInfo = new DWM_BLURBEHIND(true); - DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); - } - private void SetTransparencyAcrylicBlur(Version windowsVersion) { // Acrylic blur only supported with composition on Windows >= 10.0.15063. @@ -477,26 +454,6 @@ namespace Avalonia.Win32 _blurHost?.SetBlur(BlurEffect.Mica); } - private void SetAccentState(AccentState state) - { - var accent = new AccentPolicy(); - var accentStructSize = Marshal.SizeOf(accent); - - //Some of the AccentState Enum's values have different meanings on Windows 8.x than on Windows 10 - accent.AccentState = state; - - var accentPtr = Marshal.AllocHGlobal(accentStructSize); - Marshal.StructureToPtr(accent, accentPtr, false); - - var data = new WindowCompositionAttributeData(); - data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY; - data.SizeOfData = accentStructSize; - data.Data = accentPtr; - - SetWindowCompositionAttribute(_hwnd, ref data); - Marshal.FreeHGlobal(accentPtr); - } - private void SetUseHostBackdropBrush(bool useHostBackdropBrush) { if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion) From 29d3c7670be216f704f001ee6e28efc0adbe2e83 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 25 Jun 2023 23:26:45 -0400 Subject: [PATCH 09/32] 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 10/32] 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 11/32] 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 12/32] 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 13/32] 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 14/32] 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 15/32] 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 16/32] 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; From ae368f593b856e4b3d3008e3a6c2ccfea7fc06e7 Mon Sep 17 00:00:00 2001 From: Vitalii Orazov Date: Wed, 28 Jun 2023 01:27:12 +0300 Subject: [PATCH 17/32] fix SendDataRequest re-run task --- src/Avalonia.X11/X11Clipboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index 04d1aae194..6e18151fae 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -224,7 +224,7 @@ namespace Avalonia.X11 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; From 7ac63a593b9abcaa5dc8c8833a78a285329c749f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 28 Jun 2023 15:22:33 -0400 Subject: [PATCH 18/32] Seal geometry class --- src/Avalonia.Base/Media/Geometry.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index 0d2311eafc..3e0e6018de 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -28,6 +28,11 @@ namespace Avalonia.Media TransformProperty.Changed.AddClassHandler((x,e) => x.TransformChanged(e)); } + internal Geometry() + { + + } + /// /// Raised when the geometry changes. /// From fbca526f85b64cd9b26fe03213d3d0c96344a534 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 28 Jun 2023 15:31:29 -0400 Subject: [PATCH 19/32] Seal CreateDefiningGeometry --- src/Avalonia.Base/CombinedGeometry.cs | 4 +--- src/Avalonia.Base/Media/EllipseGeometry.cs | 2 +- src/Avalonia.Base/Media/Geometry.cs | 2 +- src/Avalonia.Base/Media/GeometryGroup.cs | 4 +--- src/Avalonia.Base/Media/LineGeometry.cs | 2 +- src/Avalonia.Base/Media/PathGeometry.cs | 4 ++-- src/Avalonia.Base/Media/PlatformGeometry.cs | 4 ++-- src/Avalonia.Base/Media/PolylineGeometry.cs | 2 +- src/Avalonia.Base/Media/RectangleGeometry.cs | 2 +- src/Avalonia.Base/Media/StreamGeometry.cs | 4 ++-- tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs | 2 +- 11 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Base/CombinedGeometry.cs b/src/Avalonia.Base/CombinedGeometry.cs index 4b5866519b..a47e756c88 100644 --- a/src/Avalonia.Base/CombinedGeometry.cs +++ b/src/Avalonia.Base/CombinedGeometry.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Text; using Avalonia.Platform; -#nullable enable - namespace Avalonia.Media { public enum GeometryCombineMode @@ -147,7 +145,7 @@ namespace Avalonia.Media return new CombinedGeometry(GeometryCombineMode, Geometry1, Geometry2, Transform); } - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var g1 = Geometry1; var g2 = Geometry2; diff --git a/src/Avalonia.Base/Media/EllipseGeometry.cs b/src/Avalonia.Base/Media/EllipseGeometry.cs index 84d74e888e..bb1263d8d5 100644 --- a/src/Avalonia.Base/Media/EllipseGeometry.cs +++ b/src/Avalonia.Base/Media/EllipseGeometry.cs @@ -135,7 +135,7 @@ namespace Avalonia.Media } /// - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var factory = AvaloniaLocator.Current.GetRequiredService(); diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index 3e0e6018de..a66cd616a3 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -139,7 +139,7 @@ namespace Avalonia.Media /// Creates the platform implementation of the geometry, without the transform applied. /// /// - protected abstract IGeometryImpl? CreateDefiningGeometry(); + private protected abstract IGeometryImpl? CreateDefiningGeometry(); /// /// Invalidates the platform implementation of the geometry. diff --git a/src/Avalonia.Base/Media/GeometryGroup.cs b/src/Avalonia.Base/Media/GeometryGroup.cs index 3e61413919..20bd297fc1 100644 --- a/src/Avalonia.Base/Media/GeometryGroup.cs +++ b/src/Avalonia.Base/Media/GeometryGroup.cs @@ -1,8 +1,6 @@ using Avalonia.Metadata; using Avalonia.Platform; -#nullable enable - namespace Avalonia.Media { /// @@ -72,7 +70,7 @@ namespace Avalonia.Media newChildren.Parent = this; } - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { if (_children.Count > 0) { diff --git a/src/Avalonia.Base/Media/LineGeometry.cs b/src/Avalonia.Base/Media/LineGeometry.cs index 6ac92ea33b..ced208e30b 100644 --- a/src/Avalonia.Base/Media/LineGeometry.cs +++ b/src/Avalonia.Base/Media/LineGeometry.cs @@ -68,7 +68,7 @@ namespace Avalonia.Media } /// - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var factory = AvaloniaLocator.Current.GetRequiredService(); diff --git a/src/Avalonia.Base/Media/PathGeometry.cs b/src/Avalonia.Base/Media/PathGeometry.cs index 8292afde7e..bdfbfadce4 100644 --- a/src/Avalonia.Base/Media/PathGeometry.cs +++ b/src/Avalonia.Base/Media/PathGeometry.cs @@ -43,7 +43,7 @@ namespace Avalonia.Media /// /// The s. /// - public static new PathGeometry Parse(string pathData) + public new static PathGeometry Parse(string pathData) { var pathGeometry = new PathGeometry(); @@ -81,7 +81,7 @@ namespace Avalonia.Media set { SetValue(FillRuleProperty, value); } } - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var figures = Figures; diff --git a/src/Avalonia.Base/Media/PlatformGeometry.cs b/src/Avalonia.Base/Media/PlatformGeometry.cs index f25a14540f..e1488a8229 100644 --- a/src/Avalonia.Base/Media/PlatformGeometry.cs +++ b/src/Avalonia.Base/Media/PlatformGeometry.cs @@ -2,7 +2,7 @@ namespace Avalonia.Media { - internal class PlatformGeometry : Geometry + internal sealed class PlatformGeometry : Geometry { private readonly IGeometryImpl _geometryImpl; @@ -16,7 +16,7 @@ namespace Avalonia.Media return new PlatformGeometry(_geometryImpl); } - protected override IGeometryImpl? CreateDefiningGeometry() + private protected override IGeometryImpl? CreateDefiningGeometry() { return _geometryImpl; } diff --git a/src/Avalonia.Base/Media/PolylineGeometry.cs b/src/Avalonia.Base/Media/PolylineGeometry.cs index b0229b6455..47cf2f48a4 100644 --- a/src/Avalonia.Base/Media/PolylineGeometry.cs +++ b/src/Avalonia.Base/Media/PolylineGeometry.cs @@ -74,7 +74,7 @@ namespace Avalonia.Media return new PolylineGeometry(Points, IsFilled); } - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var factory = AvaloniaLocator.Current.GetRequiredService(); var geometry = factory.CreateStreamGeometry(); diff --git a/src/Avalonia.Base/Media/RectangleGeometry.cs b/src/Avalonia.Base/Media/RectangleGeometry.cs index 0bf9eb5664..01771242a7 100644 --- a/src/Avalonia.Base/Media/RectangleGeometry.cs +++ b/src/Avalonia.Base/Media/RectangleGeometry.cs @@ -47,7 +47,7 @@ namespace Avalonia.Media /// public override Geometry Clone() => new RectangleGeometry(Rect); - protected override IGeometryImpl? CreateDefiningGeometry() + private protected sealed override IGeometryImpl? CreateDefiningGeometry() { var factory = AvaloniaLocator.Current.GetRequiredService(); diff --git a/src/Avalonia.Base/Media/StreamGeometry.cs b/src/Avalonia.Base/Media/StreamGeometry.cs index fb79488e0f..9969376896 100644 --- a/src/Avalonia.Base/Media/StreamGeometry.cs +++ b/src/Avalonia.Base/Media/StreamGeometry.cs @@ -31,7 +31,7 @@ namespace Avalonia.Media /// /// The string. /// A . - public static new StreamGeometry Parse(string s) + public new static StreamGeometry Parse(string s) { var streamGeometry = new StreamGeometry(); @@ -62,7 +62,7 @@ namespace Avalonia.Media } /// - protected override IGeometryImpl? CreateDefiningGeometry() + private protected override IGeometryImpl? CreateDefiningGeometry() { if (_impl == null) { diff --git a/tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs b/tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs index ae8dc9dad2..6d724c86b5 100644 --- a/tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/GeometryTests.cs @@ -103,7 +103,7 @@ namespace Avalonia.Base.UnitTests.Media throw new NotImplementedException(); } - protected override IGeometryImpl CreateDefiningGeometry() + private protected sealed override IGeometryImpl CreateDefiningGeometry() { return Mock.Of( x => x.WithTransform(It.IsAny()) == From 544cf0fdabd2c9f44219d79513caf690f4dd5135 Mon Sep 17 00:00:00 2001 From: Trym Lund Flogard Date: Wed, 28 Jun 2023 22:36:50 +0200 Subject: [PATCH 20/32] Revert "Remove support for undocumented blur #11855" This reverts commit 6a3925b91b67e2a3412a7d3e3e11ba24589dfeff. --- .../Interop/UnmanagedMethods.cs | 3 ++ src/Windows/Avalonia.Win32/WindowImpl.cs | 53 +++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index e58abcadf0..ce42227292 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1827,6 +1827,9 @@ namespace Avalonia.Win32.Interop return result; } + [DllImport("user32.dll")] + internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); + [Flags] public enum GCS : uint { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3fc2751e43..057cdb2db0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -371,7 +371,7 @@ namespace Avalonia.Win32 if (level == WindowTransparencyLevel.Transparent) SetTransparencyTransparent(windowsVersion); else if (level == WindowTransparencyLevel.Blur) - continue; // Unsupported on all versions (unreachable) + SetTransparencyBlur(windowsVersion); else if (level == WindowTransparencyLevel.AcrylicBlur) SetTransparencyAcrylicBlur(windowsVersion); else if (level == WindowTransparencyLevel.Mica) @@ -409,9 +409,9 @@ namespace Avalonia.Win32 if (level == WindowTransparencyLevel.Transparent) return windowsVersion >= PlatformConstants.Windows8; - // Support for legacy, undocumented DWM blur has been removed. + // Blur only supported on Windows 8 and lower. if (level == WindowTransparencyLevel.Blur) - return false; + return windowsVersion < PlatformConstants.Windows10; // Acrylic is supported on Windows >= 10.0.15063. if (level == WindowTransparencyLevel.AcrylicBlur) @@ -426,14 +426,37 @@ namespace Avalonia.Win32 private void SetTransparencyTransparent(Version windowsVersion) { - // Transparent only supported with composition on Windows 10+. - if (!_isUsingComposition || windowsVersion < PlatformConstants.Windows10) + // Transparent only supported with composition on Windows 8+. + if (!_isUsingComposition || windowsVersion < PlatformConstants.Windows8) return; + if (windowsVersion < PlatformConstants.Windows10) + { + // Some of the AccentState Enum's values have different meanings on Windows 8.x than on + // Windows 10, hence using ACCENT_ENABLE_BLURBEHIND to disable blurbehind ¯\_(ツ)_/¯. + // Hey, I'm just porting what was here before. + SetAccentState(AccentState.ACCENT_ENABLE_BLURBEHIND); + var blurInfo = new DWM_BLURBEHIND(false); + DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); + } + SetUseHostBackdropBrush(false); _blurHost?.SetBlur(BlurEffect.None); } + private void SetTransparencyBlur(Version windowsVersion) + { + // Blur only supported with composition on Windows 8 and lower. + if (!_isUsingComposition || windowsVersion >= PlatformConstants.Windows10) + return; + + // Some of the AccentState Enum's values have different meanings on Windows 8.x than on + // Windows 10. + SetAccentState(AccentState.ACCENT_DISABLED); + var blurInfo = new DWM_BLURBEHIND(true); + DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); + } + private void SetTransparencyAcrylicBlur(Version windowsVersion) { // Acrylic blur only supported with composition on Windows >= 10.0.15063. @@ -454,6 +477,26 @@ namespace Avalonia.Win32 _blurHost?.SetBlur(BlurEffect.Mica); } + private void SetAccentState(AccentState state) + { + var accent = new AccentPolicy(); + var accentStructSize = Marshal.SizeOf(accent); + + //Some of the AccentState Enum's values have different meanings on Windows 8.x than on Windows 10 + accent.AccentState = state; + + var accentPtr = Marshal.AllocHGlobal(accentStructSize); + Marshal.StructureToPtr(accent, accentPtr, false); + + var data = new WindowCompositionAttributeData(); + data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY; + data.SizeOfData = accentStructSize; + data.Data = accentPtr; + + SetWindowCompositionAttribute(_hwnd, ref data); + Marshal.FreeHGlobal(accentPtr); + } + private void SetUseHostBackdropBrush(bool useHostBackdropBrush) { if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion) From 8f7cf7cc4a8d8d5f937f3d20b61f026ec007696f Mon Sep 17 00:00:00 2001 From: Trym Lund Flogard Date: Wed, 28 Jun 2023 22:47:11 +0200 Subject: [PATCH 21/32] Invoke SetWindowCompositionAttribute dynamically without exposing it to ILC --- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ce42227292..68f3e3c670 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1827,8 +1827,21 @@ namespace Avalonia.Win32.Interop return result; } - [DllImport("user32.dll")] - internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); + internal static int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data) + { + var user32 = LoadLibrary("user32.dll"); + var pfnSetWindowCompositionAttribute = (delegate* unmanaged[Stdcall])GetProcAddress(user32, nameof(SetWindowCompositionAttribute)); + if (pfnSetWindowCompositionAttribute == null) + { + // This preserves the same behavior as using the DllImport attribute. + throw new EntryPointNotFoundException("The unsupported SetWindowCompositionAttribute-function has been removed from the operating system."); + } + + fixed (WindowCompositionAttributeData* pData = &data) + { + return pfnSetWindowCompositionAttribute(hwnd, pData); + } + } [Flags] public enum GCS : uint From d76c06d4c4e77bd29577b0671b69ba13d3dcc9d5 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 29 Jun 2023 10:55:29 +0200 Subject: [PATCH 22/32] Make TextInputEventArgs.Text settable --- src/Avalonia.Base/Input/TextInputEventArgs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs index f4acd694a0..305b4d81f8 100644 --- a/src/Avalonia.Base/Input/TextInputEventArgs.cs +++ b/src/Avalonia.Base/Input/TextInputEventArgs.cs @@ -4,6 +4,6 @@ namespace Avalonia.Input { public class TextInputEventArgs : RoutedEventArgs { - public string? Text { get; init; } + public string? Text { get; set; } } } From 1381ec28be5202139fca91ac6c7f2539401171bc Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 29 Jun 2023 17:04:24 -0400 Subject: [PATCH 23/32] Allow only Light and Dark in the ColorPaletteResourcesCollection --- samples/ControlCatalog/App.xaml | 3 +++ samples/ControlCatalog/App.xaml.cs | 4 +-- .../ColorPaletteResourcesCollection.cs | 25 +++++++++---------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 64bf3e53b3..02b1242471 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -28,6 +28,9 @@ + + + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 246fe4385f..605254c995 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -30,8 +30,8 @@ namespace ControlCatalog AvaloniaXamlLoader.Load(this); - _fluentTheme = new FluentTheme(); - _simpleTheme = new SimpleTheme(); + _fluentTheme = (FluentTheme)Resources["FluentTheme"]!; + _simpleTheme = (SimpleTheme)Resources["SimpleTheme"]!; _colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!; _colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!; _dataGridFluent = (IStyle)Resources["DataGridFluent"]!; diff --git a/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs index f26f6d8660..6eb465d399 100644 --- a/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs +++ b/src/Avalonia.Themes.Fluent/ColorPaletteResourcesCollection.cs @@ -10,12 +10,18 @@ internal class ColorPaletteResourcesCollection : AvaloniaDictionary + (key, x) => { if (Owner is not null) { x.PropertyChanged += Palette_PropertyChanged; } + + if (key != ThemeVariant.Dark && key != ThemeVariant.Light) + { + throw new InvalidOperationException( + $"{nameof(FluentTheme)}.{nameof(FluentTheme.Palettes)} only supports Light and Dark variants."); + } }, (_, x) => { @@ -30,22 +36,15 @@ internal class ColorPaletteResourcesCollection : AvaloniaDictionary Count > 0; public bool TryGetResource(object key, ThemeVariant? theme, out object? value) { - if (theme is not null) + if (theme == null || theme == ThemeVariant.Default) { - if (base.TryGetValue(theme, out var themePaletteResources) - && themePaletteResources.TryGetResource(key, theme, out value)) - { - return true; - } + theme = ThemeVariant.Light; } - if (theme != ThemeVariant.Default) + if (base.TryGetValue(theme, out var themePaletteResources) + && themePaletteResources.TryGetResource(key, theme, out value)) { - if (base.TryGetValue(ThemeVariant.Default, out var defaultPaletteResources) - && defaultPaletteResources.TryGetResource(key, ThemeVariant.Default, out value)) - { - return true; - } + return true; } value = null; From 43e97d98959a54b54b9b748c31059a5ba3a3c214 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 30 Jun 2023 10:34:40 +0200 Subject: [PATCH 24/32] Reset TextBox :empty class when text is empty but preedit is set Delete selection when composition starts --- src/Avalonia.Controls/TextBox.cs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 3016dc8239..3b92baa42a 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -814,7 +814,12 @@ namespace Avalonia.Controls if (IsFocused) { - _presenter?.ShowCaret(); + if(_presenter != null) + { + _presenter.ShowCaret(); + + _presenter.PropertyChanged += PresenterPropertyChanged; + } } } @@ -822,9 +827,29 @@ namespace Avalonia.Controls { base.OnDetachedFromVisualTree(e); + if (_presenter != null) + { + _presenter.HideCaret(); + + _presenter.PropertyChanged -= PresenterPropertyChanged; + } + _imClient.SetPresenter(null, null); } + private void PresenterPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if(e.Property == TextPresenter.PreeditTextProperty) + { + if(string.IsNullOrEmpty(e.OldValue as string) && !string.IsNullOrEmpty(e.NewValue as string)) + { + PseudoClasses.Set(":empty", false); + + DeleteSelection(); + } + } + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); From 8680c612fd9ce358c5cb93f4159de12e2b904263 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 30 Jun 2023 15:42:12 +0600 Subject: [PATCH 25/32] OSX rendering refactoring --- Avalonia.Desktop.slnf | 1 + Avalonia.sln | 6 + build/CoreLibraries.props | 1 + native/Avalonia.Native/inc/com.h | 1 + native/Avalonia.Native/inc/comimpl.h | 19 ++ native/Avalonia.Native/inc/noarc.h | 11 + native/Avalonia.Native/inc/rendertarget.h | 16 +- .../project.pbxproj | 20 ++ native/Avalonia.Native/src/OSX/AvnView.h | 5 +- native/Avalonia.Native/src/OSX/AvnView.mm | 46 ++-- native/Avalonia.Native/src/OSX/PopupImpl.mm | 6 +- .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 13 +- .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 58 +++-- native/Avalonia.Native/src/OSX/WindowImpl.h | 2 +- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- native/Avalonia.Native/src/OSX/common.h | 31 ++- native/Avalonia.Native/src/OSX/main.mm | 36 ++- native/Avalonia.Native/src/OSX/metal.mm | 161 ++++++++++++ native/Avalonia.Native/src/OSX/noarc.mm | 11 + .../Avalonia.Native/src/OSX/rendertarget.mm | 245 ++++++++++++------ .../SkiaPlatform/FramebufferManager.cs | 2 + .../RenderTargetNotReadyException.cs | 25 ++ .../Server/ServerCompositionTarget.cs | 13 +- .../Surfaces/IFramebufferPlatformSurface.cs | 29 ++- .../Remote/Server/RemoteServerTopLevelImpl.cs | 6 +- src/Avalonia.Metal/Avalonia.Metal.csproj | 15 ++ src/Avalonia.Metal/IMetalDevice.cs | 34 +++ .../AvaloniaNativeGlPlatformGraphics.cs | 5 +- src/Avalonia.Native/AvaloniaNativePlatform.cs | 42 ++- .../AvaloniaNativePlatformExtensions.cs | 2 + src/Avalonia.Native/AvnDispatcher.cs | 18 ++ src/Avalonia.Native/DeferredFramebuffer.cs | 49 +--- src/Avalonia.Native/Metal.cs | 119 +++++++++ src/Avalonia.Native/PopupImpl.cs | 12 +- src/Avalonia.Native/WindowImpl.cs | 10 +- src/Avalonia.Native/WindowImplBase.cs | 67 +++-- src/Avalonia.Native/avn.idl | 53 +++- src/Avalonia.X11/X11CursorFactory.cs | 2 + src/Avalonia.X11/X11FramebufferSurface.cs | 2 + src/Avalonia.X11/X11IconLoader.cs | 2 + .../Skia/BrowserSkiaRasterSurface.cs | 2 + .../Avalonia.Headless/HeadlessWindowImpl.cs | 2 + .../Output/FbdevOutput.cs | 2 + .../Avalonia.Skia/FramebufferRenderTarget.cs | 11 +- .../Avalonia.Skia/Gpu/Metal/SkiaMetalApi.cs | 80 ++++++ .../Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs | 119 +++++++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 4 + .../Avalonia.Skia/RenderTargetBitmapImpl.cs | 2 + .../FramebufferShimRenderTarget.cs | 12 +- .../Avalonia.Win32/FramebufferManager.cs | 2 + .../Avalonia.RenderTests/Media/BitmapTests.cs | 6 +- tests/Avalonia.RenderTests/TestBase.cs | 5 +- .../CompositorTestServices.cs | 2 + 53 files changed, 1204 insertions(+), 243 deletions(-) create mode 100644 native/Avalonia.Native/inc/noarc.h create mode 100644 native/Avalonia.Native/src/OSX/metal.mm create mode 100644 native/Avalonia.Native/src/OSX/noarc.mm create mode 100644 src/Avalonia.Base/RenderTargetNotReadyException.cs create mode 100644 src/Avalonia.Metal/Avalonia.Metal.csproj create mode 100644 src/Avalonia.Metal/IMetalDevice.cs create mode 100644 src/Avalonia.Native/AvnDispatcher.cs create mode 100644 src/Avalonia.Native/Metal.cs create mode 100644 src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalApi.cs create mode 100644 src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 5e770a0170..6089a06d4f 100644 --- a/Avalonia.Desktop.slnf +++ b/Avalonia.Desktop.slnf @@ -24,6 +24,7 @@ "src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj", "src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj", "src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj", + "src\\Avalonia.Metal\\Avalonia.Metal.csproj", "src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj", "src\\Avalonia.Native\\Avalonia.Native.csproj", "src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj", diff --git a/Avalonia.sln b/Avalonia.sln index 107c55aa6f..d5419365ac 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -274,6 +274,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit.Uni EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Browser", "samples\MobileSandbox.Browser\MobileSandbox.Browser.csproj", "{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Metal", "src\Avalonia.Metal\Avalonia.Metal.csproj", "{60B4ED1F-ECFA-453B-8A70-1788261C8355}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -661,6 +663,10 @@ Global {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.Build.0 = Release|Any CPU + {60B4ED1F-ECFA-453B-8A70-1788261C8355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60B4ED1F-ECFA-453B-8A70-1788261C8355}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60B4ED1F-ECFA-453B-8A70-1788261C8355}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60B4ED1F-ECFA-453B-8A70-1788261C8355}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props index 00a1e3094b..a9af587a45 100644 --- a/build/CoreLibraries.props +++ b/build/CoreLibraries.props @@ -4,6 +4,7 @@ + diff --git a/native/Avalonia.Native/inc/com.h b/native/Avalonia.Native/inc/com.h index df251514ef..42a989e050 100644 --- a/native/Avalonia.Native/inc/com.h +++ b/native/Avalonia.Native/inc/com.h @@ -28,6 +28,7 @@ typedef DWORD ULONG; #define E_UNEXPECTED 0x8000FFFFL #define E_HANDLE 0x80070006L #define E_INVALIDARG 0x80070057L +#define COR_E_INVALIDOPERATION 0x80131509L struct IUnknown { diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index 47b0a3c5f2..017de01ee9 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -43,6 +43,18 @@ public: _obj->AddRef(); } } + + ComPtr(TInterface* pObj, bool ownsHandle) + { + _obj = 0; + + if (pObj) + { + _obj = pObj; + if(!ownsHandle) + _obj->AddRef(); + } + } ComPtr(const ComPtr& ptr) { @@ -92,6 +104,13 @@ public: { return &_obj; } + + void setNoAddRef(TInterface* value) + { + if(_obj != nullptr) + _obj->Release(); + _obj = value; + } operator TInterface*() const { diff --git a/native/Avalonia.Native/inc/noarc.h b/native/Avalonia.Native/inc/noarc.h new file mode 100644 index 0000000000..c49d975ade --- /dev/null +++ b/native/Avalonia.Native/inc/noarc.h @@ -0,0 +1,11 @@ +#import + +class CppAutoreleasePool +{ + void* _pool; +public: + CppAutoreleasePool(); + ~CppAutoreleasePool(); +}; + +#define START_ARP_CALL CppAutoreleasePool __autoreleasePool \ No newline at end of file diff --git a/native/Avalonia.Native/inc/rendertarget.h b/native/Avalonia.Native/inc/rendertarget.h index a59915777f..28572bc566 100644 --- a/native/Avalonia.Native/inc/rendertarget.h +++ b/native/Avalonia.Native/inc/rendertarget.h @@ -5,13 +5,21 @@ #include "avalonia-native.h" @protocol IRenderTarget --(void) setNewLayer: (CALayer*) layer; --(HRESULT) setSwFrame: (AvnFramebuffer*) fb; + -(void) resize: (AvnPixelSize) size withScale: (float) scale; --(AvnPixelSize) pixelSize; --(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget; +-(CALayer*) layer; + @end @interface IOSurfaceRenderTarget : NSObject -(IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context; +-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget; +-(IAvnSoftwareRenderTarget*) createSoftwareRenderTarget; +-(HRESULT) setSwFrame: (AvnFramebuffer*) fb; +-(void)consumeSurfaces; @end + +@interface MetalRenderTarget : NSObject +-(MetalRenderTarget*) initWithDevice: (IAvnMetalDevice*) device; +-(void) getRenderTarget: (IAvnMetalRenderTarget**) ppv; +@end \ No newline at end of file diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index eb0b528fd3..f11b027173 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -43,6 +43,11 @@ 523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; }; 5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; }; 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; }; + 64B1EA48E308E574685AFB07 /* metal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 64B1EBEECBE13D8616D7C934 /* metal.mm */; }; + 64B1ECA861163C0EFF0E502B /* noarc.h in Headers */ = {isa = PBXBuildFile; fileRef = 64B1E26F2B1B9C577BF52F06 /* noarc.h */; }; + 64B1ED7C5433AC3B815FFCC5 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64B1E23CAFE5FEC7AD981B1B /* Metal.framework */; }; + 64B1EE5DD4D882D587CE76CE /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64B1E677F7791226F783ACE4 /* CoreGraphics.framework */; }; + 64B1EF3C757B71526FFAF436 /* noarc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */; }; 8D2F3512292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */; }; 8D300D65292D0A6800320C49 /* AvnTextInputMethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */; }; @@ -99,6 +104,11 @@ 5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = ""; }; 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = ""; }; 5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = ""; }; + 64B1E23CAFE5FEC7AD981B1B /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 64B1E26F2B1B9C577BF52F06 /* noarc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = noarc.h; path = ../../inc/noarc.h; sourceTree = ""; }; + 64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = noarc.mm; sourceTree = ""; }; + 64B1E677F7791226F783ACE4 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 64B1EBEECBE13D8616D7C934 /* metal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = metal.mm; sourceTree = ""; }; 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformBehaviorInhibition.mm; sourceTree = ""; }; 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethodDelegate.h; sourceTree = ""; }; 8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethod.h; sourceTree = ""; }; @@ -126,6 +136,8 @@ AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */, 522D5959258159C1006F7F7A /* Carbon.framework in Frameworks */, AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */, + 64B1ED7C5433AC3B815FFCC5 /* Metal.framework in Frameworks */, + 64B1EE5DD4D882D587CE76CE /* CoreGraphics.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -141,6 +153,8 @@ 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */, AB1E522B217613570091CD71 /* OpenGL.framework */, AB661C1D2148230F00291242 /* AppKit.framework */, + 64B1E23CAFE5FEC7AD981B1B /* Metal.framework */, + 64B1E677F7791226F783ACE4 /* CoreGraphics.framework */, ); name = Frameworks; sourceTree = ""; @@ -198,6 +212,9 @@ 18391DB45C7D892E61BF388C /* WindowInterfaces.h */, 18391BB698579F40F1783F31 /* PopupImpl.mm */, 183910513F396141938832B5 /* PopupImpl.h */, + 64B1EBEECBE13D8616D7C934 /* metal.mm */, + 64B1E4FA7D9D6E5F47AA8606 /* noarc.mm */, + 64B1E26F2B1B9C577BF52F06 /* noarc.h */, ); sourceTree = ""; }; @@ -230,6 +247,7 @@ 18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */, 183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */, 18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */, + 64B1ECA861163C0EFF0E502B /* noarc.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -319,6 +337,8 @@ 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */, 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */, EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */, + 64B1EA48E308E574685AFB07 /* metal.mm in Sources */, + 64B1EF3C757B71526FFAF436 /* noarc.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/native/Avalonia.Native/src/OSX/AvnView.h b/native/Avalonia.Native/src/OSX/AvnView.h index 256caa70e9..e058656d3f 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.h +++ b/native/Avalonia.Native/src/OSX/AvnView.h @@ -11,15 +11,16 @@ #include "KeyTransform.h" @class AvnAccessibilityElement; +@protocol IRenderTarget; -@interface AvnView : NSView +@interface AvnView : NSView -(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent; -(NSEvent* _Nonnull) lastMouseDownEvent; -(AvnPoint) translateLocalPoint:(AvnPoint)pt; --(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose; -(void) onClosed; -(AvnPlatformResizeReason) getResizeReason; -(void) setResizeReason:(AvnPlatformResizeReason)reason; +-(void) setRenderTarget:(NSObject*)target; + (AvnPoint)toAvnPoint:(CGPoint)p; @end diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index 86bacfb819..a4999b8df3 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -17,7 +17,7 @@ NSEvent* _lastMouseDownEvent; bool _lastKeyHandled; AvnPixelSize _lastPixelSize; - NSObject* _renderTarget; + NSObject* _currentRenderTarget; AvnPlatformResizeReason _resizeReason; AvnAccessibilityElement* _accessibilityChild; NSRect _cursorRect; @@ -41,15 +41,39 @@ - (void) updateRenderTarget { - [_renderTarget resize:_lastPixelSize withScale:static_cast([[self window] backingScaleFactor])]; - [self setNeedsDisplayInRect:[self frame]]; + if(_currentRenderTarget) { + [_currentRenderTarget resize:_lastPixelSize withScale:static_cast([[self window] backingScaleFactor])]; + [self setNeedsDisplayInRect:[self frame]]; + } +} + + +-(void) setRenderTarget:(NSObject*)target +{ + if([self layer]) + { + [self layer].delegate = nil; + } + _currentRenderTarget = target; + auto layer = [target layer]; + [self setLayer: layer]; + [layer setDelegate: self]; + layer.needsDisplayOnBoundsChange = YES; + [self updateRenderTarget]; +} + +-(void)displayLayer: (CALayer*)layer +{ + [self updateLayer]; } -(AvnView*) initWithParent: (WindowBaseImpl*) parent { self = [super init]; - _renderTarget = parent->renderTarget; [self setWantsLayer:YES]; + [self setLayerContentsPlacement: NSViewLayerContentsPlacementTopLeft]; + + [self setCanDrawSubviewsIntoLayer: NO]; [self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize]; _parent = parent; @@ -77,12 +101,6 @@ return YES; } -- (void)setLayer:(CALayer *)layer -{ - [_renderTarget setNewLayer: layer]; - [super setLayer: layer]; -} - - (BOOL)isOpaque { return YES; @@ -164,14 +182,6 @@ return; } --(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose -{ - @autoreleasepool { - [_renderTarget setSwFrame:fb]; - dispose->Release(); - } -} - - (AvnPoint) translateLocalPoint:(AvnPoint)pt { pt.Y = [self bounds].size.height - pt.Y; diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm index 972d03d08c..39aa568134 100644 --- a/native/Avalonia.Native/src/OSX/PopupImpl.mm +++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm @@ -23,7 +23,7 @@ private: END_INTERFACE_MAP() virtual ~PopupImpl(){} ComPtr WindowEvents; - PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl) + PopupImpl(IAvnWindowEvents* events) : WindowBaseImpl(events) { WindowEvents = events; [Window setLevel:NSPopUpMenuWindowLevel]; @@ -47,11 +47,11 @@ public: }; -extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl) +extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events) { @autoreleasepool { - IAvnPopup* ptr = dynamic_cast(new PopupImpl(events, gl)); + IAvnPopup* ptr = dynamic_cast(new PopupImpl(events)); return ptr; } } diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index bc35ca670f..83c7aed5d3 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -27,7 +27,7 @@ BEGIN_INTERFACE_MAP() virtual ~WindowBaseImpl(); - WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel = false); + WindowBaseImpl(IAvnWindowBaseEvents *events, bool usePanel = false); virtual HRESULT ObtainNSWindowHandle(void **ret) override; @@ -81,13 +81,15 @@ BEGIN_INTERFACE_MAP() virtual HRESULT PointToScreen(AvnPoint point, AvnPoint *ret) override; - virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) override; - virtual HRESULT SetCursor(IAvnCursor *cursor) override; virtual void UpdateCursor(); - virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) override; + virtual HRESULT CreateSoftwareRenderTarget(IAvnSoftwareRenderTarget **ppv) override; + + virtual HRESULT CreateGlRenderTarget(IAvnGlContext* glContext, IAvnGlSurfaceRenderTarget **ppv) override; + + virtual HRESULT CreateMetalRenderTarget(IAvnMetalDevice* device, IAvnMetalRenderTarget **ppv) override; virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost **retOut) override; @@ -118,7 +120,6 @@ private: void CleanNSWindow (); NSCursor *cursor; - ComPtr _glContext; bool hasPosition; NSSize lastSize; NSSize lastMinSize; @@ -132,7 +133,7 @@ protected: bool _shown; public: - NSObject *renderTarget; + NSObject *currentRenderTarget; NSWindow * Window; ComPtr BaseEvents; ComPtr InputMethod; diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index edf6bcf508..b954bdd4e3 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -16,6 +16,7 @@ #import "WindowInterfaces.h" #include "WindowBaseImpl.h" #include "AvnTextInputMethod.h" +#include "AvnView.h" WindowBaseImpl::~WindowBaseImpl() { @@ -23,12 +24,10 @@ WindowBaseImpl::~WindowBaseImpl() { Window = nullptr; } -WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel) { +WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, bool usePanel) { _shown = false; _inResize = false; BaseEvents = events; - _glContext = gl; - renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext:gl]; View = [[AvnView alloc] initWithParent:this]; InputMethod = new AvnTextInputMethod(View); StandardContainer = [[AutoFitContentView new] initWithContent:View]; @@ -448,13 +447,6 @@ HRESULT WindowBaseImpl::PointToScreen(AvnPoint point, AvnPoint *ret) { } } -HRESULT WindowBaseImpl::ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) { - START_COM_CALL; - - [View setSwRenderedFrame:fb dispose:dispose]; - return S_OK; -} - HRESULT WindowBaseImpl::SetCursor(IAvnCursor *cursor) { START_COM_CALL; @@ -479,13 +471,49 @@ void WindowBaseImpl::UpdateCursor() { } } -HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) { +HRESULT WindowBaseImpl::CreateSoftwareRenderTarget(IAvnSoftwareRenderTarget **ppv) { START_COM_CALL; + if(![NSThread isMainThread]) + return COR_E_INVALIDOPERATION; + if (View == NULL) return E_FAIL; - *ppv = [renderTarget createSurfaceRenderTarget]; - return static_cast(*ppv == nil ? E_FAIL : S_OK); + + auto target = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: nil]; + *ppv = [target createSoftwareRenderTarget]; + [View setRenderTarget: target]; + return S_OK; +} + +HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlContext* glContext, IAvnGlSurfaceRenderTarget **ppv) { + START_COM_CALL; + + if(![NSThread isMainThread]) + return COR_E_INVALIDOPERATION; + + if (View == NULL) + return E_FAIL; + + auto target = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: glContext]; + *ppv = [target createSurfaceRenderTarget]; + [View setRenderTarget: target]; + return S_OK; +} + +HRESULT WindowBaseImpl::CreateMetalRenderTarget(IAvnMetalDevice* device, IAvnMetalRenderTarget **ppv) { + START_COM_CALL; + + if(![NSThread isMainThread]) + return COR_E_INVALIDOPERATION; + + if (View == NULL) + return E_FAIL; + + auto target = [[MetalRenderTarget alloc] initWithDevice: device]; + [View setRenderTarget: target]; + [target getRenderTarget: ppv]; + return S_OK; } HRESULT WindowBaseImpl::CreateNativeControlHost(IAvnNativeControlHost **retOut) { @@ -615,11 +643,11 @@ HRESULT WindowBaseImpl::GetInputMethod(IAvnTextInputMethod **retOut) { return S_OK; } -extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl) +extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events) { @autoreleasepool { - IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl); + IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events); return ptr; } } diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 5140124a17..049ef755ff 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -39,7 +39,7 @@ BEGIN_INTERFACE_MAP() ComPtr WindowEvents; - WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl); + WindowImpl(IAvnWindowEvents* events); virtual HRESULT Show (bool activate, bool isDialog) override; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 925880534a..be761e0af7 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -9,7 +9,7 @@ #include "automation.h" #include "WindowProtocol.h" -WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) { +WindowImpl::WindowImpl(IAvnWindowEvents *events) : WindowBaseImpl(events) { _isEnabled = true; _children = std::list(); _isClientAreaExtended = false; diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 4353737dc8..5cf1b94a2f 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -6,11 +6,13 @@ #import #import #include +#include "noarc.h" extern IAvnPlatformThreadingInterface* CreatePlatformThreading(); extern void FreeAvnGCHandle(void* handle); -extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl); -extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl); +extern void PostDispatcherCallback(IAvnActionCallback* cb); +extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events); +extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events); extern IAvnSystemDialogs* CreateSystemDialogs(); extern IAvnScreens* CreateScreens(); extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*); @@ -21,6 +23,7 @@ extern NSString* GetAvnCustomDataType(); extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop); extern IAvnCursorFactory* CreateCursorFactory(); extern IAvnGlDisplay* GetGlDisplay(); +extern IAvnMetalDisplay* GetMetalDisplay(); extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events); extern IAvnTrayIcon* CreateTrayIcon(); extern IAvnMenuItem* CreateAppMenuItem(); @@ -56,6 +59,27 @@ template inline T* objc_cast(id from) { return nil; } +template class ObjCWrapper { +public: + T* Value; + ObjCWrapper(T* value) + { + Value = value; + } + operator T*() const + { + return Value; + } + T* operator->() const + { + return Value; + } + ~ObjCWrapper() + { + Value = nil; + } +}; + @interface ActionCallback : NSObject - (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback; - (void) action; @@ -79,5 +103,8 @@ public: virtual HRESULT ShowAll() override; virtual HRESULT HideOthers() override; }; +#define NSApp [NSApplication sharedApplication] + +#define START_COM_ARP_CALL START_ARP_CALL; START_COM_CALL #endif diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 4bfda4b531..1c7e2cf25a 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -191,16 +191,20 @@ public: @end static ComPtr _deallocator; +static ComPtr _dispatcher; class AvaloniaNative : public ComSingleObject { public: FORWARD_IUNKNOWN() - virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* events) override + virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, + IAvnApplicationEvents* events, + IAvnDispatcher* dispatcher) override { START_COM_CALL; _deallocator = deallocator; + _dispatcher = dispatcher; @autoreleasepool{ [[ThreadingInitializer new] do]; } @@ -213,7 +217,7 @@ public: return (IAvnMacOptions*)new MacOptions(); } - virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override + virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv) override { START_COM_CALL; @@ -221,12 +225,12 @@ public: { if(cb == nullptr || ppv == nullptr) return E_POINTER; - *ppv = CreateAvnWindow(cb, gl); + *ppv = CreateAvnWindow(cb); return S_OK; } }; - virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override + virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnPopup** ppv) override { START_COM_CALL; @@ -235,7 +239,7 @@ public: if(cb == nullptr || ppv == nullptr) return E_POINTER; - *ppv = CreateAvnPopup(cb, gl); + *ppv = CreateAvnPopup(cb); return S_OK; } } @@ -320,7 +324,22 @@ public: return S_OK; } } - + + virtual HRESULT ObtainMetalDisplay(IAvnMetalDisplay** ppv) override + { + START_COM_CALL; + @autoreleasepool + { + auto rv = ::GetMetalDisplay(); + if(rv == NULL) + return E_FAIL; + rv->AddRef(); + *ppv = rv; + return S_OK; + } + } + + virtual HRESULT CreateTrayIcon (IAvnTrayIcon** ppv) override { START_COM_CALL; @@ -432,6 +451,11 @@ extern void FreeAvnGCHandle(void* handle) _deallocator->FreeGCHandle(handle); } +extern void PostDispatcherCallback(IAvnActionCallback* cb) +{ + _dispatcher->Post(cb); +} + NSSize ToNSSize (AvnSize s) { NSSize result; diff --git a/native/Avalonia.Native/src/OSX/metal.mm b/native/Avalonia.Native/src/OSX/metal.mm new file mode 100644 index 0000000000..8a66592ec4 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/metal.mm @@ -0,0 +1,161 @@ +#import +#import +#import +#include "common.h" +#include "rendertarget.h" + +class AvnMetalDevice : public ComSingleObject +{ +public: + id device; + id queue; + FORWARD_IUNKNOWN() + + void *GetDevice() override { + return (__bridge void*) device; + } + + void *GetQueue() override { + return (__bridge void*) queue; + } + + AvnMetalDevice(id device, id queue) : device(device), queue(queue) { + } + +}; + + +class AvnMetalRenderSession : public ComSingleObject +{ + id _drawable; + id _queue; + id _texture; + CAMetalLayer* _layer; + AvnPixelSize _size; + double _scaling; +public: + FORWARD_IUNKNOWN() + + AvnMetalRenderSession(AvnMetalDevice* device, CAMetalLayer* layer, id drawable, const AvnPixelSize &size, double scaling) + : _drawable(drawable), _size(size), _scaling(scaling), _queue(device->queue), + _texture([drawable texture]) { + _layer = layer; + } + + HRESULT GetPixelSize(AvnPixelSize *ret) override { + *ret = _size; + return 0; + } + + double GetScaling() override { + return _scaling; + } + + void *GetTexture() override { + return (__bridge void*) _texture; + } + + ~AvnMetalRenderSession() + { + auto buffer = [_queue commandBuffer]; + [buffer presentDrawable: _drawable]; + [buffer commit]; + } +}; + +class AvnMetalRenderTarget : public ComSingleObject +{ + CAMetalLayer* _layer; + double _scaling = 1; + AvnPixelSize _size = {1,1}; + ComPtr _device; +public: + double PendingScaling = 1; + AvnPixelSize PendingSize = {1,1}; + FORWARD_IUNKNOWN() + AvnMetalRenderTarget(CAMetalLayer* layer, ComPtr device) + { + _layer = layer; + _device = device; + } + + HRESULT BeginDrawing(IAvnMetalRenderingSession **ret) override { + if([NSThread isMainThread]) + { + // Flush all existing rendering + auto buffer = [_device->queue commandBuffer]; + [buffer commit]; + [buffer waitUntilCompleted]; + _size = PendingSize; + _scaling= PendingScaling; + CGSize layerSize = {(CGFloat)_size.Width, (CGFloat)_size.Height}; + + [_layer setDrawableSize: layerSize]; + } + auto drawable = [_layer nextDrawable]; + if(drawable == nil) + { + ret = nil; + return E_FAIL; + } + *ret = new AvnMetalRenderSession(_device, _layer, drawable, _size, _scaling); + return 0; + } +}; + +@implementation MetalRenderTarget +{ + ComPtr _device; + CAMetalLayer* _layer; + ComPtr _target; +} +- (MetalRenderTarget *)initWithDevice:(IAvnMetalDevice *)device { + _device = dynamic_cast(device); + _layer = [CAMetalLayer new]; + _layer.device = _device->device; + _target.setNoAddRef(new AvnMetalRenderTarget(_layer, _device)); + return self; +} + + +-(void) getRenderTarget: (IAvnMetalRenderTarget**) ppv +{ + *ppv = static_cast(_target.getRetainedReference()); +} + +- (void)resize:(AvnPixelSize)size withScale:(float)scale { + CGSize layerSize = {(CGFloat)size.Width, (CGFloat)size.Height}; + _target->PendingScaling = scale; + _target->PendingSize = size; + [_layer setNeedsDisplay]; +} + +- (CALayer *)layer { + return _layer; +} +@end + + +class AvnMetalDisplay : public ComSingleObject +{ +public: + FORWARD_IUNKNOWN() + HRESULT CreateDevice(IAvnMetalDevice **ret) override { + + auto device = MTLCreateSystemDefaultDevice(); + if(device == nil) { + ret = nil; + return E_FAIL; + } + auto queue = [device newCommandQueue]; + *ret = new AvnMetalDevice(device, queue); + return S_OK; + } +}; + +static AvnMetalDisplay* _display = new AvnMetalDisplay(); + +extern IAvnMetalDisplay* GetMetalDisplay() +{ + return _display; +} \ No newline at end of file diff --git a/native/Avalonia.Native/src/OSX/noarc.mm b/native/Avalonia.Native/src/OSX/noarc.mm new file mode 100644 index 0000000000..82378ce84c --- /dev/null +++ b/native/Avalonia.Native/src/OSX/noarc.mm @@ -0,0 +1,11 @@ +#include "noarc.h" + +CppAutoreleasePool::CppAutoreleasePool() +{ + _pool = [[NSAutoreleasePool alloc] init]; +} + +CppAutoreleasePool::~CppAutoreleasePool() { + auto ptr = (NSAutoreleasePool*)_pool; + [ptr release]; +} diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index 1c22c91207..b44a605803 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -5,15 +5,12 @@ #include #include +#include -@interface IOSurfaceHolder : NSObject -@end - -@implementation IOSurfaceHolder +@implementation IOSurfaceHolder : NSObject { @public IOSurfaceRef surface; @public AvnPixelSize size; - @public bool hasContent; @public float scale; ComPtr _context; GLuint _framebuffer, _texture, _renderbuffer; @@ -42,7 +39,6 @@ self->scale = scale; self->size = size; self->_context = context; - self->hasContent = false; return self; } @@ -76,7 +72,7 @@ } glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, _texture, 0); } - + if(_renderbuffer == 0) { glGenRenderbuffers(1, &_renderbuffer); @@ -84,48 +80,72 @@ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderbuffer); } - + return S_OK; } -(void) finishDraw { - ComPtr release; - _context->MakeCurrent(release.getPPV()); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + if(_framebuffer != 0) { + glDeleteFramebuffers(1, &_framebuffer); + _framebuffer = 0; + } + if(_texture != 0) { + glDeleteTextures(1, &_texture); + _texture = 0; + } + if(_renderbuffer != 0) { + glDeleteRenderbuffers(1, &_renderbuffer); + _renderbuffer = 0; + } glFlush(); - self->hasContent = true; } -(void) dealloc { - - if(_framebuffer != 0) - { - ComPtr release; - _context->MakeCurrent(release.getPPV()); - glDeleteFramebuffers(1, &_framebuffer); - if(_texture != 0) - glDeleteTextures(1, &_texture); - if(_renderbuffer != 0) - glDeleteRenderbuffers(1, &_renderbuffer); - } - if(surface != nullptr) { CFRelease(surface); + surface = nil; } } @end + static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target); +static IAvnSoftwareRenderTarget* CreateSoftwareRenderTarget(IOSurfaceRenderTarget* target); + +static bool SizeEquals(AvnPixelSize& left, AvnPixelSize& right) +{ + return left.Width == right.Width && right.Height == left.Height; +} + +class ConsumeSurfacesCallback : public ComSingleObject +{ + IOSurfaceRenderTarget* _target; +public: + FORWARD_IUNKNOWN() + ConsumeSurfacesCallback(IOSurfaceRenderTarget* target) + { + _target = target; + } + + void Run() override { + [_target consumeSurfaces]; + } +}; @implementation IOSurfaceRenderTarget { CALayer* _layer; - @public IOSurfaceHolder* surface; @public NSObject* lock; ComPtr _glContext; + bool _consumeSurfacesScheduled; + std::queue> _surfaces; + IOSurfaceHolder* _activeSurface; + AvnPixelSize _size; + float _scale; } - (IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context; @@ -133,16 +153,12 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta self = [super init]; _glContext = context; lock = [NSObject new]; - surface = nil; + _layer = [CALayer new]; [self resize:{1,1} withScale: 1]; return self; } -- (AvnPixelSize) pixelSize { - return {1, 1}; -} - - (CALayer *)layer { return _layer; } @@ -155,57 +171,110 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta size.Width = 1; @synchronized (lock) { - if(surface == nil - || surface->size.Width != size.Width - || surface->size.Height != size.Height - || surface->scale != scale) - { - surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()]; - - [self updateLayer]; - } + _size = size; + _scale = scale; } } -- (void)updateLayer { - if ([NSThread isMainThread]) +- (void)setSurfaceInUiThreadContext: (IOSurfaceHolder*) surface +{ + [CATransaction begin]; + if(surface == _activeSurface) + [_layer setContents: nil]; + _activeSurface = surface; + [_layer setContentsScale: _activeSurface->scale]; + [_layer setContents: (__bridge IOSurface*) _activeSurface->surface]; + [CATransaction commit]; +} + +- (void)consumeSurfaces { + @synchronized (lock) { + _consumeSurfacesScheduled = false; + + while(_surfaces.size() > 1) + _surfaces.pop(); + + if(_surfaces.size() == 0) + return; + + auto targetSurface = _surfaces.front(); + _surfaces.pop(); + + [self setSurfaceInUiThreadContext: targetSurface]; + } + // This can trigger event processing on the main thread + // which might need to lock the renderer + // which can cause a deadlock. So flush call is outside of the lock + [CATransaction flush]; + +} + +- (IOSurfaceHolder*) getNextSurfaceInSafeContext +{ + IOSurfaceHolder* targetSurface = nil; + if([NSThread isMainThread]) { - @synchronized (lock) { - if(_layer == nil) - return; - if(!surface->hasContent) - return; - [CATransaction begin]; - [_layer setContents: nil]; - if(surface != nil) + // Drain the surface queue and try to find a surface usable for rendering + while(_surfaces.size() > 0) + { + auto front = _surfaces.front(); + _surfaces.pop(); + if(targetSurface == nil && SizeEquals(front.Value->size, _size)) { - [_layer setContentsScale: surface->scale]; - [_layer setContents: (__bridge IOSurface*) surface->surface]; + targetSurface = front; } - [CATransaction commit]; } - // This can trigger event processing on the main thread - // which might need to lock the renderer - // which can cause a deadlock. So flush call is outside of the lock - [CATransaction flush]; + if(targetSurface == nil && _activeSurface != nil && SizeEquals(_activeSurface->size, _size)) + targetSurface = _activeSurface; + } + else + { + // Try to reuse an outdated surface that is still not picked up by the UI thread + while(_surfaces.size() > 1) + { + auto front = _surfaces.front(); + _surfaces.pop(); + + // Simply discard the surface on size mismatch + if(SizeEquals(front.Value->size, _size)) + targetSurface = front; + } } + + if(targetSurface == nil) + targetSurface = [[IOSurfaceHolder alloc] initWithSize: _size withScale: _scale withOpenGlContext: _glContext]; + return targetSurface; +} + +- (void) presentSurfaceInSafeContext: (IOSurfaceHolder*) surface +{ + if([NSThread isMainThread]) + [self setSurfaceInUiThreadContext: surface]; else - dispatch_async(dispatch_get_main_queue(), ^{ - [self updateLayer]; - }); + { + _surfaces.push(surface); + if(_consumeSurfacesScheduled) + return; + _consumeSurfacesScheduled = true; + __block auto strongSelf = self; + ComPtr cb(new ConsumeSurfacesCallback(self), true); + PostDispatcherCallback(cb); + } } -- (void) setNewLayer:(CALayer *)layer { - _layer = layer; - [self updateLayer]; +- (void) presentSurface: (IOSurfaceHolder*) surface +{ + @synchronized(lock) + { + [self presentSurfaceInSafeContext: surface]; + } } - (HRESULT)setSwFrame:(AvnFramebuffer *)fb { @synchronized (lock) { if(fb->PixelFormat == AvnPixelFormat::kAvnRgb565) return E_INVALIDARG; - if(surface == nil) - return E_FAIL; + auto surface = [self getNextSurfaceInSafeContext]; IOSurfaceRef surf = surface->surface; if(IOSurfaceLock(surf, 0, nil)) return E_FAIL; @@ -221,8 +290,7 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta memcpy(pSurface + y*sstride, pFb + y*fstride, wbytes); } IOSurfaceUnlock(surf, 0, nil); - surface->hasContent = true; - [self updateLayer]; + [self presentSurfaceInSafeContext: surface]; return S_OK; } } @@ -232,6 +300,11 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta return CreateGlRenderTarget(self); } +-(IAvnSoftwareRenderTarget*) createSoftwareRenderTarget +{ + return CreateSoftwareRenderTarget(self); +} + @end class AvnGlRenderingSession : public ComSingleObject @@ -241,12 +314,12 @@ class AvnGlRenderingSession : public ComSingleObject releaseContext) + AvnGlRenderingSession(IOSurfaceRenderTarget* target, IOSurfaceHolder* surface, ComPtr releaseContext) { _target = target; // This happens in a synchronized block set up by AvnRenderTarget, so we take the current surface for this // particular render session - _surface = _target->surface; + _surface = surface; _releaseContext = releaseContext; } @@ -272,8 +345,9 @@ public: virtual ~AvnGlRenderingSession() { + START_ARP_CALL; [_surface finishDraw]; - [_target updateLayer]; + [_target presentSurface: _surface]; _releaseContext = nil; } }; @@ -290,17 +364,15 @@ public: virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override { - START_COM_CALL; - + START_COM_ARP_CALL; ComPtr releaseContext; @synchronized (_target->lock) { - if(_target->surface == nil) - return E_FAIL; + auto surface = [_target getNextSurfaceInSafeContext]; _target->_glContext->MakeCurrent(releaseContext.getPPV()); - HRESULT res = [_target->surface prepareForGlRender]; + HRESULT res = [surface prepareForGlRender]; if(res) return res; - *ret = new AvnGlRenderingSession(_target, releaseContext); + *ret = new AvnGlRenderingSession(_target, surface, releaseContext); return S_OK; } } @@ -310,4 +382,27 @@ public: static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target) { return new AvnGlRenderTarget(target); -} +}; + + +class AvnSoftwareRenderTarget : public ComSingleObject +{ + IOSurfaceRenderTarget* _target; +public: + FORWARD_IUNKNOWN() + + AvnSoftwareRenderTarget(IOSurfaceRenderTarget *target) { + _target = target; + } + + HRESULT SetFrame(AvnFramebuffer *fb) override { + START_COM_ARP_CALL; + [_target setSwFrame: fb]; + return 0; + } +}; + +static IAvnSoftwareRenderTarget* CreateSoftwareRenderTarget(IOSurfaceRenderTarget* target) +{ + return new AvnSoftwareRenderTarget(target); +}; diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs index 56a4eb22d4..365c9cdb42 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs @@ -13,5 +13,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform } public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface, _topLevel.RenderScaling); + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); } } diff --git a/src/Avalonia.Base/RenderTargetNotReadyException.cs b/src/Avalonia.Base/RenderTargetNotReadyException.cs new file mode 100644 index 0000000000..e34dd41709 --- /dev/null +++ b/src/Avalonia.Base/RenderTargetNotReadyException.cs @@ -0,0 +1,25 @@ +using System; + +namespace Avalonia; + +public class RenderTargetNotReadyException : Exception +{ + public RenderTargetNotReadyException() + { + } + + public RenderTargetNotReadyException(string message) + : base(message) + { + } + + public RenderTargetNotReadyException(Exception innerException) + : base(null, innerException) + { + } + + public RenderTargetNotReadyException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 8f1aa1cb49..3883e4e574 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -135,7 +135,18 @@ namespace Avalonia.Rendering.Composition.Server _redrawRequested = true; } - _renderTarget ??= _compositor.CreateRenderTarget(_surfaces()); + try + { + _renderTarget ??= _compositor.CreateRenderTarget(_surfaces()); + } + catch (RenderTargetNotReadyException) + { + return; + } + catch (RenderTargetCorruptedException) + { + return; + } if ((_dirtyRect.Width == 0 && _dirtyRect.Height == 0) && !_redrawRequested) return; diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs index 0a7daeaa24..22ee322e36 100644 --- a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs +++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs @@ -1,10 +1,17 @@ -using Avalonia.Metadata; +using System; +using Avalonia.Metadata; using Avalonia.Platform; namespace Avalonia.Controls.Platform.Surfaces { [Unstable] public interface IFramebufferPlatformSurface + { + IFramebufferRenderTarget CreateFramebufferRenderTarget(); + } + + [Unstable] + public interface IFramebufferRenderTarget : IDisposable { /// /// Provides a framebuffer descriptor for drawing. @@ -14,4 +21,24 @@ namespace Avalonia.Controls.Platform.Surfaces /// 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/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 74f12280bb..ac7e8ec179 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -282,9 +282,6 @@ namespace Avalonia.Controls.Remote.Server } } - public ILockedFramebuffer Lock() - => GetOrCreateFramebuffer().Lock(_sendLastFrameIfNeeded); - private void SendLastFrameIfNeeded() { if (IsDisposed) @@ -328,5 +325,8 @@ namespace Avalonia.Controls.Remote.Server public override IMouseDevice MouseDevice { get; } = new MouseDevice(); public IKeyboardDevice KeyboardDevice { get; } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => + new FuncFramebufferRenderTarget(() => GetOrCreateFramebuffer().Lock(_sendLastFrameIfNeeded)); } } diff --git a/src/Avalonia.Metal/Avalonia.Metal.csproj b/src/Avalonia.Metal/Avalonia.Metal.csproj new file mode 100644 index 0000000000..6b17caecbb --- /dev/null +++ b/src/Avalonia.Metal/Avalonia.Metal.csproj @@ -0,0 +1,15 @@ + + + net6.0;netstandard2.0 + true + + + + + + + + + + + diff --git a/src/Avalonia.Metal/IMetalDevice.cs b/src/Avalonia.Metal/IMetalDevice.cs new file mode 100644 index 0000000000..0910e6aa17 --- /dev/null +++ b/src/Avalonia.Metal/IMetalDevice.cs @@ -0,0 +1,34 @@ +using System; +using Avalonia.Metadata; +using Avalonia.Platform; + +namespace Avalonia.Metal; + + +[PrivateApi] +public interface IMetalDevice : IPlatformGraphicsContext +{ + IntPtr Device { get; } + IntPtr CommandQueue { get; } +} + +[PrivateApi] +public interface IMetalPlatformSurface +{ + IMetalPlatformSurfaceRenderTarget CreateMetalRenderTarget(IMetalDevice device); +} + +[PrivateApi] +public interface IMetalPlatformSurfaceRenderTarget : IDisposable +{ + IMetalPlatformSurfaceRenderingSession BeginRendering(); +} + +[PrivateApi] +public interface IMetalPlatformSurfaceRenderingSession : IDisposable +{ + IntPtr Texture { get; } + PixelSize Size { get; } + double Scaling { get; } + bool IsYFlipped { get; } +} diff --git a/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs index 8e40960af6..16280a90cd 100644 --- a/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs +++ b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs @@ -192,10 +192,13 @@ namespace Avalonia.Native { _window = window; } + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { + if (!Dispatcher.UIThread.CheckAccess()) + throw new RenderTargetNotReadyException(); var avnContext = (GlContext)context; - return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), avnContext); + return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(avnContext.Context), avnContext); } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 5d5e17839f..7da15811db 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -19,7 +19,7 @@ namespace Avalonia.Native { private readonly IAvaloniaNativeFactory _factory; private AvaloniaNativePlatformOptions? _options; - private AvaloniaNativeGlPlatformGraphics? _platformGl; + private IPlatformGraphics? _platformGraphics; [DllImport("libAvaloniaNative")] static extern IntPtr CreateAvaloniaNative(); @@ -93,7 +93,7 @@ namespace Avalonia.Native 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) { @@ -126,20 +126,38 @@ namespace Avalonia.Native if (_options.UseGpu) { - try + if (_options.UseMetal) { - _platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(_platformGl); - + try + { + var metal = new MetalPlatformGraphics(_factory); + metal.CreateContext().Dispose(); + _platformGraphics = metal; + } + catch + { + // Ignored + } } - catch (Exception) + + if (_platformGraphics == null) { - // ignored + try + { + _platformGraphics = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); + } + catch (Exception) + { + // ignored + } } + + if(_platformGraphics != null) + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(_platformGraphics); } - - Compositor = new Compositor(_platformGl, true); + + Compositor = new Compositor(_platformGraphics, true); } public ITrayIconImpl CreateTrayIcon() @@ -149,7 +167,7 @@ namespace Avalonia.Native public IWindowImpl CreateWindow() { - return new WindowImpl(_factory, _options, _platformGl); + return new WindowImpl(_factory, _options); } public IWindowImpl CreateEmbeddableWindow() diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 2b989ce733..78b4b0fbf3 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -35,6 +35,8 @@ namespace Avalonia /// public bool UseGpu { get; set; } = true; + public bool UseMetal { get; set; } + /// /// Embeds popups to the window when set to true. The default value is false. /// diff --git a/src/Avalonia.Native/AvnDispatcher.cs b/src/Avalonia.Native/AvnDispatcher.cs new file mode 100644 index 0000000000..b3658d5e67 --- /dev/null +++ b/src/Avalonia.Native/AvnDispatcher.cs @@ -0,0 +1,18 @@ +using Avalonia.Native.Interop; +using Avalonia.Threading; +using MicroCom.Runtime; + +namespace Avalonia.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/Avalonia.Native/DeferredFramebuffer.cs b/src/Avalonia.Native/DeferredFramebuffer.cs index c390459286..0942bee508 100644 --- a/src/Avalonia.Native/DeferredFramebuffer.cs +++ b/src/Avalonia.Native/DeferredFramebuffer.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Native.Interop; using Avalonia.Platform; @@ -7,11 +8,13 @@ namespace Avalonia.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 @@ namespace Avalonia.Native 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/Avalonia.Native/Metal.cs b/src/Avalonia.Native/Metal.cs new file mode 100644 index 0000000000..8c8d293576 --- /dev/null +++ b/src/Avalonia.Native/Metal.cs @@ -0,0 +1,119 @@ +using System; +using Avalonia.Metal; +using Avalonia.Native.Interop; +using Avalonia.Platform; +using Avalonia.Threading; +using Avalonia.Utilities; + +namespace Avalonia.Native; + +class MetalPlatformGraphics : IPlatformGraphics +{ + private readonly IAvnMetalDisplay _display; + + public MetalPlatformGraphics(IAvaloniaNativeFactory factory) + { + _display = factory.ObtainMetalDisplay(); + } + public bool UsesSharedContext => false; + public IPlatformGraphicsContext CreateContext() => new MetalDevice(_display.CreateDevice()); + + public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException(); +} + +class MetalDevice : IMetalDevice +{ + public IAvnMetalDevice Native { get; private set; } + private DisposableLock _syncRoot = new(); + + + public MetalDevice(IAvnMetalDevice native) + { + Native = native; + } + + public void Dispose() + { + Native?.Dispose(); + Native = null; + } + + public object TryGetFeature(Type featureType) => null; + + public bool IsLost => false; + + public IDisposable EnsureCurrent() => _syncRoot.Lock(); + + public IntPtr Device => Native.Device; + public IntPtr CommandQueue => Native.Queue; +} + +class MetalPlatformSurface : IMetalPlatformSurface +{ + private readonly IAvnWindowBase _window; + + public MetalPlatformSurface(IAvnWindowBase window) + { + _window = window; + } + public IMetalPlatformSurfaceRenderTarget CreateMetalRenderTarget(IMetalDevice device) + { + if (!Dispatcher.UIThread.CheckAccess()) + throw new RenderTargetNotReadyException(); + + var dev = (MetalDevice)device; + var target = _window.CreateMetalRenderTarget(dev.Native); + return new MetalRenderTarget(target); + } +} + +internal class MetalRenderTarget : IMetalPlatformSurfaceRenderTarget +{ + private IAvnMetalRenderTarget _native; + + public MetalRenderTarget(IAvnMetalRenderTarget native) + { + _native = native; + } + + public void Dispose() + { + _native?.Dispose(); + _native = null; + } + + public IMetalPlatformSurfaceRenderingSession BeginRendering() + { + var session = _native.BeginDrawing(); + return new MetalDrawingSession(session); + } +} + +internal class MetalDrawingSession : IMetalPlatformSurfaceRenderingSession +{ + private IAvnMetalRenderingSession _session; + + public MetalDrawingSession(IAvnMetalRenderingSession session) + { + _session = session; + } + + public void Dispose() + { + _session?.Dispose(); + _session = null; + } + + public IntPtr Texture => _session.Texture; + public PixelSize Size + { + get + { + var size = _session.PixelSize; + return new(size.Width, size.Height); + } + } + + public double Scaling => _session.Scaling; + public bool IsYFlipped => false; +} diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 6b7f7e8883..2dad083767 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -8,21 +8,15 @@ namespace Avalonia.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, glFeature) + IWindowBaseImpl parent) : base(factory) { - _opts = opts; - _glFeature = glFeature; _parent = parent; using (var e = new PopupEvents(this)) { - Init(factory.CreatePopup(e, _glFeature.SharedContext.Context), factory.CreateScreens()); + Init(factory.CreatePopup(e), factory.CreateScreens()); } PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } @@ -68,7 +62,7 @@ namespace Avalonia.Native base.Show(false, isDialog); } - public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); + public override IPopupImpl CreatePopup() => new PopupImpl(_factory, this); public void SetWindowManagerAddShadowHint(bool enabled) { diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 817fe3d080..f1b7f6876c 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Native internal 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 @@ namespace Avalonia.Native private readonly AvaloniaNativeTextInputMethod _inputMethod; private bool _canResize = true; - internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature) + 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, glFeature.SharedContext.Context), factory.CreateScreens()); + Init(_native = factory.CreateWindow(e), factory.CreateScreens()); } _nativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory); @@ -215,7 +213,7 @@ namespace Avalonia.Native 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/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 760816643e..3b4e457ba5 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -54,24 +54,20 @@ namespace Avalonia.Native 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 = AvaloniaLocator.Current.GetService(); _mouse = new MouseDevice(); @@ -82,9 +78,8 @@ namespace Avalonia.Native { _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,29 +128,55 @@ namespace Avalonia.Native } } - 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() + { + lock (_parent._syncRoot) { - if (_native == null) - return false; - cb(_native); - _lastRenderedLogicalSize = _savedLogicalSize; - return true; + _target?.Dispose(); + _target = null; } - }, (int)w, (int)h, new Vector(dpi, dpi)); + } + + 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/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index a58a00d59d..bc372bbcb5 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -485,10 +485,11 @@ enum AvnPlatformThemeVariant [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] interface IAvaloniaNativeFactory : IUnknown { - HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* appCb); + HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* appCb, + IAvnDispatcher* dispatcher); IAvnMacOptions* GetMacOptions(); - HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv); - HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv); + HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv); + HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnPopup** ppv); HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv); HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv); HRESULT CreateScreens(IAvnScreens** ppv); @@ -496,6 +497,7 @@ interface IAvaloniaNativeFactory : IUnknown HRESULT CreateDndClipboard(IAvnClipboard** ppv); HRESULT CreateCursorFactory(IAvnCursorFactory** ppv); HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv); + HRESULT ObtainMetalDisplay(IAvnMetalDisplay** ppv); HRESULT SetAppMenu(IAvnMenu* menu); HRESULT SetServicesMenu(IAvnMenu* menu); HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv); @@ -533,10 +535,11 @@ interface IAvnWindowBase : IUnknown HRESULT SetPosition(AvnPoint point); HRESULT PointToClient(AvnPoint point, AvnPoint*ret); HRESULT PointToScreen(AvnPoint point, AvnPoint*ret); - HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose); HRESULT SetTopMost(bool value); HRESULT SetCursor(IAvnCursor* cursor); - HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret); + HRESULT CreateGlRenderTarget(IAvnGlContext* context, IAvnGlSurfaceRenderTarget** ret); + HRESULT CreateSoftwareRenderTarget(IAvnSoftwareRenderTarget** ret); + HRESULT CreateMetalRenderTarget(IAvnMetalDevice* device, IAvnMetalRenderTarget** ret); HRESULT SetMainMenu(IAvnMenu* menu); HRESULT ObtainNSWindowHandle([intptr]void** retOut); HRESULT ObtainNSWindowHandleRetained([intptr]void** retOut); @@ -734,6 +737,13 @@ interface IAvnCursorFactory : IUnknown HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut); } +[uuid(931062d2-5bc8-4062-8588-83dd8deb99c2)] +interface IAvnSoftwareRenderTarget : IUnknown +{ + HRESULT SetFrame(AvnFramebuffer* fb); +} + + [uuid(60452465-8616-40af-bc00-042e69828ce7)] interface IAvnGlDisplay : IUnknown { @@ -766,6 +776,33 @@ interface IAvnGlSurfaceRenderingSession : IUnknown HRESULT GetScaling(double* ret); } +[uuid(da291767-4db3-4598-893d-09ecaa23893f)] +interface IAvnMetalDisplay : IUnknown +{ + HRESULT CreateDevice(IAvnMetalDevice** ret); +} + +[uuid(969fa914-b74a-4c9f-8725-5160dc63579e)] +interface IAvnMetalDevice : IUnknown +{ + [intptr]void* GetDevice(); + [intptr]void* GetQueue(); +} + +[uuid(f1306b71-eca0-426e-8700-105192693b1a)] +interface IAvnMetalRenderTarget : IUnknown +{ + HRESULT BeginDrawing(IAvnMetalRenderingSession** ret); +} + +[uuid(e625b406-f04c-484e-946a-4abd2c6015ad)] +interface IAvnMetalRenderingSession : IUnknown +{ + HRESULT GetPixelSize(AvnPixelSize* ret); + double GetScaling(); + [intptr]void* GetTexture(); +} + [uuid(60992d19-38f0-4141-a0a9-76ac303801f3)] interface IAvnTrayIcon : IUnknown { @@ -828,6 +865,12 @@ interface IAvnGCHandleDeallocatorCallback : IUnknown void FreeGCHandle([intptr]void* handle); } +[uuid(96688589-5dc7-41ec-9ce3-d481942454ee)] +interface IAvnDispatcher : IUnknown +{ + void Post(IAvnActionCallback* cb); +} + [uuid(91c7f677-f26b-4ff3-93cc-cf15aa966ffa)] interface IAvnNativeControlHost : IUnknown { diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 9818d3f86a..4060869b19 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -139,6 +139,8 @@ namespace Avalonia.X11 _pixelSize, _pixelSize.Width * 4, new Vector(96, 96), PixelFormat.Bgra8888, null); } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); } } diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs index 05885d1a25..6daa040593 100644 --- a/src/Avalonia.X11/X11FramebufferSurface.cs +++ b/src/Avalonia.X11/X11FramebufferSurface.cs @@ -27,5 +27,7 @@ namespace Avalonia.X11 XUnlockDisplay(_display); return new X11Framebuffer(_display, _xid, _depth, width, height, _scaling()); } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); } } diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index bede875e89..cb09802d76 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -86,5 +86,7 @@ namespace Avalonia.X11 new Vector(96, 96), PixelFormat.Bgra8888, () => h.Free()); } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); } } diff --git a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs index 76b07a6827..777d44ea66 100644 --- a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaRasterSurface.cs @@ -84,5 +84,7 @@ namespace Avalonia.Browser.Skia Marshal.FreeHGlobal(Address); } } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); } } diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index c7eb07ba10..964934d6cf 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -218,6 +218,8 @@ namespace Avalonia.Headless }); } + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); + public WriteableBitmap? GetLastRenderedFrame() { lock (_sync) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs index 56c6bdb8c4..d85485e7b3 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs @@ -152,6 +152,8 @@ namespace Avalonia.LinuxFramebuffer new FbDevBackBuffer(_fd, _fixedInfo, _varInfo, _mappedAddress)) .Lock(new Vector(96, 96) * Scaling); } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); private void ReleaseUnmanagedResources() diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 64afaa6817..2cdc9a2d82 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -12,12 +12,12 @@ namespace Avalonia.Skia /// internal class FramebufferRenderTarget : IRenderTarget { - private readonly IFramebufferPlatformSurface _platformSurface; private SKImageInfo _currentImageInfo; private IntPtr _currentFramebufferAddress; private SKSurface? _framebufferSurface; private PixelFormatConversionShim? _conversionShim; private IDisposable? _preFramebufferCopyHandler; + private IFramebufferRenderTarget? _renderTarget; /// /// Create new framebuffer render target using a target surface. @@ -25,19 +25,24 @@ namespace Avalonia.Skia /// Target surface. public FramebufferRenderTarget(IFramebufferPlatformSurface platformSurface) { - _platformSurface = platformSurface; + _renderTarget = platformSurface.CreateFramebufferRenderTarget(); } /// public void Dispose() { + _renderTarget?.Dispose(); + _renderTarget = null; FreeSurface(); } /// public IDrawingContextImpl CreateDrawingContext() { - var framebuffer = _platformSurface.Lock(); + if (_renderTarget == null) + throw new ObjectDisposedException(nameof(FramebufferRenderTarget)); + + var framebuffer = _renderTarget.Lock(); var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, framebuffer.Format.ToSkColorType(), framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); diff --git a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalApi.cs b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalApi.cs new file mode 100644 index 0000000000..9475d96fc0 --- /dev/null +++ b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalApi.cs @@ -0,0 +1,80 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.InteropServices; +using Avalonia.Platform.Interop; +using SkiaSharp; +using BindingFlags = System.Reflection.BindingFlags; + +namespace Avalonia.Skia.Metal; + +internal unsafe class SkiaMetalApi +{ + delegate* unmanaged[Stdcall] _gr_direct_context_make_metal_with_options; + private delegate* unmanaged[Stdcall] + _gr_backendrendertarget_new_metal; + private readonly ConstructorInfo _contextCtor; + private readonly MethodInfo _contextOptionsToNative; + private readonly ConstructorInfo _renderTargetCtor; + + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(GRContext))] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, typeof(GRBackendRenderTarget))] + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, typeof(GRContextOptions))] + public SkiaMetalApi() + { + // Make sure that skia is loaded + GC.KeepAlive(new SKPaint()); + + var loader = AvaloniaLocator.Current.GetRequiredService(); +#if NET6_0_OR_GREATER + var dll = NativeLibrary.Load("libSkiaSharp", typeof(SKPaint).Assembly, null); +#else + var dll = loader.LoadLibrary("libSkiaSharp"); +#endif + _gr_direct_context_make_metal_with_options = (delegate* unmanaged[Stdcall] ) + loader.GetProcAddress(dll, "gr_direct_context_make_metal_with_options", false); + _gr_backendrendertarget_new_metal = + (delegate* unmanaged[Stdcall]) + loader.GetProcAddress(dll, "gr_backendrendertarget_new_metal", false); + + _contextCtor = typeof(GRContext).GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, + new[] { typeof(IntPtr), typeof(bool) }, null) ?? throw new MissingMemberException("GRContext.ctor(IntPtr,bool)"); + + + _renderTargetCtor = typeof(GRBackendRenderTarget).GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, + new[] { typeof(IntPtr), typeof(bool) }, null) ?? throw new MissingMemberException("GRContext.ctor(IntPtr,bool)"); + + _contextOptionsToNative = typeof(GRContextOptions).GetMethod("ToNative", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + ?? throw new MissingMemberException("GRContextOptions.ToNative()"); + } + + public GRContext CreateContext(IntPtr device, IntPtr queue, GRContextOptions? options) + { + options ??= new(); + var nativeOptions = _contextOptionsToNative.Invoke(options, null)!; + var pOptions = Marshal.AllocHGlobal(Marshal.SizeOf(nativeOptions)); + Marshal.StructureToPtr(nativeOptions, pOptions, false); + var context = _gr_direct_context_make_metal_with_options(device, queue, pOptions); + Marshal.FreeHGlobal(pOptions); + if (context == IntPtr.Zero) + throw new ArgumentException(); + return (GRContext)_contextCtor.Invoke(new object[] { context, true }); + } + + internal struct GRMtlTextureInfoNative + { + public IntPtr Texture; + } + + public GRBackendRenderTarget CreateBackendRenderTarget(int width, int height, int samples, IntPtr texture) + { + var info = new GRMtlTextureInfoNative() { Texture = texture }; + var target = _gr_backendrendertarget_new_metal(width, height, samples, &info); + if (target == IntPtr.Zero) + throw new InvalidOperationException("Unable to create GRBackendRenderTarget"); + return (GRBackendRenderTarget)_renderTargetCtor.Invoke(new object[] { target, true }); + } +} diff --git a/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs new file mode 100644 index 0000000000..8965f4fc7a --- /dev/null +++ b/src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Avalonia.Metal; +using SkiaSharp; + +namespace Avalonia.Skia.Metal; + +internal class SkiaMetalGpu : ISkiaGpu +{ + private SkiaMetalApi _api = new(); + private GRContext? _context; + private readonly IMetalDevice _device; + + public SkiaMetalGpu(IMetalDevice device, long? maxResourceBytes) + { + _context = _api.CreateContext(device.Device, device.CommandQueue, + new GRContextOptions() { AvoidStencilBuffers = true }); + _device = device; + if (maxResourceBytes.HasValue) + _context.SetResourceCacheLimit(maxResourceBytes.Value); + } + + public void Dispose() + { + _context?.Dispose(); + _context = null; + } + + public object? TryGetFeature(Type featureType) => null; + + public bool IsLost => false; + public IDisposable EnsureCurrent() => _device.EnsureCurrent(); + + public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable surfaces) + { + foreach (var surface in surfaces) + { + if (surface is IMetalPlatformSurface metalSurface) + { + var target = metalSurface.CreateMetalRenderTarget(_device); + return new SkiaMetalRenderTarget(this, target); + } + } + + return null; + } + + public class SkiaMetalRenderTarget : ISkiaGpuRenderTarget + { + private readonly SkiaMetalGpu _gpu; + private IMetalPlatformSurfaceRenderTarget? _target; + + public SkiaMetalRenderTarget(SkiaMetalGpu gpu, IMetalPlatformSurfaceRenderTarget target) + { + _gpu = gpu; + _target = target; + } + + public void Dispose() + { + _target?.Dispose(); + _target = null; + } + + public ISkiaGpuRenderSession BeginRenderingSession() + { + var session = (_target ?? throw new ObjectDisposedException(nameof(SkiaMetalRenderTarget))).BeginRendering(); + var backendTarget = _gpu._api.CreateBackendRenderTarget(session.Size.Width, session.Size.Height, + 1, session.Texture); + + var surface = SKSurface.Create(_gpu._context!, backendTarget, + session.IsYFlipped ? GRSurfaceOrigin.BottomLeft : GRSurfaceOrigin.TopLeft, + SKColorType.Bgra8888); + + return new SkiaMetalRenderSession(_gpu, surface, session); + } + + public bool IsCorrupted => false; + } + + internal class SkiaMetalRenderSession : ISkiaGpuRenderSession + { + private readonly SkiaMetalGpu _gpu; + private SKSurface? _surface; + private IMetalPlatformSurfaceRenderingSession? _session; + + public SkiaMetalRenderSession(SkiaMetalGpu gpu, + SKSurface surface, + IMetalPlatformSurfaceRenderingSession session) + { + _gpu = gpu; + _surface = surface; + _session = session; + } + + public void Dispose() + { + _surface?.Canvas.Flush(); + _surface?.Flush(); + _gpu._context?.Flush(); + + _surface?.Dispose(); + _surface = null; + _session?.Dispose(); + _session = null; + } + + public GRContext GrContext => _gpu._context!; + public SKSurface SkSurface => _surface!; + public double ScaleFactor => _session!.Scaling; + + public GRSurfaceOrigin SurfaceOrigin => + _session!.IsYFlipped ? GRSurfaceOrigin.BottomLeft : GRSurfaceOrigin.TopLeft; + } + + + public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession? session) => null; +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index f0a25cc1cb..fe74971432 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -7,6 +7,8 @@ using Avalonia.Platform; using Avalonia.Media.Imaging; using SkiaSharp; using Avalonia.Media.TextFormatting; +using Avalonia.Metal; +using Avalonia.Skia.Metal; namespace Avalonia.Skia { @@ -32,6 +34,8 @@ namespace Avalonia.Skia return new SkiaContext(skiaGpu); if (graphicsContext is IGlContext gl) return new SkiaContext(new GlSkiaGpu(gl, _maxResourceBytes)); + if (graphicsContext is IMetalDevice metal) + return new SkiaContext(new SkiaMetalGpu(metal, _maxResourceBytes)); throw new ArgumentException("Graphics context of type is not supported"); } diff --git a/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs b/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs index e20755b4e2..454e137389 100644 --- a/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs @@ -28,4 +28,6 @@ internal class RenderTargetBitmapImpl : WriteableBitmapImpl, _renderTarget.Dispose(); base.Dispose(); } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); } \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs index 0af326d6a8..1f20b3ed08 100644 --- a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs @@ -11,20 +11,24 @@ namespace Avalonia.Direct2D1 { class FramebufferShimRenderTarget : IRenderTarget { - private readonly IFramebufferPlatformSurface _surface; + private IFramebufferRenderTarget? _target; public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface) { - _surface = surface; + _target = surface.CreateFramebufferRenderTarget(); } public void Dispose() - { + { + _target?.Dispose(); + _target = null; } public IDrawingContextImpl CreateDrawingContext() { - var locked = _surface.Lock(); + if (_target == null) + throw new ObjectDisposedException(nameof(FramebufferShimRenderTarget)); + var locked = _target.Lock(); if (locked.Format == PixelFormat.Rgb565) { locked.Dispose(); diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index cba4879f5b..47cab24aa2 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -60,6 +60,8 @@ namespace Avalonia.Win32 } } } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); public void Dispose() { diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index 6916d0c130..131f5ec18a 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -53,12 +53,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media //no-op } - public ILockedFramebuffer Lock() - { - return this; - } - public void Deallocate() => Marshal.FreeHGlobal(Address); + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(() => this); } diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 3e0ba7d2f6..5c50b3cf11 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -135,7 +135,10 @@ namespace Avalonia.Direct2D1.RenderTests _bitmap = bitmap; } - public ILockedFramebuffer Lock() => _bitmap.Lock(); + public IFramebufferRenderTarget CreateFramebufferRenderTarget() + { + return new FuncFramebufferRenderTarget(() => _bitmap.Lock()); + } } protected void CompareImages([CallerMemberName] string testName = "", diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index 0a85072af1..53fd610a17 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -161,6 +161,8 @@ public class CompositorTestServices : IDisposable return new LockedFramebuffer(ptr, new PixelSize(1, 1), 4, new Vector(96, 96), PixelFormat.Rgba8888, () => Marshal.FreeHGlobal(ptr)); } + + public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); } public Compositor Compositor => _compositor; From e44c5a869a49b799b1dab78339cd92f5dabc4105 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 30 Jun 2023 16:15:07 +0600 Subject: [PATCH 26/32] Use rendering mode --- src/Avalonia.Native/AvaloniaNativePlatform.cs | 39 +++++++++++-------- .../AvaloniaNativePlatformExtensions.cs | 36 ++++++++++++++--- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 7da15811db..37e5e41faf 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -123,40 +123,45 @@ namespace Avalonia.Native hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); AvaloniaLocator.CurrentMutable.Bind().ToConstant(hotkeys); - - if (_options.UseGpu) + + foreach (var mode in _options.RenderingMode) { - if (_options.UseMetal) + if (mode == AvaloniaNativeRenderingMode.OpenGl) { try { - var metal = new MetalPlatformGraphics(_factory); - metal.CreateContext().Dispose(); - _platformGraphics = metal; + _platformGraphics = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); + break; } - catch + catch (Exception) { - // Ignored + // ignored } } - - if (_platformGraphics == null) +#pragma warning disable CS0618 + else if (mode == AvaloniaNativeRenderingMode.Metal) +#pragma warning restore CS0618 { try { - _platformGraphics = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); + var metal = new MetalPlatformGraphics(_factory); + metal.CreateContext().Dispose(); + _platformGraphics = metal; } - catch (Exception) + catch { - // ignored + // Ignored } } - - if(_platformGraphics != null) - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(_platformGraphics); + else if (mode == AvaloniaNativeRenderingMode.Software) + break; } + if (_platformGraphics != null) + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(_platformGraphics); + + Compositor = new Compositor(_platformGraphics, true); } diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 6193895b67..b670f302ac 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -26,18 +26,44 @@ namespace Avalonia } } + 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; - - public bool UseMetal { 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[] + { + AvaloniaNativeRenderingMode.OpenGl, + AvaloniaNativeRenderingMode.Software + }; + /// /// Embeds popups to the window when set to true. The default value is false. /// From 58f619290bbb86a292c2f3610b3c977bd79a5d12 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Jun 2023 12:16:08 +0100 Subject: [PATCH 27/32] attempt to make build services auto updated. --- packages/Avalonia/Avalonia.csproj | 1 - packages/Avalonia/Avalonia.props | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index b8373cf609..00f0c0577c 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,6 @@ - all diff --git a/packages/Avalonia/Avalonia.props b/packages/Avalonia/Avalonia.props index a2da228887..eaf11d805a 100644 --- a/packages/Avalonia/Avalonia.props +++ b/packages/Avalonia/Avalonia.props @@ -13,5 +13,6 @@ + From e7e37cfaa1cb9dd4ff35871e8588433c12188484 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Jun 2023 12:45:00 +0100 Subject: [PATCH 28/32] Revert "attempt to make build services auto updated." This reverts commit 58f619290bbb86a292c2f3610b3c977bd79a5d12. --- packages/Avalonia/Avalonia.csproj | 1 + packages/Avalonia/Avalonia.props | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 00f0c0577c..b8373cf609 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,6 +5,7 @@ + all diff --git a/packages/Avalonia/Avalonia.props b/packages/Avalonia/Avalonia.props index eaf11d805a..a2da228887 100644 --- a/packages/Avalonia/Avalonia.props +++ b/packages/Avalonia/Avalonia.props @@ -13,6 +13,5 @@ - From 628a54be63fb7c4ee9b7eee62b38f95ca3fd1595 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Jun 2023 12:46:13 +0100 Subject: [PATCH 29/32] fix build services version. --- packages/Avalonia/Avalonia.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index b8373cf609..50b7df15fa 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,7 @@ - + all From 2aabc38895d5bf1a7446216d34020c3248ecf5db Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Jun 2023 13:50:50 +0100 Subject: [PATCH 30/32] fix build services. --- packages/Avalonia/Avalonia.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 50b7df15fa..b2d369f93b 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,7 @@ - + all From 013e9dd8f071f2c42590f5a78e9e83a3c7f43548 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 30 Jun 2023 15:24:51 +0200 Subject: [PATCH 31/32] Fix PresenterPropertyChanged add handler --- src/Avalonia.Controls/TextBox.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 3b92baa42a..37e7b7beac 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -812,14 +812,14 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTree(e); - if (IsFocused) + if (_presenter != null) { - if(_presenter != null) + if (IsFocused) { _presenter.ShowCaret(); - - _presenter.PropertyChanged += PresenterPropertyChanged; } + + _presenter.PropertyChanged += PresenterPropertyChanged; } } From 125cbc42bb7a1f98fde22c30aebe18040b70ca7a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 30 Jun 2023 15:32:57 +0100 Subject: [PATCH 32/32] Ensure render is started when a window opens. --- src/Avalonia.Controls/Window.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 42052b8d69..ce4fc8dd5c 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -715,8 +715,8 @@ namespace Avalonia.Controls SetWindowStartupLocation(owner); - PlatformImpl?.Show(ShowActivated, false); StartRendering(); + PlatformImpl?.Show(ShowActivated, false); OnOpened(EventArgs.Empty); } } @@ -791,9 +791,8 @@ namespace Avalonia.Controls SetWindowStartupLocation(owner); - PlatformImpl?.Show(ShowActivated, true); - StartRendering(); + PlatformImpl?.Show(ShowActivated, true); Observable.FromEventPattern( x => Closed += x,