diff --git a/.editorconfig b/.editorconfig index 238e9887bd..ff7ac5d69e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -140,16 +140,22 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme # CS1591: Missing XML comment for publicly visible type or member dotnet_diagnostic.CS1591.severity = suggestion +# CS0162: Remove unreachable code +dotnet_diagnostic.CS0162.severity = error # CA1304: Specify CultureInfo dotnet_diagnostic.CA1304.severity = warning # CA1802: Use literals where appropriate dotnet_diagnostic.CA1802.severity = warning +# CA1815: Override equals and operator equals on value types +dotnet_diagnostic.CA1815.severity = warning # CA1820: Test for empty strings using string length dotnet_diagnostic.CA1820.severity = warning # CA1821: Remove empty finalizers dotnet_diagnostic.CA1821.severity = warning # CA1822: Mark members as static dotnet_diagnostic.CA1822.severity = suggestion +# CA1823: Avoid unused private fields +dotnet_diagnostic.CA1823.severity = warning dotnet_code_quality.CA1822.api_surface = private, internal # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning diff --git a/Avalonia.sln b/Avalonia.sln index 7efb294b64..fc42a5d63b 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -100,7 +100,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\EmbedXaml.props = build\EmbedXaml.props build\HarfBuzzSharp.props = build\HarfBuzzSharp.props build\ImageSharp.props = build\ImageSharp.props - build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\Microsoft.CSharp.props = build\Microsoft.CSharp.props build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props @@ -136,8 +135,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.LinuxFramebuffer", "src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj", "{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.Skia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}" @@ -228,9 +225,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser", "src\Bro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}" EndProject @@ -366,10 +363,6 @@ Global {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.Build.0 = Release|Any CPU - {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Any CPU.Build.0 = Release|Any CPU {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -580,7 +573,6 @@ Global {7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098} {854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} - {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098} diff --git a/build/JetBrains.Annotations.props b/build/JetBrains.Annotations.props deleted file mode 100644 index 7bc12cbd84..0000000000 --- a/build/JetBrains.Annotations.props +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/nukebuild/DotNetConfigHelper.cs b/nukebuild/DotNetConfigHelper.cs index eca1e2684d..9d43261616 100644 --- a/nukebuild/DotNetConfigHelper.cs +++ b/nukebuild/DotNetConfigHelper.cs @@ -1,5 +1,4 @@ using System.Globalization; -using JetBrains.Annotations; using Nuke.Common.Tools.DotNet; // ReSharper disable ReturnValueOfPureMethodIsNotUsed diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 4a5f5bc96c..9e0fb3f852 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -92,6 +92,9 @@ + + + diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml b/samples/ControlCatalog/Pages/CompositionPage.axaml index 403f45b8eb..602b9b768d 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml @@ -2,44 +2,57 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pages="using:ControlCatalog.Pages" x:Class="ControlCatalog.Pages.CompositionPage"> - - Implicit animations + + + - - - - - - - - - - - - - - - - - - - - - - Resize me - - + + + + + + + + + + + + + + + + + + + + + Resize me + + - - - + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index c70675b606..8b12a2d663 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -1,28 +1,39 @@ using System; using System.Collections.Generic; +using System.Numerics; +using System.Threading; using Avalonia; +using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Animations; using Avalonia.VisualTree; +using Math = System.Math; namespace ControlCatalog.Pages; public partial class CompositionPage : UserControl { private ImplicitAnimationCollection? _implicitAnimations; + private CompositionCustomVisual? _customVisual; + private CompositionSolidColorVisual? _solidVisual; public CompositionPage() { AvaloniaXamlLoader.Load(this); + AttachAnimatedSolidVisual(this.FindControl("SolidVisualHost")!); + AttachCustomVisual(this.FindControl("CustomVisualHost")!); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); this.Get("Items").Items = CreateColorItems(); + } private static List CreateColorItems() @@ -126,6 +137,167 @@ public partial class CompositionPage : UserControl compositionVisual.ImplicitAnimations = page._implicitAnimations; } } + + void AttachAnimatedSolidVisual(Visual v) + { + void Update() + { + if(_solidVisual == null) + return; + _solidVisual.Size = new Vector2((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3); + _solidVisual.Offset = new Vector3((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3, 0); + } + v.AttachedToVisualTree += delegate + { + var compositor = ElementComposition.GetElementVisual(v)?.Compositor; + if(compositor == null || _solidVisual?.Compositor == compositor) + return; + _solidVisual = compositor.CreateSolidColorVisual(); + ElementComposition.SetElementChildVisual(v, _solidVisual); + _solidVisual.Color = Colors.Red; + var animation = _solidVisual.Compositor.CreateColorKeyFrameAnimation(); + animation.InsertKeyFrame(0, Colors.Red); + animation.InsertKeyFrame(0.5f, Colors.Blue); + animation.InsertKeyFrame(1, Colors.Green); + animation.Duration = TimeSpan.FromSeconds(5); + animation.IterationBehavior = AnimationIterationBehavior.Forever; + animation.Direction = PlaybackDirection.Alternate; + _solidVisual.StartAnimation("Color", animation); + + _solidVisual.AnchorPoint = new Vector2(0, 0); + + var scale = _solidVisual.Compositor.CreateVector3KeyFrameAnimation(); + scale.Duration = TimeSpan.FromSeconds(5); + scale.IterationBehavior = AnimationIterationBehavior.Forever; + scale.InsertKeyFrame(0, new Vector3(1, 1, 0)); + scale.InsertKeyFrame(0.5f, new Vector3(1.5f, 1.5f, 0)); + scale.InsertKeyFrame(1, new Vector3(1, 1, 0)); + + _solidVisual.StartAnimation("Scale", scale); + + var center = + _solidVisual.Compositor.CreateExpressionAnimation( + "Vector3(this.Target.Size.X * 0.5, this.Target.Size.Y * 0.5, 1)"); + _solidVisual.StartAnimation("CenterPoint", center); + Update(); + }; + v.PropertyChanged += (_, a) => + { + if (a.Property == BoundsProperty) + Update(); + }; + } + + void AttachCustomVisual(Visual v) + { + void Update() + { + if (_customVisual == null) + return; + var h = (float)Math.Min(v.Bounds.Height, v.Bounds.Width / 3); + _customVisual.Size = new Vector2((float)v.Bounds.Width, h); + _customVisual.Offset = new Vector3(0, (float)(v.Bounds.Height - h) / 2, 0); + } + v.AttachedToVisualTree += delegate + { + var compositor = ElementComposition.GetElementVisual(v)?.Compositor; + if(compositor == null || _customVisual?.Compositor == compositor) + return; + _customVisual = compositor.CreateCustomVisual(new CustomVisualHandler()); + ElementComposition.SetElementChildVisual(v, _customVisual); + _customVisual.SendHandlerMessage(CustomVisualHandler.StartMessage); + Update(); + }; + + v.PropertyChanged += (_, a) => + { + if (a.Property == BoundsProperty) + Update(); + }; + } + + class CustomVisualHandler : CompositionCustomVisualHandler + { + private TimeSpan _animationElapsed; + private TimeSpan? _lastServerTime; + private bool _running; + + public static readonly object StopMessage = new(), StartMessage = new(); + + public override void OnRender(ImmediateDrawingContext drawingContext) + { + if (_running) + { + if (_lastServerTime.HasValue) _animationElapsed += (CompositionNow - _lastServerTime.Value); + _lastServerTime = CompositionNow; + } + + const int cnt = 20; + var maxPointSizeX = EffectiveSize.X / (cnt * 1.6); + var maxPointSizeY = EffectiveSize.Y / 4; + var pointSize = Math.Min(maxPointSizeX, maxPointSizeY); + var animationLength = TimeSpan.FromSeconds(4); + var animationStage = _animationElapsed.TotalSeconds / animationLength.TotalSeconds; + + var sinOffset = Math.Cos(_animationElapsed.TotalSeconds) * 1.5; + + for (var c = 0; c < cnt; c++) + { + var stage = (animationStage + (double)c / cnt) % 1; + var colorStage = + (animationStage + (Math.Sin(_animationElapsed.TotalSeconds * 2) + 1) / 2 + (double)c / cnt) % 1; + var posX = (EffectiveSize.X + pointSize * 3) * stage - pointSize; + var posY = (EffectiveSize.Y - pointSize) * (1 + Math.Sin(stage * 3.14 * 3 + sinOffset)) / 2 + pointSize / 2; + var opacity = Math.Sin(stage * 3.14); + + + drawingContext.DrawEllipse(new ImmutableSolidColorBrush(Color.FromArgb( + 255, + (byte)(255 - 255 * colorStage), + (byte)(255 * Math.Abs(0.5 - colorStage) * 2), + (byte)(255 * colorStage) + ), opacity), null, + new Point(posX, posY), pointSize / 2, pointSize / 2); + } + + } + + public override void OnMessage(object message) + { + if (message == StartMessage) + { + _running = true; + _lastServerTime = null; + RegisterForNextAnimationFrameUpdate(); + } + else if (message == StopMessage) + _running = false; + } + + public override void OnAnimationFrameUpdate() + { + if (_running) + { + Invalidate(); + RegisterForNextAnimationFrameUpdate(); + } + } + } + + private void ButtonThreadSleep(object? sender, RoutedEventArgs e) + { + Thread.Sleep(10000); + } + + private void ButtonStartCustomVisual(object? sender, RoutedEventArgs e) + { + _customVisual?.SendHandlerMessage(CustomVisualHandler.StartMessage); + } + + private void ButtonStopCustomVisual(object? sender, RoutedEventArgs e) + { + _customVisual?.SendHandlerMessage(CustomVisualHandler.StopMessage); + } } public class CompositionPageColorItem diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs new file mode 100644 index 0000000000..ee10f21317 --- /dev/null +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -0,0 +1,212 @@ +using System; +using System.Numerics; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml; +using Avalonia.Rendering.Composition; + +namespace ControlCatalog.Pages +{ + public class GesturePage : UserControl + { + private bool _isInit; + private float _currentScale; + + public GesturePage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if(_isInit) + { + return; + } + + _isInit = true; + + SetPullHandlers(this.Find("TopPullZone"), false); + SetPullHandlers(this.Find("BottomPullZone"), true); + SetPullHandlers(this.Find("RightPullZone"), true); + SetPullHandlers(this.Find("LeftPullZone"), false); + + var image = this.Find("PinchImage"); + SetPinchHandlers(image); + + var reset = this.Find + + diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs index 511b01c7ac..5b3169a1b0 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs +++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs @@ -70,7 +70,7 @@ namespace ControlCatalog.Pages 3 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, 0), cropSize), 4 => new PixelRect(new PixelPoint(0, bitmapHeight - cropSize.Height), cropSize), 5 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, bitmapHeight - cropSize.Height), cropSize), - _ => PixelRect.Empty + _ => default }; } diff --git a/samples/interop/Direct3DInteropSample/App.paml b/samples/interop/Direct3DInteropSample/App.paml deleted file mode 100644 index e6d77dfaf4..0000000000 --- a/samples/interop/Direct3DInteropSample/App.paml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs deleted file mode 100644 index 29365decfe..0000000000 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Markup.Xaml; - -namespace Direct3DInteropSample -{ - public class App : Application - { - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - desktop.MainWindow = new MainWindow(); - base.OnFrameworkInitializationCompleted(); - } - } -} diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj deleted file mode 100644 index f9ef4693d5..0000000000 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - Exe - net461 - - - - - - %(Filename) - - - Designer - - - - - - - - PreserveNewest - - - - - - - - - - - diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs deleted file mode 100644 index 6cc3cb9116..0000000000 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System; - -using Avalonia; -using Avalonia.Controls; -using Avalonia.Direct2D1; -using Avalonia.Direct2D1.Media; -using Avalonia.Markup.Xaml; -using Avalonia.Platform; -using Avalonia.Rendering; - -using SharpDX; -using SharpDX.D3DCompiler; -using SharpDX.Direct2D1; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; -using SharpDX.DXGI; - -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using Buffer = SharpDX.Direct3D11.Buffer; -using DeviceContext = SharpDX.Direct2D1.DeviceContext; -using Factory2 = SharpDX.DXGI.Factory2; -using InputElement = SharpDX.Direct3D11.InputElement; -using Matrix = SharpDX.Matrix; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; -using Resource = SharpDX.Direct3D11.Resource; - -namespace Direct3DInteropSample -{ - public class MainWindow : Window - { - Texture2D _backBuffer; - RenderTargetView _renderView; - Texture2D _depthBuffer; - DepthStencilView _depthView; - private readonly SwapChain _swapChain; - private SwapChainDescription1 _desc; - private Matrix _proj = Matrix.Identity; - private readonly Matrix _view; - private Buffer _contantBuffer; - private DeviceContext _deviceContext; - private readonly MainWindowViewModel _model; - - public MainWindow() - { - DataContext = _model = new MainWindowViewModel(); - - _desc = new SwapChainDescription1() - { - BufferCount = 1, - Width = (int)ClientSize.Width, - Height = (int)ClientSize.Height, - Format = Format.R8G8B8A8_UNorm, - SampleDescription = new SampleDescription(1, 0), - SwapEffect = SwapEffect.Discard, - Usage = Usage.RenderTargetOutput - }; - - using (var factory = Direct2D1Platform.DxgiDevice.Adapter.GetParent()) - { - _swapChain = new SwapChain1(factory, Direct2D1Platform.DxgiDevice, PlatformImpl?.Handle.Handle ?? IntPtr.Zero, ref _desc); - } - - _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) - { - DotsPerInch = new Size2F(96, 96) - }; - - CreateMesh(); - - _view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY); - - this.GetObservable(ClientSizeProperty).Subscribe(Resize); - - Resize(ClientSize); - - AvaloniaXamlLoader.Load(this); - - Background = Avalonia.Media.Brushes.Transparent; - } - - - protected override void HandlePaint(Rect rect) - { - var viewProj = Matrix.Multiply(_view, _proj); - var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; - - // Clear views - context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0); - context.ClearRenderTargetView(_renderView, Color.White); - - // Update WorldViewProj Matrix - var worldViewProj = Matrix.RotationX((float)_model.RotationX) * Matrix.RotationY((float)_model.RotationY) - * Matrix.RotationZ((float)_model.RotationZ) - * Matrix.Scaling((float)_model.Zoom) - * viewProj; - worldViewProj.Transpose(); - context.UpdateSubresource(ref worldViewProj, _contantBuffer); - - // Draw the cube - context.Draw(36, 0); - base.HandlePaint(rect); - - // Present! - _swapChain.Present(0, PresentFlags.None); - } - - private void CreateMesh() - { - var device = Direct2D1Platform.Direct3D11Device; - - // Compile Vertex and Pixel shaders - var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0"); - var vertexShader = new VertexShader(device, vertexShaderByteCode); - - var pixelShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "PS", "ps_4_0"); - var pixelShader = new PixelShader(device, pixelShaderByteCode); - - var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode); - - var inputElements = new[] - { - new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), - new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) - }; - - // Layout from VertexShader input signature - var layout = new InputLayout( - device, - signature, - inputElements); - - // Instantiate Vertex buffer from vertex data - var vertices = Buffer.Create( - device, - BindFlags.VertexBuffer, - new[] - { - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - }); - - // Create Constant Buffer - _contantBuffer = new Buffer(device, Utilities.SizeOf(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0); - - var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; - - // Prepare All the stages - context.InputAssembler.InputLayout = layout; - context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; - context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertices, Utilities.SizeOf() * 2, 0)); - context.VertexShader.SetConstantBuffer(0, _contantBuffer); - context.VertexShader.Set(vertexShader); - context.PixelShader.Set(pixelShader); - } - - private void Resize(Size size) - { - Utilities.Dispose(ref _deviceContext); - Utilities.Dispose(ref _backBuffer); - Utilities.Dispose(ref _renderView); - Utilities.Dispose(ref _depthBuffer); - Utilities.Dispose(ref _depthView); - var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; - - // Resize the backbuffer - _swapChain.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None); - - // Get the backbuffer from the swapchain - _backBuffer = Resource.FromSwapChain(_swapChain, 0); - - // Renderview on the backbuffer - _renderView = new RenderTargetView(Direct2D1Platform.Direct3D11Device, _backBuffer); - - // Create the depth buffer - _depthBuffer = new Texture2D( - Direct2D1Platform.Direct3D11Device, - new Texture2DDescription() - { - Format = Format.D32_Float_S8X24_UInt, - ArraySize = 1, - MipLevels = 1, - Width = (int)size.Width, - Height = (int)size.Height, - SampleDescription = new SampleDescription(1, 0), - Usage = ResourceUsage.Default, - BindFlags = BindFlags.DepthStencil, - CpuAccessFlags = CpuAccessFlags.None, - OptionFlags = ResourceOptionFlags.None - }); - - // Create the depth buffer view - _depthView = new DepthStencilView(Direct2D1Platform.Direct3D11Device, _depthBuffer); - - // Setup targets and viewport for rendering - context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f)); - context.OutputMerger.SetTargets(_depthView, _renderView); - - // Setup new projection matrix with correct aspect ratio - _proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f); - - using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) - { - var renderTarget = new SharpDX.Direct2D1.RenderTarget( - Direct2D1Platform.Direct2D1Factory, - dxgiBackBuffer, - new RenderTargetProperties - { - DpiX = 96, - DpiY = 96, - Type = RenderTargetType.Default, - PixelFormat = new PixelFormat( - Format.Unknown, - AlphaMode.Premultiplied) - }); - - _deviceContext = renderTarget.QueryInterface(); - - renderTarget.Dispose(); - } - } - - private class D3DRenderTarget : IRenderTarget - { - private readonly MainWindow _window; - - public D3DRenderTarget(MainWindow window) - { - _window = window; - } - - public void Dispose() - { - } - - public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) - { - return new DrawingContextImpl(visualBrushRenderer, null, _window._deviceContext); - } - } - - - protected override IRenderTarget CreateRenderTarget() => new D3DRenderTarget(this); - } -} diff --git a/samples/interop/Direct3DInteropSample/MainWindow.paml b/samples/interop/Direct3DInteropSample/MainWindow.paml deleted file mode 100644 index 37c6265836..0000000000 --- a/samples/interop/Direct3DInteropSample/MainWindow.paml +++ /dev/null @@ -1,14 +0,0 @@ - - - - Rotation X - - Rotation Y - - Rotation Z - - Zoom - - - - \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs b/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs deleted file mode 100644 index 21679a99c5..0000000000 --- a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MiniMvvm; - -namespace Direct3DInteropSample -{ - public class MainWindowViewModel : ViewModelBase - { - private double _rotationX; - - public double RotationX - { - get { return _rotationX; } - set { this.RaiseAndSetIfChanged(ref _rotationX, value); } - } - - private double _rotationY = 1; - - public double RotationY - { - get { return _rotationY; } - set { this.RaiseAndSetIfChanged(ref _rotationY, value); } - } - - private double _rotationZ = 2; - - public double RotationZ - { - get { return _rotationZ; } - set { this.RaiseAndSetIfChanged(ref _rotationZ, value); } - } - - - private double _zoom = 1; - - public double Zoom - { - get { return _zoom; } - set { this.RaiseAndSetIfChanged(ref _zoom, value); } - } - } -} diff --git a/samples/interop/Direct3DInteropSample/MiniCube.fx b/samples/interop/Direct3DInteropSample/MiniCube.fx deleted file mode 100644 index f246421f2d..0000000000 --- a/samples/interop/Direct3DInteropSample/MiniCube.fx +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -struct VS_IN -{ - float4 pos : POSITION; - float4 col : COLOR; -}; - -struct PS_IN -{ - float4 pos : SV_POSITION; - float4 col : COLOR; -}; - -float4x4 worldViewProj; - -PS_IN VS( VS_IN input ) -{ - PS_IN output = (PS_IN)0; - - output.pos = mul(input.pos, worldViewProj); - output.col = input.col; - - return output; -} - -float4 PS( PS_IN input ) : SV_Target -{ - return input.col; -} \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/Program.cs b/samples/interop/Direct3DInteropSample/Program.cs deleted file mode 100644 index bf8e76d7e4..0000000000 --- a/samples/interop/Direct3DInteropSample/Program.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Avalonia; - -namespace Direct3DInteropSample -{ - class Program - { - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .With(new Win32PlatformOptions { UseDeferredRendering = false }) - .UseWin32() - .UseDirect2D1(); - - public static int Main(string[] args) - => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); - } -} diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index 8d56086470..c885a7768c 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -167,7 +167,7 @@ namespace Avalonia.Android } } - public readonly struct ComposingRegion + public readonly record struct ComposingRegion { private readonly int _start = -1; private readonly int _end = -1; diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 2b6d29e7c5..75856e4b52 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -32,6 +32,7 @@ namespace Avalonia.Android public static AndroidPlatformOptions Options { get; private set; } internal static Compositor Compositor { get; private set; } + internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; } public static void Initialize() { @@ -51,15 +52,19 @@ namespace Avalonia.Android if (Options.UseGpu) { - EglPlatformOpenGlInterface.TryInitialize(); + EglPlatformGraphics.TryInitialize(); } if (Options.UseCompositor) { Compositor = new Compositor( AvaloniaLocator.Current.GetRequiredService(), - AvaloniaLocator.Current.GetService()); + AvaloniaLocator.Current.GetService()); } + else + RenderInterface = + new PlatformRenderInterfaceContextManager(AvaloniaLocator.Current + .GetService()); } } diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs deleted file mode 100644 index e85ed11028..0000000000 --- a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Avalonia.OpenGL; -using Avalonia.OpenGL.Egl; -using Avalonia.OpenGL.Surfaces; - -namespace Avalonia.Android.OpenGL -{ - internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase - { - private readonly EglPlatformOpenGlInterface _egl; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - - private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) - { - _egl = egl; - _info = info; - } - - public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => - new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle), _info.Handle); - - public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) - { - var feature = AvaloniaLocator.Current.GetService(); - if (feature is EglPlatformOpenGlInterface egl) - { - return new GlPlatformSurface(egl, info); - } - - return null; - } - } -} diff --git a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs deleted file mode 100644 index f9071d9b27..0000000000 --- a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -using Avalonia.OpenGL.Egl; -using Avalonia.OpenGL.Surfaces; - -namespace Avalonia.Android.OpenGL -{ - internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo - { - private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; - private readonly EglSurface _surface; - private readonly IntPtr _handle; - - public GlRenderTarget( - EglPlatformOpenGlInterface egl, - EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, - EglSurface surface, - IntPtr handle) - : base(egl) - { - _info = info; - _surface = surface; - _handle = handle; - } - - public bool IsCorrupted => _handle != _info.Handle; - - public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info); - } -} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs index aabf8160f8..94e5f4bd01 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs @@ -96,12 +96,14 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IntPtr bits; // Do not touch. +#pragma warning disable CA1823 // Avoid unused private fields uint reserved1; uint reserved2; uint reserved3; uint reserved4; uint reserved5; uint reserved6; +#pragma warning restore CA1823 // Avoid unused private fields } } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 4150b52946..56dbadca03 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -8,7 +8,6 @@ using Android.Runtime; using Android.Text; using Android.Views; using Android.Views.InputMethods; -using Avalonia.Android.OpenGL; using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Storage; @@ -30,7 +29,7 @@ using AndroidRect = Android.Graphics.Rect; namespace Avalonia.Android.Platform.SkiaPlatform { - class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, + class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider { private readonly IGlPlatformSurface _gl; @@ -47,7 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _textInputMethod = new AndroidInputMethod(_view); _keyboardHelper = new AndroidKeyboardEventsHelper(this); _pointerHelper = new AndroidMotionEventsHelper(this); - _gl = GlPlatformSurface.TryCreate(this); + _gl = new EglGlPlatformSurface(this); _framebuffer = new FramebufferManager(this); RenderScaling = _view.Scaling; @@ -106,10 +105,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IRenderer CreateRenderer(IRenderRoot root) => AndroidPlatform.Options.UseCompositor - ? new CompositingRenderer(root, AndroidPlatform.Compositor) + ? new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces) : AndroidPlatform.Options.UseDeferredRendering - ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true } - : new ImmediateRenderer((Visual)root); + ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService(), + () => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces), + AndroidPlatform.RenderInterface) + { RenderOnlyOnRenderThread = true } + : new ImmediateRenderer((Visual)root, + () => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces), + AndroidPlatform.RenderInterface); public virtual void Hide() { @@ -283,7 +287,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); - IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle; + IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle; public PixelSize Size => _view.Size; diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs index 0881fde988..6a6e69894b 100644 --- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs @@ -5,7 +5,6 @@ using Avalonia.Animation.Animators; using Avalonia.Animation.Utils; using Avalonia.Data; using Avalonia.Reactive; -using JetBrains.Annotations; namespace Avalonia.Animation { diff --git a/src/Avalonia.Base/Animation/Cue.cs b/src/Avalonia.Base/Animation/Cue.cs index 6578148b07..c48f2ab6b0 100644 --- a/src/Avalonia.Base/Animation/Cue.cs +++ b/src/Avalonia.Base/Animation/Cue.cs @@ -8,7 +8,7 @@ namespace Avalonia.Animation /// Determines the time index for a . /// [TypeConverter(typeof(CueTypeConverter))] - public readonly struct Cue : IEquatable, IEquatable + public readonly record struct Cue : IEquatable, IEquatable { /// /// The normalized percent value, ranging from 0.0 to 1.0 @@ -49,15 +49,6 @@ namespace Avalonia.Animation } } - /// - /// Checks for equality between two s. - /// - /// The second cue. - public bool Equals(Cue other) - { - return CueValue == other.CueValue; - } - /// /// Checks for equality between a /// and a value. diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 0d3da66f7a..94a9ea8352 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Avalonia.Base/Contract.cs b/src/Avalonia.Base/Contract.cs deleted file mode 100644 index 27427700ac..0000000000 --- a/src/Avalonia.Base/Contract.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; - -namespace Avalonia -{ - /// - /// A stub of Code Contract's Contract class. - /// - /// - /// It would be nice to use Code Contracts on Avalonia but last time I tried it slowed things - /// to a crawl and often crashed. Instead use the same signature for checking preconditions - /// in the hope that it might become usable at some point. - /// - public static class Contract - { - /// - /// Specifies a precondition. - /// - /// - /// The exception to throw if is false. - /// - /// The precondition. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("condition:false=>stop")] - public static void Requires(bool condition) where TException : Exception, new() - { - if (!condition) - { - throw new TException(); - } - } - } -} diff --git a/src/Avalonia.Base/CornerRadius.cs b/src/Avalonia.Base/CornerRadius.cs index d56a0ef19d..1666fac2e1 100644 --- a/src/Avalonia.Base/CornerRadius.cs +++ b/src/Avalonia.Base/CornerRadius.cs @@ -61,9 +61,13 @@ namespace Avalonia public double BottomLeft { get; } /// - /// Gets a value indicating whether all corner radii are set to 0. + /// Gets a value indicating whether the instance has default values (all corner radii are set to 0). /// - public bool IsEmpty => TopLeft.Equals(0) && IsUniform; + public bool IsDefault => TopLeft == 0 && TopRight == 0 && BottomLeft == 0 && BottomRight == 0; + + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; /// /// Gets a value indicating whether all corner radii are equal. @@ -79,7 +83,6 @@ namespace Avalonia { // ReSharper disable CompareOfFloatsByEqualityOperator return TopLeft == other.TopLeft && - TopRight == other.TopRight && BottomRight == other.BottomRight && BottomLeft == other.BottomLeft; diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs index 4e07ebf445..3bc172f596 100644 --- a/src/Avalonia.Base/Data/BindingValue.cs +++ b/src/Avalonia.Base/Data/BindingValue.cs @@ -80,7 +80,7 @@ namespace Avalonia.Data /// - For an unset value, use or simply `default` /// - For other types, call one of the static factory methods /// - public readonly struct BindingValue + public readonly record struct BindingValue { private readonly T _value; diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index e4b833176c..5fb2bb5c13 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -4,8 +4,6 @@ namespace Avalonia.Data.Core { public abstract class ExpressionNode { - private static readonly object CacheInvalid = new object(); - protected static readonly WeakReference UnsetReference = new WeakReference(AvaloniaProperty.UnsetValue); diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 0a9f834aeb..2151c100e5 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -49,8 +49,6 @@ namespace Avalonia.Data.Core new TaskStreamPlugin(), new ObservableStreamPlugin(), }; - - private static readonly object UninitializedValue = new object(); private readonly ExpressionNode _node; private object? _root; private Func? _rootGetter; diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index a349486bf8..4a1e2660de 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -28,11 +28,9 @@ namespace Avalonia.Data /// public InstancedBinding(ISubject subject, BindingMode mode, BindingPriority priority) { - Contract.Requires(subject != null); - Mode = mode; Priority = priority; - Value = subject; + Value = subject ?? throw new ArgumentNullException(nameof(subject)); } private InstancedBinding(object? value, BindingMode mode, BindingPriority priority) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs new file mode 100644 index 0000000000..eea7c3b7d1 --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -0,0 +1,128 @@ +using Avalonia.Input.GestureRecognizers; + +namespace Avalonia.Input +{ + public class PinchGestureRecognizer : StyledElement, IGestureRecognizer + { + private IInputElement? _target; + private IGestureRecognizerActionsDispatcher? _actions; + private float _initialDistance; + private IPointer? _firstContact; + private Point _firstPoint; + private IPointer? _secondContact; + private Point _secondPoint; + private Point _origin; + + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + { + _target = target; + _actions = actions; + } + + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + PointerPressed(e); + } + + private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + PointerReleased(e); + } + + public void PointerCaptureLost(IPointer pointer) + { + RemoveContact(pointer); + } + + public void PointerMoved(PointerEventArgs e) + { + if (_target != null && _target is Visual visual) + { + if(_firstContact == e.Pointer) + { + _firstPoint = e.GetPosition(visual); + } + else if (_secondContact == e.Pointer) + { + _secondPoint = e.GetPosition(visual); + } + else + { + return; + } + + if (_firstContact != null && _secondContact != null) + { + var distance = GetDistance(_firstPoint, _secondPoint); + + var scale = distance / _initialDistance; + + _target?.RaiseEvent(new PinchEventArgs(scale, _origin)); + } + } + } + + public void PointerPressed(PointerPressedEventArgs e) + { + if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + { + if (_firstContact == null) + { + _firstContact = e.Pointer; + _firstPoint = e.GetPosition(visual); + + return; + } + else if (_secondContact == null && _firstContact != e.Pointer) + { + _secondContact = e.Pointer; + _secondPoint = e.GetPosition(visual); + } + else + { + return; + } + + if (_firstContact != null && _secondContact != null) + { + _initialDistance = GetDistance(_firstPoint, _secondPoint); + + _origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f); + + _actions!.Capture(_firstContact, this); + _actions!.Capture(_secondContact, this); + } + } + } + + public void PointerReleased(PointerReleasedEventArgs e) + { + RemoveContact(e.Pointer); + } + + private void RemoveContact(IPointer pointer) + { + if (_firstContact == pointer || _secondContact == pointer) + { + if (_secondContact == pointer) + { + _secondContact = null; + } + + if (_firstContact == pointer) + { + _firstContact = _secondContact; + + _secondContact = null; + } + _target?.RaiseEvent(new PinchEndedEventArgs()); + } + } + + private float GetDistance(Point a, Point b) + { + var length = _secondPoint - _firstPoint; + return (float)new Vector(length.X, length.Y).Length; + } + } +} diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index fedd07ec32..23bab13fc8 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -1,15 +1,19 @@ -using Avalonia.Input.GestureRecognizers; +using System; +using Avalonia.Input.GestureRecognizers; namespace Avalonia.Input { public class PullGestureRecognizer : StyledElement, IGestureRecognizer { + internal static int MinPullDetectionSize = 50; + private IInputElement? _target; private IGestureRecognizerActionsDispatcher? _actions; private Point _initialPosition; private int _gestureId; private IPointer? _tracking; private PullDirection _pullDirection; + private bool _pullInProgress; /// /// Defines the property. @@ -31,23 +35,12 @@ namespace Avalonia.Input PullDirection = pullDirection; } + public PullGestureRecognizer() { } + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) { _target = target; _actions = actions; - - _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - } - - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) - { - PointerPressed(e); - } - - private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) - { - PointerReleased(e); } public void PointerCaptureLost(IPointer pointer) @@ -94,6 +87,7 @@ namespace Avalonia.Input break; } + _pullInProgress = true; _target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection)); } } @@ -111,16 +105,16 @@ namespace Avalonia.Input switch (PullDirection) { case PullDirection.TopToBottom: - canPull = position.Y < bounds.Height * 0.1; + canPull = position.Y < Math.Max(MinPullDetectionSize, bounds.Height * 0.1); break; case PullDirection.BottomToTop: - canPull = position.Y > bounds.Height - (bounds.Height * 0.1); + canPull = position.Y > Math.Min(bounds.Height - MinPullDetectionSize, bounds.Height - (bounds.Height * 0.1)); break; case PullDirection.LeftToRight: - canPull = position.X < bounds.Width * 0.1; + canPull = position.X < Math.Max(MinPullDetectionSize, bounds.Width * 0.1); break; case PullDirection.RightToLeft: - canPull = position.X > bounds.Width - (bounds.Width * 0.1); + canPull = position.X > Math.Min(bounds.Width - MinPullDetectionSize, bounds.Width - (bounds.Width * 0.1)); break; } @@ -135,7 +129,7 @@ namespace Avalonia.Input public void PointerReleased(PointerReleasedEventArgs e) { - if (_tracking == e.Pointer) + if (_tracking == e.Pointer && _pullInProgress) { EndPull(); } @@ -145,6 +139,7 @@ namespace Avalonia.Input { _tracking = null; _initialPosition = default; + _pullInProgress = false; _target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection)); } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 7bcb81767d..64fe275547 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -16,6 +16,7 @@ namespace Avalonia.Input.GestureRecognizers private bool _canHorizontallyScroll; private bool _canVerticallyScroll; private int _gestureId; + private int _scrollStartDistance = 30; // Movement per second private Vector _inertia; @@ -38,6 +39,15 @@ namespace Avalonia.Input.GestureRecognizers nameof(CanVerticallyScroll), o => o.CanVerticallyScroll, (o, v) => o.CanVerticallyScroll = v); + + /// + /// Defines the property. + /// + public static readonly DirectProperty ScrollStartDistanceProperty = + AvaloniaProperty.RegisterDirect( + nameof(ScrollStartDistance), + o => o.ScrollStartDistance, + (o, v) => o.ScrollStartDistance = v); /// /// Gets or sets a value indicating whether the content can be scrolled horizontally. @@ -56,6 +66,15 @@ namespace Avalonia.Input.GestureRecognizers get => _canVerticallyScroll; set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); } + + /// + /// Gets or sets a value indicating the distance the pointer moves before scrolling is started + /// + public int ScrollStartDistance + { + get => _scrollStartDistance; + set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); + } public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) @@ -75,9 +94,6 @@ namespace Avalonia.Input.GestureRecognizers _trackedRootPoint = e.GetPosition((Visual?)_target); } } - - // Arbitrary chosen value, probably need to move that to platform settings or something - private const double ScrollStartDistance = 30; // Pixels per second speed that is considered to be the stop of inertial scroll private const double InertialScrollSpeedEnd = 5; diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 1ea88fe824..b4d5feaf3b 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -46,6 +46,14 @@ namespace Avalonia.Input private static readonly WeakReference s_lastPress = new WeakReference(null); private static Point s_lastPressPoint; + public static readonly RoutedEvent PinchEvent = + RoutedEvent.Register( + "PinchEvent", RoutingStrategies.Bubble, typeof(Gestures)); + + public static readonly RoutedEvent PinchEndedEvent = + RoutedEvent.Register( + "PinchEndedEvent", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent PullGestureEvent = RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs index 39c9766105..35fa549995 100644 --- a/src/Avalonia.Base/Input/KeyEventArgs.cs +++ b/src/Avalonia.Base/Input/KeyEventArgs.cs @@ -5,7 +5,7 @@ namespace Avalonia.Input { public class KeyEventArgs : RoutedEventArgs { - internal KeyEventArgs() + public KeyEventArgs() { } diff --git a/src/Avalonia.Base/Input/PinchEventArgs.cs b/src/Avalonia.Base/Input/PinchEventArgs.cs new file mode 100644 index 0000000000..31c760eb51 --- /dev/null +++ b/src/Avalonia.Base/Input/PinchEventArgs.cs @@ -0,0 +1,24 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class PinchEventArgs : RoutedEventArgs + { + public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent) + { + Scale = scale; + ScaleOrigin = scaleOrigin; + } + + public double Scale { get; } = 1; + + public Point ScaleOrigin { get; } + } + + public class PinchEndedEventArgs : RoutedEventArgs + { + public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent) + { + } + } +} diff --git a/src/Avalonia.Base/Input/PointerPoint.cs b/src/Avalonia.Base/Input/PointerPoint.cs index c51f286053..bd36b73b26 100644 --- a/src/Avalonia.Base/Input/PointerPoint.cs +++ b/src/Avalonia.Base/Input/PointerPoint.cs @@ -5,7 +5,7 @@ namespace Avalonia.Input /// /// Provides basic properties for the input pointer associated with a single mouse, pen/stylus, or touch contact. /// - public struct PointerPoint + public record struct PointerPoint { public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties) { @@ -33,7 +33,7 @@ namespace Avalonia.Input /// /// Provides extended properties for a PointerPoint object. /// - public struct PointerPointProperties + public record struct PointerPointProperties { /// /// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device. diff --git a/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs index 3a5ae1340f..cf4d37b6da 100644 --- a/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs @@ -21,11 +21,9 @@ namespace Avalonia.Input.Raw /// The root from which the event originates. public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root) { - device = device ?? throw new ArgumentNullException(nameof(device)); - - Device = device; + Device = device ?? throw new ArgumentNullException(nameof(device)); Timestamp = timestamp; - Root = root; + Root = root ?? throw new ArgumentNullException(nameof(root)); } /// diff --git a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs index b8f6f99ae8..ebedc9e6e8 100644 --- a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs @@ -53,9 +53,6 @@ namespace Avalonia.Input.Raw RawInputModifiers inputModifiers) : base(device, timestamp, root) { - Contract.Requires(device != null); - Contract.Requires(root != null); - Point = new RawPointerPoint(); Position = position; Type = type; @@ -80,9 +77,6 @@ namespace Avalonia.Input.Raw RawInputModifiers inputModifiers) : base(device, timestamp, root) { - Contract.Requires(device != null); - Contract.Requires(root != null); - Point = point; Type = type; InputModifiers = inputModifiers; @@ -130,7 +124,7 @@ namespace Avalonia.Input.Raw internal IInputElement? InputHitTestResult { get; set; } } - public struct RawPointerPoint + public record struct RawPointerPoint { /// /// Pointer position, in client DIPs. diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs index 4a60f8a046..531cf3c704 100644 --- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs +++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs @@ -46,7 +46,7 @@ namespace Avalonia.Input.TextInput void SelectInSurroundingText(int start, int end); } - public struct TextInputMethodSurroundingText + public record struct TextInputMethodSurroundingText { public string Text { get; set; } public int CursorOffset { get; set; } diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs index 787bf1abd3..a027bec0c6 100644 --- a/src/Avalonia.Base/Input/TextInputEventArgs.cs +++ b/src/Avalonia.Base/Input/TextInputEventArgs.cs @@ -4,7 +4,7 @@ namespace Avalonia.Input { public class TextInputEventArgs : RoutedEventArgs { - internal TextInputEventArgs() + public TextInputEventArgs() { } diff --git a/src/Avalonia.Base/Logging/ParametrizedLogger.cs b/src/Avalonia.Base/Logging/ParametrizedLogger.cs index 6b7331b504..a82af41a75 100644 --- a/src/Avalonia.Base/Logging/ParametrizedLogger.cs +++ b/src/Avalonia.Base/Logging/ParametrizedLogger.cs @@ -5,7 +5,7 @@ namespace Avalonia.Logging /// /// Logger sink parametrized for given logging level. /// - public readonly struct ParametrizedLogger + public readonly record struct ParametrizedLogger { private readonly ILogSink _sink; private readonly LogEventLevel _level; diff --git a/src/Avalonia.Base/Matrix.cs b/src/Avalonia.Base/Matrix.cs index df6439b1b3..a72710ffd4 100644 --- a/src/Avalonia.Base/Matrix.cs +++ b/src/Avalonia.Base/Matrix.cs @@ -571,7 +571,7 @@ namespace Avalonia return true; } - public struct Decomposed + public record struct Decomposed { public Vector Translate; public Vector Scale; diff --git a/src/Avalonia.Base/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs index 2139516be6..dd2c23f4ae 100644 --- a/src/Avalonia.Base/Media/BoxShadow.cs +++ b/src/Avalonia.Base/Media/BoxShadow.cs @@ -45,7 +45,14 @@ namespace Avalonia.Media } } - public bool IsEmpty => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0; + /// + /// Gets a value indicating whether the instance has default values. + /// + public bool IsDefault => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0; + + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; private readonly static char[] s_Separator = new char[] { ' ', '\t' }; @@ -82,7 +89,7 @@ namespace Avalonia.Media { var sb = StringBuilderCache.Acquire(); - if (IsEmpty) + if (IsDefault) { return "none"; } @@ -171,5 +178,11 @@ namespace Avalonia.Media public Rect TransformBounds(in Rect rect) => IsInset ? rect : rect.Translate(new Vector(OffsetX, OffsetY)).Inflate(Spread + Blur); + + public static bool operator ==(BoxShadow left, BoxShadow right) => + left.Equals(right); + + public static bool operator !=(BoxShadow left, BoxShadow right) => + !(left == right); } } diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs index ab2694389f..50ae7699dd 100644 --- a/src/Avalonia.Base/Media/BoxShadows.cs +++ b/src/Avalonia.Base/Media/BoxShadows.cs @@ -21,7 +21,7 @@ namespace Avalonia.Media { _first = shadow; _list = null; - Count = _first.IsEmpty ? 0 : 1; + Count = _first.IsDefault ? 0 : 1; } public BoxShadows(BoxShadow first, BoxShadow[] rest) @@ -62,7 +62,9 @@ namespace Avalonia.Media } [EditorBrowsable(EditorBrowsableState.Never)] +#pragma warning disable CA1815 // Override equals and operator equals on value types public struct BoxShadowsEnumerator +#pragma warning restore CA1815 // Override equals and operator equals on value types { private int _index; private BoxShadows _shadows; @@ -118,7 +120,7 @@ namespace Avalonia.Media get { foreach(var boxShadow in this) - if (!boxShadow.IsEmpty && boxShadow.IsInset) + if (!boxShadow.IsDefault && boxShadow.IsInset) return true; return false; } @@ -149,5 +151,11 @@ namespace Avalonia.Media return hashCode; } } + + public static bool operator ==(BoxShadows left, BoxShadows right) => + left.Equals(right); + + public static bool operator !=(BoxShadows left, BoxShadows right) => + !(left == right); } } diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs index 8d531e9394..4138f1c891 100644 --- a/src/Avalonia.Base/Media/Brush.cs +++ b/src/Avalonia.Base/Media/Brush.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media /// Describes how an area is painted. /// [TypeConverter(typeof(BrushConverter))] - public abstract class Brush : Animatable, IMutableBrush + public abstract class Brush : Animatable { /// /// Defines the property. @@ -92,9 +92,6 @@ namespace Avalonia.Media throw new FormatException($"Invalid brush string: '{s}'."); } - /// - public abstract IBrush ToImmutable(); - /// /// Marks a property as affecting the brush's visual representation. /// diff --git a/src/Avalonia.Base/Media/BrushExtensions.cs b/src/Avalonia.Base/Media/BrushExtensions.cs index 2fc8778a5e..d0208c187a 100644 --- a/src/Avalonia.Base/Media/BrushExtensions.cs +++ b/src/Avalonia.Base/Media/BrushExtensions.cs @@ -16,11 +16,11 @@ namespace Avalonia.Media /// The result of calling if the brush is mutable, /// otherwise . /// - public static IBrush ToImmutable(this IBrush brush) + public static IImmutableBrush ToImmutable(this IBrush brush) { _ = brush ?? throw new ArgumentNullException(nameof(brush)); - return (brush as IMutableBrush)?.ToImmutable() ?? brush; + return (brush as IMutableBrush)?.ToImmutable() ?? (IImmutableBrush)brush; } /// diff --git a/src/Avalonia.Base/Media/Brushes.cs b/src/Avalonia.Base/Media/Brushes.cs index 5957775f39..7ecba6df3e 100644 --- a/src/Avalonia.Base/Media/Brushes.cs +++ b/src/Avalonia.Base/Media/Brushes.cs @@ -8,706 +8,706 @@ namespace Avalonia.Media /// /// Gets an colored brush. /// - public static ISolidColorBrush AliceBlue => KnownColor.AliceBlue.ToBrush(); + public static IImmutableSolidColorBrush AliceBlue => KnownColor.AliceBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush AntiqueWhite => KnownColor.AntiqueWhite.ToBrush(); + public static IImmutableSolidColorBrush AntiqueWhite => KnownColor.AntiqueWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Aqua => KnownColor.Aqua.ToBrush(); + public static IImmutableSolidColorBrush Aqua => KnownColor.Aqua.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Aquamarine => KnownColor.Aquamarine.ToBrush(); + public static IImmutableSolidColorBrush Aquamarine => KnownColor.Aquamarine.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Azure => KnownColor.Azure.ToBrush(); + public static IImmutableSolidColorBrush Azure => KnownColor.Azure.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Beige => KnownColor.Beige.ToBrush(); + public static IImmutableSolidColorBrush Beige => KnownColor.Beige.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Bisque => KnownColor.Bisque.ToBrush(); + public static IImmutableSolidColorBrush Bisque => KnownColor.Bisque.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Black => KnownColor.Black.ToBrush(); + public static IImmutableSolidColorBrush Black => KnownColor.Black.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BlanchedAlmond => KnownColor.BlanchedAlmond.ToBrush(); + public static IImmutableSolidColorBrush BlanchedAlmond => KnownColor.BlanchedAlmond.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Blue => KnownColor.Blue.ToBrush(); + public static IImmutableSolidColorBrush Blue => KnownColor.Blue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BlueViolet => KnownColor.BlueViolet.ToBrush(); + public static IImmutableSolidColorBrush BlueViolet => KnownColor.BlueViolet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Brown => KnownColor.Brown.ToBrush(); + public static IImmutableSolidColorBrush Brown => KnownColor.Brown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BurlyWood => KnownColor.BurlyWood.ToBrush(); + public static IImmutableSolidColorBrush BurlyWood => KnownColor.BurlyWood.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush CadetBlue => KnownColor.CadetBlue.ToBrush(); + public static IImmutableSolidColorBrush CadetBlue => KnownColor.CadetBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Chartreuse => KnownColor.Chartreuse.ToBrush(); + public static IImmutableSolidColorBrush Chartreuse => KnownColor.Chartreuse.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Chocolate => KnownColor.Chocolate.ToBrush(); + public static IImmutableSolidColorBrush Chocolate => KnownColor.Chocolate.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Coral => KnownColor.Coral.ToBrush(); + public static IImmutableSolidColorBrush Coral => KnownColor.Coral.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush CornflowerBlue => KnownColor.CornflowerBlue.ToBrush(); + public static IImmutableSolidColorBrush CornflowerBlue => KnownColor.CornflowerBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Cornsilk => KnownColor.Cornsilk.ToBrush(); + public static IImmutableSolidColorBrush Cornsilk => KnownColor.Cornsilk.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Crimson => KnownColor.Crimson.ToBrush(); + public static IImmutableSolidColorBrush Crimson => KnownColor.Crimson.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Cyan => KnownColor.Cyan.ToBrush(); + public static IImmutableSolidColorBrush Cyan => KnownColor.Cyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkBlue => KnownColor.DarkBlue.ToBrush(); + public static IImmutableSolidColorBrush DarkBlue => KnownColor.DarkBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkCyan => KnownColor.DarkCyan.ToBrush(); + public static IImmutableSolidColorBrush DarkCyan => KnownColor.DarkCyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGoldenrod => KnownColor.DarkGoldenrod.ToBrush(); + public static IImmutableSolidColorBrush DarkGoldenrod => KnownColor.DarkGoldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGray => KnownColor.DarkGray.ToBrush(); + public static IImmutableSolidColorBrush DarkGray => KnownColor.DarkGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGreen => KnownColor.DarkGreen.ToBrush(); + public static IImmutableSolidColorBrush DarkGreen => KnownColor.DarkGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkKhaki => KnownColor.DarkKhaki.ToBrush(); + public static IImmutableSolidColorBrush DarkKhaki => KnownColor.DarkKhaki.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkMagenta => KnownColor.DarkMagenta.ToBrush(); + public static IImmutableSolidColorBrush DarkMagenta => KnownColor.DarkMagenta.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOliveGreen => KnownColor.DarkOliveGreen.ToBrush(); + public static IImmutableSolidColorBrush DarkOliveGreen => KnownColor.DarkOliveGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOrange => KnownColor.DarkOrange.ToBrush(); + public static IImmutableSolidColorBrush DarkOrange => KnownColor.DarkOrange.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOrchid => KnownColor.DarkOrchid.ToBrush(); + public static IImmutableSolidColorBrush DarkOrchid => KnownColor.DarkOrchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkRed => KnownColor.DarkRed.ToBrush(); + public static IImmutableSolidColorBrush DarkRed => KnownColor.DarkRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSalmon => KnownColor.DarkSalmon.ToBrush(); + public static IImmutableSolidColorBrush DarkSalmon => KnownColor.DarkSalmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSeaGreen => KnownColor.DarkSeaGreen.ToBrush(); + public static IImmutableSolidColorBrush DarkSeaGreen => KnownColor.DarkSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSlateBlue => KnownColor.DarkSlateBlue.ToBrush(); + public static IImmutableSolidColorBrush DarkSlateBlue => KnownColor.DarkSlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSlateGray => KnownColor.DarkSlateGray.ToBrush(); + public static IImmutableSolidColorBrush DarkSlateGray => KnownColor.DarkSlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkTurquoise => KnownColor.DarkTurquoise.ToBrush(); + public static IImmutableSolidColorBrush DarkTurquoise => KnownColor.DarkTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkViolet => KnownColor.DarkViolet.ToBrush(); + public static IImmutableSolidColorBrush DarkViolet => KnownColor.DarkViolet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DeepPink => KnownColor.DeepPink.ToBrush(); + public static IImmutableSolidColorBrush DeepPink => KnownColor.DeepPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DeepSkyBlue => KnownColor.DeepSkyBlue.ToBrush(); + public static IImmutableSolidColorBrush DeepSkyBlue => KnownColor.DeepSkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DimGray => KnownColor.DimGray.ToBrush(); + public static IImmutableSolidColorBrush DimGray => KnownColor.DimGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DodgerBlue => KnownColor.DodgerBlue.ToBrush(); + public static IImmutableSolidColorBrush DodgerBlue => KnownColor.DodgerBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Firebrick => KnownColor.Firebrick.ToBrush(); + public static IImmutableSolidColorBrush Firebrick => KnownColor.Firebrick.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush FloralWhite => KnownColor.FloralWhite.ToBrush(); + public static IImmutableSolidColorBrush FloralWhite => KnownColor.FloralWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush ForestGreen => KnownColor.ForestGreen.ToBrush(); + public static IImmutableSolidColorBrush ForestGreen => KnownColor.ForestGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Fuchsia => KnownColor.Fuchsia.ToBrush(); + public static IImmutableSolidColorBrush Fuchsia => KnownColor.Fuchsia.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gainsboro => KnownColor.Gainsboro.ToBrush(); + public static IImmutableSolidColorBrush Gainsboro => KnownColor.Gainsboro.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush GhostWhite => KnownColor.GhostWhite.ToBrush(); + public static IImmutableSolidColorBrush GhostWhite => KnownColor.GhostWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gold => KnownColor.Gold.ToBrush(); + public static IImmutableSolidColorBrush Gold => KnownColor.Gold.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Goldenrod => KnownColor.Goldenrod.ToBrush(); + public static IImmutableSolidColorBrush Goldenrod => KnownColor.Goldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gray => KnownColor.Gray.ToBrush(); + public static IImmutableSolidColorBrush Gray => KnownColor.Gray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Green => KnownColor.Green.ToBrush(); + public static IImmutableSolidColorBrush Green => KnownColor.Green.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush GreenYellow => KnownColor.GreenYellow.ToBrush(); + public static IImmutableSolidColorBrush GreenYellow => KnownColor.GreenYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Honeydew => KnownColor.Honeydew.ToBrush(); + public static IImmutableSolidColorBrush Honeydew => KnownColor.Honeydew.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush HotPink => KnownColor.HotPink.ToBrush(); + public static IImmutableSolidColorBrush HotPink => KnownColor.HotPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush IndianRed => KnownColor.IndianRed.ToBrush(); + public static IImmutableSolidColorBrush IndianRed => KnownColor.IndianRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Indigo => KnownColor.Indigo.ToBrush(); + public static IImmutableSolidColorBrush Indigo => KnownColor.Indigo.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Ivory => KnownColor.Ivory.ToBrush(); + public static IImmutableSolidColorBrush Ivory => KnownColor.Ivory.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Khaki => KnownColor.Khaki.ToBrush(); + public static IImmutableSolidColorBrush Khaki => KnownColor.Khaki.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Lavender => KnownColor.Lavender.ToBrush(); + public static IImmutableSolidColorBrush Lavender => KnownColor.Lavender.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LavenderBlush => KnownColor.LavenderBlush.ToBrush(); + public static IImmutableSolidColorBrush LavenderBlush => KnownColor.LavenderBlush.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LawnGreen => KnownColor.LawnGreen.ToBrush(); + public static IImmutableSolidColorBrush LawnGreen => KnownColor.LawnGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LemonChiffon => KnownColor.LemonChiffon.ToBrush(); + public static IImmutableSolidColorBrush LemonChiffon => KnownColor.LemonChiffon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightBlue => KnownColor.LightBlue.ToBrush(); + public static IImmutableSolidColorBrush LightBlue => KnownColor.LightBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightCoral => KnownColor.LightCoral.ToBrush(); + public static IImmutableSolidColorBrush LightCoral => KnownColor.LightCoral.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightCyan => KnownColor.LightCyan.ToBrush(); + public static IImmutableSolidColorBrush LightCyan => KnownColor.LightCyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToBrush(); + public static IImmutableSolidColorBrush LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGray => KnownColor.LightGray.ToBrush(); + public static IImmutableSolidColorBrush LightGray => KnownColor.LightGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGreen => KnownColor.LightGreen.ToBrush(); + public static IImmutableSolidColorBrush LightGreen => KnownColor.LightGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightPink => KnownColor.LightPink.ToBrush(); + public static IImmutableSolidColorBrush LightPink => KnownColor.LightPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSalmon => KnownColor.LightSalmon.ToBrush(); + public static IImmutableSolidColorBrush LightSalmon => KnownColor.LightSalmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSeaGreen => KnownColor.LightSeaGreen.ToBrush(); + public static IImmutableSolidColorBrush LightSeaGreen => KnownColor.LightSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSkyBlue => KnownColor.LightSkyBlue.ToBrush(); + public static IImmutableSolidColorBrush LightSkyBlue => KnownColor.LightSkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSlateGray => KnownColor.LightSlateGray.ToBrush(); + public static IImmutableSolidColorBrush LightSlateGray => KnownColor.LightSlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSteelBlue => KnownColor.LightSteelBlue.ToBrush(); + public static IImmutableSolidColorBrush LightSteelBlue => KnownColor.LightSteelBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightYellow => KnownColor.LightYellow.ToBrush(); + public static IImmutableSolidColorBrush LightYellow => KnownColor.LightYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Lime => KnownColor.Lime.ToBrush(); + public static IImmutableSolidColorBrush Lime => KnownColor.Lime.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LimeGreen => KnownColor.LimeGreen.ToBrush(); + public static IImmutableSolidColorBrush LimeGreen => KnownColor.LimeGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Linen => KnownColor.Linen.ToBrush(); + public static IImmutableSolidColorBrush Linen => KnownColor.Linen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Magenta => KnownColor.Magenta.ToBrush(); + public static IImmutableSolidColorBrush Magenta => KnownColor.Magenta.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Maroon => KnownColor.Maroon.ToBrush(); + public static IImmutableSolidColorBrush Maroon => KnownColor.Maroon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumAquamarine => KnownColor.MediumAquamarine.ToBrush(); + public static IImmutableSolidColorBrush MediumAquamarine => KnownColor.MediumAquamarine.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumBlue => KnownColor.MediumBlue.ToBrush(); + public static IImmutableSolidColorBrush MediumBlue => KnownColor.MediumBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumOrchid => KnownColor.MediumOrchid.ToBrush(); + public static IImmutableSolidColorBrush MediumOrchid => KnownColor.MediumOrchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumPurple => KnownColor.MediumPurple.ToBrush(); + public static IImmutableSolidColorBrush MediumPurple => KnownColor.MediumPurple.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSeaGreen => KnownColor.MediumSeaGreen.ToBrush(); + public static IImmutableSolidColorBrush MediumSeaGreen => KnownColor.MediumSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSlateBlue => KnownColor.MediumSlateBlue.ToBrush(); + public static IImmutableSolidColorBrush MediumSlateBlue => KnownColor.MediumSlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSpringGreen => KnownColor.MediumSpringGreen.ToBrush(); + public static IImmutableSolidColorBrush MediumSpringGreen => KnownColor.MediumSpringGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumTurquoise => KnownColor.MediumTurquoise.ToBrush(); + public static IImmutableSolidColorBrush MediumTurquoise => KnownColor.MediumTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumVioletRed => KnownColor.MediumVioletRed.ToBrush(); + public static IImmutableSolidColorBrush MediumVioletRed => KnownColor.MediumVioletRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MidnightBlue => KnownColor.MidnightBlue.ToBrush(); + public static IImmutableSolidColorBrush MidnightBlue => KnownColor.MidnightBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MintCream => KnownColor.MintCream.ToBrush(); + public static IImmutableSolidColorBrush MintCream => KnownColor.MintCream.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MistyRose => KnownColor.MistyRose.ToBrush(); + public static IImmutableSolidColorBrush MistyRose => KnownColor.MistyRose.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Moccasin => KnownColor.Moccasin.ToBrush(); + public static IImmutableSolidColorBrush Moccasin => KnownColor.Moccasin.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush NavajoWhite => KnownColor.NavajoWhite.ToBrush(); + public static IImmutableSolidColorBrush NavajoWhite => KnownColor.NavajoWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Navy => KnownColor.Navy.ToBrush(); + public static IImmutableSolidColorBrush Navy => KnownColor.Navy.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OldLace => KnownColor.OldLace.ToBrush(); + public static IImmutableSolidColorBrush OldLace => KnownColor.OldLace.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Olive => KnownColor.Olive.ToBrush(); + public static IImmutableSolidColorBrush Olive => KnownColor.Olive.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OliveDrab => KnownColor.OliveDrab.ToBrush(); + public static IImmutableSolidColorBrush OliveDrab => KnownColor.OliveDrab.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Orange => KnownColor.Orange.ToBrush(); + public static IImmutableSolidColorBrush Orange => KnownColor.Orange.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OrangeRed => KnownColor.OrangeRed.ToBrush(); + public static IImmutableSolidColorBrush OrangeRed => KnownColor.OrangeRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Orchid => KnownColor.Orchid.ToBrush(); + public static IImmutableSolidColorBrush Orchid => KnownColor.Orchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleGoldenrod => KnownColor.PaleGoldenrod.ToBrush(); + public static IImmutableSolidColorBrush PaleGoldenrod => KnownColor.PaleGoldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleGreen => KnownColor.PaleGreen.ToBrush(); + public static IImmutableSolidColorBrush PaleGreen => KnownColor.PaleGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleTurquoise => KnownColor.PaleTurquoise.ToBrush(); + public static IImmutableSolidColorBrush PaleTurquoise => KnownColor.PaleTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleVioletRed => KnownColor.PaleVioletRed.ToBrush(); + public static IImmutableSolidColorBrush PaleVioletRed => KnownColor.PaleVioletRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PapayaWhip => KnownColor.PapayaWhip.ToBrush(); + public static IImmutableSolidColorBrush PapayaWhip => KnownColor.PapayaWhip.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PeachPuff => KnownColor.PeachPuff.ToBrush(); + public static IImmutableSolidColorBrush PeachPuff => KnownColor.PeachPuff.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Peru => KnownColor.Peru.ToBrush(); + public static IImmutableSolidColorBrush Peru => KnownColor.Peru.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Pink => KnownColor.Pink.ToBrush(); + public static IImmutableSolidColorBrush Pink => KnownColor.Pink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Plum => KnownColor.Plum.ToBrush(); + public static IImmutableSolidColorBrush Plum => KnownColor.Plum.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PowderBlue => KnownColor.PowderBlue.ToBrush(); + public static IImmutableSolidColorBrush PowderBlue => KnownColor.PowderBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Purple => KnownColor.Purple.ToBrush(); + public static IImmutableSolidColorBrush Purple => KnownColor.Purple.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Red => KnownColor.Red.ToBrush(); + public static IImmutableSolidColorBrush Red => KnownColor.Red.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush RosyBrown => KnownColor.RosyBrown.ToBrush(); + public static IImmutableSolidColorBrush RosyBrown => KnownColor.RosyBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush RoyalBlue => KnownColor.RoyalBlue.ToBrush(); + public static IImmutableSolidColorBrush RoyalBlue => KnownColor.RoyalBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SaddleBrown => KnownColor.SaddleBrown.ToBrush(); + public static IImmutableSolidColorBrush SaddleBrown => KnownColor.SaddleBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Salmon => KnownColor.Salmon.ToBrush(); + public static IImmutableSolidColorBrush Salmon => KnownColor.Salmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SandyBrown => KnownColor.SandyBrown.ToBrush(); + public static IImmutableSolidColorBrush SandyBrown => KnownColor.SandyBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SeaGreen => KnownColor.SeaGreen.ToBrush(); + public static IImmutableSolidColorBrush SeaGreen => KnownColor.SeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SeaShell => KnownColor.SeaShell.ToBrush(); + public static IImmutableSolidColorBrush SeaShell => KnownColor.SeaShell.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Sienna => KnownColor.Sienna.ToBrush(); + public static IImmutableSolidColorBrush Sienna => KnownColor.Sienna.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Silver => KnownColor.Silver.ToBrush(); + public static IImmutableSolidColorBrush Silver => KnownColor.Silver.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SkyBlue => KnownColor.SkyBlue.ToBrush(); + public static IImmutableSolidColorBrush SkyBlue => KnownColor.SkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SlateBlue => KnownColor.SlateBlue.ToBrush(); + public static IImmutableSolidColorBrush SlateBlue => KnownColor.SlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SlateGray => KnownColor.SlateGray.ToBrush(); + public static IImmutableSolidColorBrush SlateGray => KnownColor.SlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Snow => KnownColor.Snow.ToBrush(); + public static IImmutableSolidColorBrush Snow => KnownColor.Snow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SpringGreen => KnownColor.SpringGreen.ToBrush(); + public static IImmutableSolidColorBrush SpringGreen => KnownColor.SpringGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SteelBlue => KnownColor.SteelBlue.ToBrush(); + public static IImmutableSolidColorBrush SteelBlue => KnownColor.SteelBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Tan => KnownColor.Tan.ToBrush(); + public static IImmutableSolidColorBrush Tan => KnownColor.Tan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Teal => KnownColor.Teal.ToBrush(); + public static IImmutableSolidColorBrush Teal => KnownColor.Teal.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Thistle => KnownColor.Thistle.ToBrush(); + public static IImmutableSolidColorBrush Thistle => KnownColor.Thistle.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Tomato => KnownColor.Tomato.ToBrush(); + public static IImmutableSolidColorBrush Tomato => KnownColor.Tomato.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Transparent => KnownColor.Transparent.ToBrush(); + public static IImmutableSolidColorBrush Transparent => KnownColor.Transparent.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Turquoise => KnownColor.Turquoise.ToBrush(); + public static IImmutableSolidColorBrush Turquoise => KnownColor.Turquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Violet => KnownColor.Violet.ToBrush(); + public static IImmutableSolidColorBrush Violet => KnownColor.Violet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Wheat => KnownColor.Wheat.ToBrush(); + public static IImmutableSolidColorBrush Wheat => KnownColor.Wheat.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush White => KnownColor.White.ToBrush(); + public static IImmutableSolidColorBrush White => KnownColor.White.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush WhiteSmoke => KnownColor.WhiteSmoke.ToBrush(); + public static IImmutableSolidColorBrush WhiteSmoke => KnownColor.WhiteSmoke.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Yellow => KnownColor.Yellow.ToBrush(); + public static IImmutableSolidColorBrush Yellow => KnownColor.Yellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush YellowGreen => KnownColor.YellowGreen.ToBrush(); + public static IImmutableSolidColorBrush YellowGreen => KnownColor.YellowGreen.ToBrush(); } } diff --git a/src/Avalonia.Base/Media/ConicGradientBrush.cs b/src/Avalonia.Base/Media/ConicGradientBrush.cs index 4b50019ddc..bce7a5af19 100644 --- a/src/Avalonia.Base/Media/ConicGradientBrush.cs +++ b/src/Avalonia.Base/Media/ConicGradientBrush.cs @@ -47,7 +47,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public override IImmutableBrush ToImmutable() { return new ImmutableConicGradientBrush(this); } diff --git a/src/Avalonia.Base/Media/DashStyle.cs b/src/Avalonia.Base/Media/DashStyle.cs index 3a30b2d32f..9c30b6f872 100644 --- a/src/Avalonia.Base/Media/DashStyle.cs +++ b/src/Avalonia.Base/Media/DashStyle.cs @@ -17,8 +17,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty> DashesProperty = - AvaloniaProperty.Register>(nameof(Dashes)); + public static readonly StyledProperty?> DashesProperty = + AvaloniaProperty.Register?>(nameof(Dashes)); /// /// Defines the property. @@ -83,7 +83,7 @@ namespace Avalonia.Media /// /// Gets or sets the length of alternating dashes and gaps. /// - public AvaloniaList Dashes + public AvaloniaList? Dashes { get => GetValue(DashesProperty); set => SetValue(DashesProperty, value); @@ -98,7 +98,7 @@ namespace Avalonia.Media set => SetValue(OffsetProperty, value); } - IReadOnlyList IDashStyle.Dashes => Dashes; + IReadOnlyList? IDashStyle.Dashes => Dashes; /// /// Raised when the dash style changes. diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index d0f3b9e21a..eabd7c8274 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -261,7 +261,7 @@ namespace Avalonia.Media DrawRectangle(brush, null, rect, cornerRadius, cornerRadius); } - public readonly struct PushedState : IDisposable + public readonly record struct PushedState : IDisposable { private readonly int _level; private readonly DrawingContext _context; diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index e71f568207..4a46d89153 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -461,9 +461,10 @@ namespace Avalonia.Media if (_rootDrawing == null) { - // When a DrawingGroup is set, it should be made the root if - // a root drawing didnt exist. - Contract.Requires(_currentDrawingGroup == null); + if (_currentDrawingGroup != null) + { + throw new NotSupportedException("When a DrawingGroup is set, it should be made the root if a root drawing didnt exist."); + } // If this is the first Drawing being added, avoid creating a DrawingGroup // and set this drawing as the root drawing. This optimizes the common diff --git a/src/Avalonia.Base/Media/FontMetrics.cs b/src/Avalonia.Base/Media/FontMetrics.cs index 1cd01675db..6d952a6b93 100644 --- a/src/Avalonia.Base/Media/FontMetrics.cs +++ b/src/Avalonia.Base/Media/FontMetrics.cs @@ -3,7 +3,7 @@ /// /// The font metrics is holding information about a font's ascent, descent, etc. in design em units. /// - public readonly struct FontMetrics + public readonly record struct FontMetrics { /// /// Gets the font design units per em. diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs index 138e8b79eb..774580415a 100644 --- a/src/Avalonia.Base/Media/FormattedText.cs +++ b/src/Avalonia.Base/Media/FormattedText.cs @@ -1379,7 +1379,7 @@ namespace Avalonia.Media } } - if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsEmpty) + if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsDefault) { return null; } diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index c31a6699c2..2019f54c70 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -30,7 +30,7 @@ namespace Avalonia.Media /// /// Gets the geometry's bounding rectangle. /// - public Rect Bounds => PlatformImpl?.Bounds ?? Rect.Empty; + public Rect Bounds => PlatformImpl?.Bounds ?? default; /// /// Gets the platform-specific implementation of the geometry. @@ -84,7 +84,7 @@ namespace Avalonia.Media /// /// The stroke thickness. /// The bounding rectangle. - public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Empty; + public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? default; /// /// Indicates whether the geometry's fill contains the specified point. diff --git a/src/Avalonia.Base/Media/GeometryDrawing.cs b/src/Avalonia.Base/Media/GeometryDrawing.cs index 7df7d25954..26cc2c3cab 100644 --- a/src/Avalonia.Base/Media/GeometryDrawing.cs +++ b/src/Avalonia.Base/Media/GeometryDrawing.cs @@ -69,7 +69,7 @@ namespace Avalonia.Media public override Rect GetBounds() { IPen pen = Pen ?? s_boundsPen; - return Geometry?.GetRenderBounds(pen) ?? Rect.Empty; + return Geometry?.GetRenderBounds(pen) ?? default; } } } diff --git a/src/Avalonia.Base/Media/GlyphMetrics.cs b/src/Avalonia.Base/Media/GlyphMetrics.cs index 2ee1f87d38..e9b5a112ac 100644 --- a/src/Avalonia.Base/Media/GlyphMetrics.cs +++ b/src/Avalonia.Base/Media/GlyphMetrics.cs @@ -1,6 +1,6 @@ namespace Avalonia.Media; -public readonly struct GlyphMetrics +public readonly record struct GlyphMetrics { /// /// Distance from the x-origin to the left extremum of the glyph. diff --git a/src/Avalonia.Base/Media/GlyphRunDrawing.cs b/src/Avalonia.Base/Media/GlyphRunDrawing.cs index 7e0d5c3c81..242b9913fa 100644 --- a/src/Avalonia.Base/Media/GlyphRunDrawing.cs +++ b/src/Avalonia.Base/Media/GlyphRunDrawing.cs @@ -32,7 +32,7 @@ public override Rect GetBounds() { - return GlyphRun != null ? new Rect(GlyphRun.Size) : Rect.Empty; + return GlyphRun != null ? new Rect(GlyphRun.Size) : default; } } } diff --git a/src/Avalonia.Base/Media/GlyphRunMetrics.cs b/src/Avalonia.Base/Media/GlyphRunMetrics.cs index 983f029c7a..492b5214cd 100644 --- a/src/Avalonia.Base/Media/GlyphRunMetrics.cs +++ b/src/Avalonia.Base/Media/GlyphRunMetrics.cs @@ -1,6 +1,6 @@ namespace Avalonia.Media { - public readonly struct GlyphRunMetrics + public readonly record struct GlyphRunMetrics { public GlyphRunMetrics(double width, double widthIncludingTrailingWhitespace, double height, int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster) diff --git a/src/Avalonia.Base/Media/GradientBrush.cs b/src/Avalonia.Base/Media/GradientBrush.cs index c84413ecbb..83cdaa1694 100644 --- a/src/Avalonia.Base/Media/GradientBrush.cs +++ b/src/Avalonia.Base/Media/GradientBrush.cs @@ -12,7 +12,7 @@ namespace Avalonia.Media /// /// Base class for brushes that draw with a gradient. /// - public abstract class GradientBrush : Brush, IGradientBrush + public abstract class GradientBrush : Brush, IGradientBrush, IMutableBrush { /// /// Defines the property. @@ -92,5 +92,7 @@ namespace Avalonia.Media { RaiseInvalidated(EventArgs.Empty); } + + public abstract IImmutableBrush ToImmutable(); } } diff --git a/src/Avalonia.Base/Media/IDashStyle.cs b/src/Avalonia.Base/Media/IDashStyle.cs index 7208216603..b988ad210a 100644 --- a/src/Avalonia.Base/Media/IDashStyle.cs +++ b/src/Avalonia.Base/Media/IDashStyle.cs @@ -12,7 +12,7 @@ namespace Avalonia.Media /// /// Gets or sets the length of alternating dashes and gaps. /// - IReadOnlyList Dashes { get; } + IReadOnlyList? Dashes { get; } /// /// Gets or sets how far in the dash sequence the stroke will start. diff --git a/src/Avalonia.Base/Media/IImmutableBrush.cs b/src/Avalonia.Base/Media/IImmutableBrush.cs new file mode 100644 index 0000000000..5781b0117a --- /dev/null +++ b/src/Avalonia.Base/Media/IImmutableBrush.cs @@ -0,0 +1,9 @@ +namespace Avalonia.Media; + +/// +/// Represents an immutable brush which can be safely used with various threading contexts +/// +public interface IImmutableBrush : IBrush +{ + +} \ No newline at end of file diff --git a/src/Avalonia.Base/Media/IMutableBrush.cs b/src/Avalonia.Base/Media/IMutableBrush.cs index fef124ba36..f128aba4df 100644 --- a/src/Avalonia.Base/Media/IMutableBrush.cs +++ b/src/Avalonia.Base/Media/IMutableBrush.cs @@ -7,12 +7,12 @@ namespace Avalonia.Media /// Represents a mutable brush which can return an immutable clone of itself. /// [NotClientImplementable] - public interface IMutableBrush : IBrush, IAffectsRender + internal interface IMutableBrush : IBrush, IAffectsRender { /// /// Creates an immutable clone of the brush. /// /// The immutable clone. - IBrush ToImmutable(); + internal IImmutableBrush ToImmutable(); } } diff --git a/src/Avalonia.Base/Media/ISolidColorBrush.cs b/src/Avalonia.Base/Media/ISolidColorBrush.cs index 29e11210f1..f58768ef09 100644 --- a/src/Avalonia.Base/Media/ISolidColorBrush.cs +++ b/src/Avalonia.Base/Media/ISolidColorBrush.cs @@ -13,4 +13,13 @@ namespace Avalonia.Media /// Color Color { get; } } + + /// + /// Fills an area with a solid color. + /// + [NotClientImplementable] + public interface IImmutableSolidColorBrush : ISolidColorBrush, IImmutableBrush + { + + } } diff --git a/src/Avalonia.Base/Media/ImageBrush.cs b/src/Avalonia.Base/Media/ImageBrush.cs index 19dcf00901..2f2a0fb627 100644 --- a/src/Avalonia.Base/Media/ImageBrush.cs +++ b/src/Avalonia.Base/Media/ImageBrush.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media /// /// Paints an area with an . /// - public class ImageBrush : TileBrush, IImageBrush + public class ImageBrush : TileBrush, IImageBrush, IMutableBrush { /// /// Defines the property. @@ -45,7 +45,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public IImmutableBrush ToImmutable() { return new ImmutableImageBrush(this); } diff --git a/src/Avalonia.Base/Media/ImageDrawing.cs b/src/Avalonia.Base/Media/ImageDrawing.cs index 82f97b52b4..d3e5c4841b 100644 --- a/src/Avalonia.Base/Media/ImageDrawing.cs +++ b/src/Avalonia.Base/Media/ImageDrawing.cs @@ -42,7 +42,7 @@ namespace Avalonia.Media var imageSource = ImageSource; var rect = Rect; - if (imageSource is object && !rect.IsEmpty) + if (imageSource is object && !rect.IsDefault) { context.DrawImage(imageSource, rect); } diff --git a/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs index 70f9fbf567..525a543b70 100644 --- a/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs @@ -78,8 +78,8 @@ namespace Avalonia.Media.Imaging get { if (Source is not IBitmap bmp) - return Size.Empty; - if (SourceRect.IsEmpty) + return default; + if (SourceRect.IsDefault) return Source.Size; return SourceRect.Size.ToSizeWithDpi(bmp.Dpi); } diff --git a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs index d56711ad68..88e5e627ee 100644 --- a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs @@ -60,5 +60,7 @@ namespace Avalonia.Media.Imaging /// public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer? vbr) => PlatformImpl.Item.CreateDrawingContext(vbr); + + bool IRenderTarget.IsCorrupted => false; } } diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs new file mode 100644 index 0000000000..1e1a73437d --- /dev/null +++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; +using Avalonia.Threading; +using Avalonia.Utilities; +using Avalonia.Media.Imaging; +using Avalonia.Media.Immutable; + +namespace Avalonia.Media +{ + public sealed class ImmediateDrawingContext : IDisposable, IOptionalFeatureProvider + { + private readonly bool _ownsImpl; + private int _currentLevel; + + private static ThreadSafeObjectPool> StateStackPool { get; } = + ThreadSafeObjectPool>.Default; + + private static ThreadSafeObjectPool> TransformStackPool { get; } = + ThreadSafeObjectPool>.Default; + + private Stack? _states = StateStackPool.Get(); + + private Stack? _transformContainers = TransformStackPool.Get(); + + readonly struct TransformContainer + { + public readonly Matrix LocalTransform; + public readonly Matrix ContainerTransform; + + public TransformContainer(Matrix localTransform, Matrix containerTransform) + { + LocalTransform = localTransform; + ContainerTransform = containerTransform; + } + } + + internal ImmediateDrawingContext(IDrawingContextImpl impl, bool ownsImpl) + { + _ownsImpl = ownsImpl; + PlatformImpl = impl; + _currentContainerTransform = impl.Transform; + } + + public IDrawingContextImpl PlatformImpl { get; } + + private Matrix _currentTransform = Matrix.Identity; + + private Matrix _currentContainerTransform; + + /// + /// Gets the current transform of the drawing context. + /// + public Matrix CurrentTransform + { + get { return _currentTransform; } + private set + { + _currentTransform = value; + var transform = _currentTransform * _currentContainerTransform; + PlatformImpl.Transform = transform; + } + } + + /// + /// Draws an bitmap. + /// + /// The bitmap. + /// The rect in the output to draw to. + public void DrawBitmap(IBitmap source, Rect rect) + { + _ = source ?? throw new ArgumentNullException(nameof(source)); + DrawBitmap(source, new Rect(source.Size), rect); + } + + /// + /// Draws an image. + /// + /// The bitmap. + /// The rect in the image to draw. + /// The rect in the output to draw to. + /// The bitmap interpolation mode. + public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default) + { + _ = source ?? throw new ArgumentNullException(nameof(source)); + PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect, bitmapInterpolationMode); + } + + /// + /// Draws a line. + /// + /// The stroke pen. + /// The first point of the line. + /// The second point of the line. + public void DrawLine(ImmutablePen pen, Point p1, Point p2) + { + if (PenIsVisible(pen)) + { + PlatformImpl.DrawLine(pen, p1, p2); + } + } + + /// + /// Draws a rectangle with the specified Brush and Pen. + /// + /// The brush used to fill the rectangle, or null for no fill. + /// The pen used to stroke the rectangle, or null for no stroke. + /// The rectangle bounds. + /// The radius in the X dimension of the rounded corners. + /// This value will be clamped to the range of 0 to Width/2 + /// + /// The radius in the Y dimension of the rounded corners. + /// This value will be clamped to the range of 0 to Height/2 + /// + /// Box shadow effect parameters + /// + /// The brush and the pen can both be null. If the brush is null, then no fill is performed. + /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. + /// + public void DrawRectangle(IImmutableBrush? brush, ImmutablePen? pen, Rect rect, double radiusX = 0, double radiusY = 0, + BoxShadows boxShadows = default) + { + if (brush == null && !PenIsVisible(pen)) + { + return; + } + + if (!MathUtilities.IsZero(radiusX)) + { + radiusX = Math.Min(radiusX, rect.Width / 2); + } + + if (!MathUtilities.IsZero(radiusY)) + { + radiusY = Math.Min(radiusY, rect.Height / 2); + } + + PlatformImpl.DrawRectangle(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadows); + } + + /// + /// Draws the outline of a rectangle. + /// + /// The pen. + /// The rectangle bounds. + /// The corner radius. + public void DrawRectangle(ImmutablePen pen, Rect rect, float cornerRadius = 0.0f) + { + DrawRectangle(null, pen, rect, cornerRadius, cornerRadius); + } + + /// + /// Draws an ellipse with the specified Brush and Pen. + /// + /// The brush used to fill the ellipse, or null for no fill. + /// The pen used to stroke the ellipse, or null for no stroke. + /// The location of the center of the ellipse. + /// The horizontal radius of the ellipse. + /// The vertical radius of the ellipse. + /// + /// The brush and the pen can both be null. If the brush is null, then no fill is performed. + /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. + /// + public void DrawEllipse(IImmutableBrush? brush, ImmutablePen? pen, Point center, double radiusX, double radiusY) + { + if (brush == null && !PenIsVisible(pen)) + { + return; + } + + var originX = center.X - radiusX; + var originY = center.Y - radiusY; + var width = radiusX * 2; + var height = radiusY * 2; + + PlatformImpl.DrawEllipse(brush, pen, new Rect(originX, originY, width, height)); + } + + /// + /// Draws a glyph run. + /// + /// The foreground brush. + /// The glyph run. + public void DrawGlyphRun(IImmutableBrush foreground, GlyphRun glyphRun) + { + _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); + + PlatformImpl.DrawGlyphRun(foreground, glyphRun); + } + + /// + /// Draws a filled rectangle. + /// + /// The brush. + /// The rectangle bounds. + /// The corner radius. + public void FillRectangle(IImmutableBrush brush, Rect rect, float cornerRadius = 0.0f) + { + DrawRectangle(brush, null, rect, cornerRadius, cornerRadius); + } + + public readonly record struct PushedState : IDisposable + { + private readonly int _level; + private readonly ImmediateDrawingContext _context; + private readonly Matrix _matrix; + private readonly PushedStateType _type; + + public enum PushedStateType + { + None, + Matrix, + Opacity, + Clip, + MatrixContainer, + GeometryClip, + OpacityMask, + } + + internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) + { + if (context._states is null) + throw new ObjectDisposedException(nameof(ImmediateDrawingContext)); + + _context = context; + _type = type; + _matrix = matrix; + _level = context._currentLevel += 1; + context._states.Push(this); + } + + public void Dispose() + { + if (_type == PushedStateType.None) + return; + if (_context._states is null || _context._transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); + if (_context._currentLevel != _level) + throw new InvalidOperationException("Wrong Push/Pop state order"); + _context._currentLevel--; + _context._states.Pop(); + if (_type == PushedStateType.Matrix) + _context.CurrentTransform = _matrix; + else if (_type == PushedStateType.Clip) + _context.PlatformImpl.PopClip(); + else if (_type == PushedStateType.Opacity) + _context.PlatformImpl.PopOpacity(); + else if (_type == PushedStateType.GeometryClip) + _context.PlatformImpl.PopGeometryClip(); + else if (_type == PushedStateType.OpacityMask) + _context.PlatformImpl.PopOpacityMask(); + else if (_type == PushedStateType.MatrixContainer) + { + var cont = _context._transformContainers.Pop(); + _context._currentContainerTransform = cont.ContainerTransform; + _context.CurrentTransform = cont.LocalTransform; + } + } + } + + + public PushedState PushClip(RoundedRect clip) + { + PlatformImpl.PushClip(clip); + return new PushedState(this, PushedState.PushedStateType.Clip); + } + + /// + /// Pushes a clip rectangle. + /// + /// The clip rectangle. + /// A disposable used to undo the clip rectangle. + public PushedState PushClip(Rect clip) + { + PlatformImpl.PushClip(clip); + return new PushedState(this, PushedState.PushedStateType.Clip); + } + + /// + /// Pushes an opacity value. + /// + /// The opacity. + /// A disposable used to undo the opacity. + public PushedState PushOpacity(double opacity) + //TODO: Eliminate platform-specific push opacity call + { + PlatformImpl.PushOpacity(opacity); + return new PushedState(this, PushedState.PushedStateType.Opacity); + } + + /// + /// Pushes an opacity mask. + /// + /// The opacity mask. + /// + /// The size of the brush's target area. TODO: Are we sure this is needed? + /// + /// A disposable to undo the opacity mask. + public PushedState PushOpacityMask(IImmutableBrush mask, Rect bounds) + { + PlatformImpl.PushOpacityMask(mask, bounds); + return new PushedState(this, PushedState.PushedStateType.OpacityMask); + } + + /// + /// Pushes a matrix post-transformation. + /// + /// The matrix + /// A disposable used to undo the transformation. + public PushedState PushPostTransform(Matrix matrix) => PushSetTransform(CurrentTransform * matrix); + + /// + /// Pushes a matrix pre-transformation. + /// + /// The matrix + /// A disposable used to undo the transformation. + public PushedState PushPreTransform(Matrix matrix) => PushSetTransform(matrix * CurrentTransform); + + /// + /// Sets the current matrix transformation. + /// + /// The matrix + /// A disposable used to undo the transformation. + public PushedState PushSetTransform(Matrix matrix) + { + var oldMatrix = CurrentTransform; + CurrentTransform = matrix; + + return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix); + } + + /// + /// Pushes a new transform context. + /// + /// A disposable used to undo the transformation. + public PushedState PushTransformContainer() + { + if (_transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); + _transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform)); + _currentContainerTransform = CurrentTransform * _currentContainerTransform; + _currentTransform = Matrix.Identity; + return new PushedState(this, PushedState.PushedStateType.MatrixContainer); + } + + /// + /// Disposes of any resources held by the . + /// + public void Dispose() + { + if (_states is null || _transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); + while (_states.Count != 0) + _states.Peek().Dispose(); + StateStackPool.Return(_states); + _states = null; + if (_transformContainers.Count != 0) + throw new InvalidOperationException("Transform container stack is non-empty"); + TransformStackPool.Return(_transformContainers); + _transformContainers = null; + if (_ownsImpl) + PlatformImpl.Dispose(); + } + + private static bool PenIsVisible(IPen? pen) + { + return pen?.Brush != null && pen.Thickness > 0; + } + + public object? TryGetFeature(Type type) => PlatformImpl.GetFeature(type); + } +} diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs index 82485c13b0..1f53f06955 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs @@ -17,7 +17,7 @@ namespace Avalonia.Media.Immutable /// /// The dashes collection. /// The dash sequence offset. - public ImmutableDashStyle(IEnumerable dashes, double offset) + public ImmutableDashStyle(IEnumerable? dashes, double offset) { _dashes = dashes?.ToArray() ?? Array.Empty(); Offset = offset; @@ -69,7 +69,7 @@ namespace Avalonia.Media.Immutable return hashCode; } - private static bool SequenceEqual(IReadOnlyList left, IReadOnlyList right) + private static bool SequenceEqual(IReadOnlyList left, IReadOnlyList? right) { if (ReferenceEquals(left, right)) { diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs index 1e95acbf22..c86d86d20a 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable /// /// A brush that draws with a gradient. /// - public abstract class ImmutableGradientBrush : IGradientBrush + public abstract class ImmutableGradientBrush : IGradientBrush, IImmutableBrush { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Media/Immutable/ImmutablePen.cs b/src/Avalonia.Base/Media/Immutable/ImmutablePen.cs index 8a53eaf7b8..ef4468fccf 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutablePen.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutablePen.cs @@ -38,15 +38,13 @@ namespace Avalonia.Media.Immutable /// The line join. /// The miter limit. public ImmutablePen( - IBrush? brush, + IImmutableBrush? brush, double thickness = 1.0, ImmutableDashStyle? dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) { - Debug.Assert(!(brush is IMutableBrush)); - Brush = brush; Thickness = thickness; LineCap = lineCap; diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs index 6755dfd236..4e623f02c5 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable /// /// Fills an area with a solid color. /// - public class ImmutableSolidColorBrush : ISolidColorBrush, IEquatable + public class ImmutableSolidColorBrush : IImmutableSolidColorBrush, IEquatable { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs index 6df27872fc..1ee52365e0 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable /// /// A brush which displays a repeating image. /// - public abstract class ImmutableTileBrush : ITileBrush + public abstract class ImmutableTileBrush : ITileBrush, IImmutableBrush { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Media/KnownColors.cs b/src/Avalonia.Base/Media/KnownColors.cs index ae2fca5a60..64cf1ef2f1 100644 --- a/src/Avalonia.Base/Media/KnownColors.cs +++ b/src/Avalonia.Base/Media/KnownColors.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media private static readonly IReadOnlyDictionary _knownColorNames; private static readonly IReadOnlyDictionary _knownColors; #if !BUILDTASK - private static readonly Dictionary _knownBrushes; + private static readonly Dictionary _knownBrushes; #endif [GenerateEnumValueDictionary()] @@ -39,7 +39,7 @@ namespace Avalonia.Media _knownColors = knownColors; #if !BUILDTASK - _knownBrushes = new Dictionary(); + _knownBrushes = new (); #endif } @@ -72,7 +72,7 @@ namespace Avalonia.Media } #if !BUILDTASK - public static ISolidColorBrush ToBrush(this KnownColor color) + public static IImmutableSolidColorBrush ToBrush(this KnownColor color) { lock (_knownBrushes) { diff --git a/src/Avalonia.Base/Media/LinearGradientBrush.cs b/src/Avalonia.Base/Media/LinearGradientBrush.cs index a51adf0949..9b9ce5e4b7 100644 --- a/src/Avalonia.Base/Media/LinearGradientBrush.cs +++ b/src/Avalonia.Base/Media/LinearGradientBrush.cs @@ -47,7 +47,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public override IImmutableBrush ToImmutable() { return new ImmutableLinearGradientBrush(this); } diff --git a/src/Avalonia.Base/Media/RadialGradientBrush.cs b/src/Avalonia.Base/Media/RadialGradientBrush.cs index 16c367c0d0..f803051676 100644 --- a/src/Avalonia.Base/Media/RadialGradientBrush.cs +++ b/src/Avalonia.Base/Media/RadialGradientBrush.cs @@ -67,7 +67,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public override IImmutableBrush ToImmutable() { return new ImmutableRadialGradientBrush(this); } diff --git a/src/Avalonia.Base/Media/SolidColorBrush.cs b/src/Avalonia.Base/Media/SolidColorBrush.cs index 962819a1a1..d8e25fe748 100644 --- a/src/Avalonia.Base/Media/SolidColorBrush.cs +++ b/src/Avalonia.Base/Media/SolidColorBrush.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media /// /// Fills an area with a solid color. /// - public class SolidColorBrush : Brush, ISolidColorBrush + public class SolidColorBrush : Brush, ISolidColorBrush, IMutableBrush { /// /// Defines the property. @@ -80,7 +80,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public IImmutableBrush ToImmutable() { return new ImmutableSolidColorBrush(this); } diff --git a/src/Avalonia.Base/Media/TextCollapsingCreateInfo.cs b/src/Avalonia.Base/Media/TextCollapsingCreateInfo.cs index 78f15b724a..40ba613717 100644 --- a/src/Avalonia.Base/Media/TextCollapsingCreateInfo.cs +++ b/src/Avalonia.Base/Media/TextCollapsingCreateInfo.cs @@ -2,7 +2,7 @@ namespace Avalonia.Media { - public readonly struct TextCollapsingCreateInfo + public readonly record struct TextCollapsingCreateInfo { public readonly double Width; public readonly TextRunProperties TextRunProperties; diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs index e745a873a2..4472ba87eb 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs @@ -8,7 +8,6 @@ namespace Avalonia.Media.TextFormatting internal readonly struct FormattedTextSource : ITextSource { private readonly CharacterBufferRange _text; - private readonly int length; private readonly TextRunProperties _defaultProperties; private readonly IReadOnlyList>? _textModifier; diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 644c0ecbe1..902b897240 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -262,7 +262,7 @@ namespace Avalonia.Media.TextFormatting } } - public readonly struct GlyphInfo + public readonly record struct GlyphInfo { public GlyphInfo(ushort glyphIndex, int glyphCluster, double glyphAdvance = 0, Vector glyphOffset = default) { diff --git a/src/Avalonia.Base/Media/TextFormatting/SplitResult.cs b/src/Avalonia.Base/Media/TextFormatting/SplitResult.cs index 03b93cfaf0..53021c4656 100644 --- a/src/Avalonia.Base/Media/TextFormatting/SplitResult.cs +++ b/src/Avalonia.Base/Media/TextFormatting/SplitResult.cs @@ -1,6 +1,8 @@ namespace Avalonia.Media.TextFormatting { +#pragma warning disable CA1815 // Override equals and operator equals on value types public readonly struct SplitResult +#pragma warning restore CA1815 // Override equals and operator equals on value types { public SplitResult(T first, T? second) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 48f5fec2c0..55c88d0077 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -576,7 +576,7 @@ namespace Avalonia.Media.TextFormatting var startX = Start; double currentWidth = 0; - var currentRect = Rect.Empty; + var currentRect = default(Rect); TextRunBounds lastRunBounds = default; @@ -792,7 +792,7 @@ namespace Avalonia.Media.TextFormatting var startX = WidthIncludingTrailingWhitespace; double currentWidth = 0; - var currentRect = Rect.Empty; + var currentRect = default(Rect); for (var index = TextRuns.Count - 1; index >= 0; index--) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineMetrics.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineMetrics.cs index 40a7f6167a..cb21e27696 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineMetrics.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineMetrics.cs @@ -4,7 +4,7 @@ /// Represents a metric for a objects, /// that holds information about ascent, descent, line gap, size and origin of the text line. /// - public readonly struct TextLineMetrics + public readonly record struct TextLineMetrics { public TextLineMetrics(bool hasOverflowed, double height, int newlineLength, double start, double textBaseline, int trailingWhitespaceLength, double width, diff --git a/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs index dc21c9b6f2..c83ae89320 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs @@ -3,7 +3,7 @@ /// /// A metric that holds information about text specific measurements. /// - public readonly struct TextMetrics + public readonly record struct TextMetrics { public TextMetrics(IGlyphTypeface glyphTypeface, double fontRenderingEmSize) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRange.cs b/src/Avalonia.Base/Media/TextFormatting/TextRange.cs index 1177c758f4..e8bab55aff 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextRange.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextRange.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.TextFormatting /// /// References a portion of a text buffer. /// - public readonly struct TextRange + public readonly record struct TextRange { public TextRange(int start, int length) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs b/src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs index bdc7a1ca89..707c9048dc 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextRunBounds.cs @@ -3,7 +3,7 @@ /// /// The bounding rectangle of text run /// - public readonly struct TextRunBounds + public readonly record struct TextRunBounds { /// /// Constructing TextRunBounds diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs index 80bbbcdbfe..610fc3dbc9 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.TextFormatting /// /// Options to customize text shaping. /// - public readonly struct TextShaperOptions + public readonly record struct TextShaperOptions { public TextShaperOptions( IGlyphTypeface typeface, diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index 9e5186552e..e6408bcfa6 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; namespace Avalonia.Media.TextFormatting.Unicode { - public readonly struct Codepoint + public readonly record struct Codepoint { private readonly uint _value; diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs index 330ead476a..a2c36d9a13 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs @@ -5,7 +5,6 @@ namespace Avalonia.Media.TextFormatting.Unicode public ref struct CodepointEnumerator { private CharacterBufferRange _text; - private int _pos; public CodepointEnumerator(CharacterBufferRange text) { diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs index 59c4df0a2e..0e470341f3 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs @@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Information about a potential line break position /// [DebuggerDisplay("{PositionMeasure}/{PositionWrap} @ {Required}")] - public readonly struct LineBreak + public readonly record struct LineBreak { /// /// Constructor diff --git a/src/Avalonia.Base/Media/TextHitTestResult.cs b/src/Avalonia.Base/Media/TextHitTestResult.cs index c8922f06c8..d19adc2807 100644 --- a/src/Avalonia.Base/Media/TextHitTestResult.cs +++ b/src/Avalonia.Base/Media/TextHitTestResult.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media /// /// Holds a hit test result from a . /// - public readonly struct TextHitTestResult + public readonly record struct TextHitTestResult { public TextHitTestResult(CharacterHit characterHit, int textPosition, bool isInside, bool isTrailing) { diff --git a/src/Avalonia.Base/Media/Transformation/TransformOperation.cs b/src/Avalonia.Base/Media/Transformation/TransformOperation.cs index a8c0fe9b12..542d71963d 100644 --- a/src/Avalonia.Base/Media/Transformation/TransformOperation.cs +++ b/src/Avalonia.Base/Media/Transformation/TransformOperation.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Transformation /// /// Represents a single primitive transform (like translation, rotation, scale, etc.). /// - public struct TransformOperation + public record struct TransformOperation { public OperationType Type; public Matrix Matrix; @@ -196,7 +196,7 @@ namespace Avalonia.Media.Transformation } [StructLayout(LayoutKind.Explicit)] - public struct DataLayout + public record struct DataLayout { [FieldOffset(0)] public SkewLayout Skew; @@ -206,25 +206,25 @@ namespace Avalonia.Media.Transformation [FieldOffset(0)] public RotateLayout Rotate; - public struct SkewLayout + public record struct SkewLayout { public double X; public double Y; } - public struct ScaleLayout + public record struct ScaleLayout { public double X; public double Y; } - public struct TranslateLayout + public record struct TranslateLayout { public double X; public double Y; } - public struct RotateLayout + public record struct RotateLayout { public double Angle; } diff --git a/src/Avalonia.Base/Media/Transformation/TransformOperations.cs b/src/Avalonia.Base/Media/Transformation/TransformOperations.cs index 334bb93562..46ca41386a 100644 --- a/src/Avalonia.Base/Media/Transformation/TransformOperations.cs +++ b/src/Avalonia.Base/Media/Transformation/TransformOperations.cs @@ -165,7 +165,7 @@ namespace Avalonia.Media.Transformation return Math.Max(from._operations.Count, to._operations.Count); } - public readonly struct Builder + public readonly record struct Builder { private readonly List _operations; diff --git a/src/Avalonia.Base/Media/Typeface.cs b/src/Avalonia.Base/Media/Typeface.cs index e6047bf96c..1e744c30c8 100644 --- a/src/Avalonia.Base/Media/Typeface.cs +++ b/src/Avalonia.Base/Media/Typeface.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using JetBrains.Annotations; namespace Avalonia.Media { @@ -17,7 +16,7 @@ namespace Avalonia.Media /// The font style. /// The font weight. /// The font stretch. - public Typeface([NotNull] FontFamily fontFamily, + public Typeface(FontFamily fontFamily, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal, FontStretch stretch = FontStretch.Normal) diff --git a/src/Avalonia.Base/Media/UnicodeRange.cs b/src/Avalonia.Base/Media/UnicodeRange.cs index 344b85bae9..4cab7b4a14 100644 --- a/src/Avalonia.Base/Media/UnicodeRange.cs +++ b/src/Avalonia.Base/Media/UnicodeRange.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media /// /// The descripes a set of Unicode characters. /// - public readonly struct UnicodeRange + public readonly record struct UnicodeRange { public readonly static UnicodeRange Default = Parse("0-10FFFD"); @@ -102,9 +102,9 @@ namespace Avalonia.Media } } - public readonly struct UnicodeRangeSegment + public readonly record struct UnicodeRangeSegment { - private static Regex s_regex = new Regex(@"^(?:[uU]\+)?(?:([0-9a-fA-F](?:[0-9a-fA-F?]{1,5})?))$"); + private static Regex s_regex = new Regex(@"^(?:[uU]\+)?(?:([0-9a-fA-F](?:[0-9a-fA-F?]{1,5})?))$", RegexOptions.Compiled); public UnicodeRangeSegment(int start, int end) { diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs index 8dce79cf11..1261d233ac 100644 --- a/src/Avalonia.Base/Media/VisualBrush.cs +++ b/src/Avalonia.Base/Media/VisualBrush.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media /// /// Paints an area with an . /// - public class VisualBrush : TileBrush, IVisualBrush + public class VisualBrush : TileBrush, IVisualBrush, IMutableBrush { /// /// Defines the property. @@ -45,7 +45,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + IImmutableBrush IMutableBrush.ToImmutable() { return new ImmutableVisualBrush(this); } diff --git a/src/Avalonia.Base/PixelRect.cs b/src/Avalonia.Base/PixelRect.cs index 855ba104ad..469f33e7fd 100644 --- a/src/Avalonia.Base/PixelRect.cs +++ b/src/Avalonia.Base/PixelRect.cs @@ -12,6 +12,7 @@ namespace Avalonia /// /// An empty rectangle. /// + [Obsolete("Use the default keyword instead.")] public static readonly PixelRect Empty = default; /// @@ -133,9 +134,13 @@ namespace Avalonia public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2)); /// - /// Gets a value that indicates whether the rectangle is empty. + /// Gets a value indicating whether the instance has default values (the rectangle is empty). /// - public bool IsEmpty => Width == 0 && Height == 0; + public bool IsDefault => Width == 0 && Height == 0; + + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; /// /// Checks for equality between two s. @@ -257,7 +262,7 @@ namespace Avalonia } else { - return Empty; + return default; } } @@ -290,11 +295,11 @@ namespace Avalonia /// The union. public PixelRect Union(PixelRect rect) { - if (IsEmpty) + if (IsDefault) { return rect; } - else if (rect.IsEmpty) + else if (rect.IsDefault) { return this; } diff --git a/src/Avalonia.Base/PixelVector.cs b/src/Avalonia.Base/PixelVector.cs index 79e7b94c21..0e156190a4 100644 --- a/src/Avalonia.Base/PixelVector.cs +++ b/src/Avalonia.Base/PixelVector.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using Avalonia.Animation.Animators; -using JetBrains.Annotations; namespace Avalonia { @@ -135,7 +134,6 @@ namespace Avalonia /// /// The other vector. /// True if vectors are nearly equal. - [Pure] public bool NearlyEquals(PixelVector other) { const float tolerance = float.Epsilon; diff --git a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs new file mode 100644 index 0000000000..b6464dea58 --- /dev/null +++ b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs @@ -0,0 +1,18 @@ +using System; + +namespace Avalonia.Platform; + +public interface IOptionalFeatureProvider +{ + /// + /// Queries for an optional feature + /// + /// Feature type + public object? TryGetFeature(Type featureType); +} + +public static class OptionalFeatureProviderExtensions +{ + public static T? TryGetFeature(this IOptionalFeatureProvider provider) where T : class => + (T?)provider.TryGetFeature(typeof(T)); +} \ No newline at end of file diff --git a/src/Avalonia.Base/Platform/IPlatformGpu.cs b/src/Avalonia.Base/Platform/IPlatformGpu.cs index 0507dea1d7..f6953619d2 100644 --- a/src/Avalonia.Base/Platform/IPlatformGpu.cs +++ b/src/Avalonia.Base/Platform/IPlatformGpu.cs @@ -4,13 +4,21 @@ using Avalonia.Metadata; namespace Avalonia.Platform; [Unstable] -public interface IPlatformGpu +public interface IPlatformGraphics { - IPlatformGpuContext PrimaryContext { get; } + bool UsesSharedContext { get; } + IPlatformGraphicsContext CreateContext(); + IPlatformGraphicsContext GetSharedContext(); } [Unstable] -public interface IPlatformGpuContext : IDisposable +public interface IPlatformGraphicsContext : IDisposable, IOptionalFeatureProvider { + bool IsLost { get; } IDisposable EnsureCurrent(); +} + +public class PlatformGraphicsContextLostException : Exception +{ + } \ No newline at end of file diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 518c5f37b8..1828f24aff 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -65,15 +65,6 @@ namespace Avalonia.Platform /// The geometry returned contains the combined geometry of all glyphs in the glyph run. IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun); - /// - /// Creates a renderer. - /// - /// - /// The list of native platform surfaces that can be used for output. - /// - /// An . - IRenderTarget CreateRenderTarget(IEnumerable surfaces); - /// /// Creates a render target bitmap implementation. /// @@ -181,6 +172,13 @@ namespace Avalonia.Platform /// IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList? glyphAdvances, IReadOnlyList? glyphOffsets); + /// + /// Creates a backend-specific object using a low-level API graphics context + /// + /// An underlying low-level graphics context (e. g. wrapped OpenGL context, Vulkan device, D3DDevice, etc) + /// + IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext? graphicsApiContext); + /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. /// @@ -200,4 +198,17 @@ namespace Avalonia.Platform /// public PixelFormat DefaultPixelFormat { get; } } + + [Unstable] + public interface IPlatformRenderInterfaceContext : IOptionalFeatureProvider, IDisposable + { + /// + /// Creates a renderer. + /// + /// + /// The list of native platform surfaces that can be used for output. + /// + /// An . + IRenderTarget CreateRenderTarget(IEnumerable surfaces); + } } diff --git a/src/Avalonia.Base/Platform/IRenderTarget.cs b/src/Avalonia.Base/Platform/IRenderTarget.cs index 7023f2ca51..73e9e58da4 100644 --- a/src/Avalonia.Base/Platform/IRenderTarget.cs +++ b/src/Avalonia.Base/Platform/IRenderTarget.cs @@ -19,10 +19,10 @@ namespace Avalonia.Platform /// to be drawn. /// IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer? visualBrushRenderer); - } - - public interface IRenderTargetWithCorruptionInfo : IRenderTarget - { - bool IsCorrupted { get; } + + /// + /// Indicates if the render target is no longer usable and needs to be recreated + /// + public bool IsCorrupted { get; } } } diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs index 3f8983479f..91d2a1e0cf 100644 --- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs @@ -21,7 +21,7 @@ namespace Avalonia.Platform } [Unstable] - public struct RuntimePlatformInfo + public record struct RuntimePlatformInfo { public OperatingSystemType OperatingSystem { get; set; } diff --git a/src/Avalonia.Base/Platform/ITextShaperImpl.cs b/src/Avalonia.Base/Platform/ITextShaperImpl.cs index ff91097eda..c3eb89ab1a 100644 --- a/src/Avalonia.Base/Platform/ITextShaperImpl.cs +++ b/src/Avalonia.Base/Platform/ITextShaperImpl.cs @@ -13,6 +13,7 @@ namespace Avalonia.Platform /// Shapes the specified region within the text and returns a shaped buffer. /// /// The text buffer. + /// The length of text. /// Text shaper options to customize the shaping process. /// A shaped glyph run. ShapedBuffer ShapeText(CharacterBufferReference text, int length, TextShaperOptions options); diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index ef14211902..b5ac5c817d 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Reactive.Disposables; using Avalonia.Data; +using Avalonia.Threading; namespace Avalonia.PropertyStore { @@ -116,26 +117,42 @@ namespace Avalonia.PropertyStore private void SetValue(BindingValue value) { - if (Frame.Owner is null) - return; + static void Execute(BindingEntryBase instance, BindingValue value) + { + if (instance.Frame.Owner is null) + return; - LoggingUtils.LogIfNecessary(Frame.Owner.Owner, Property, value); + LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value); - if (value.HasValue) - { - if (!_hasValue || !EqualityComparer.Default.Equals(_value, value.Value)) + if (value.HasValue) + { + if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, value.Value)) + { + instance._value = value.Value; + instance._hasValue = true; + if (instance._subscription is not null && instance._subscription != s_creatingQuiet) + instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority); + } + } + else if (value.Type != BindingValueType.DoNothing) { - _value = value.Value; - _hasValue = true; - if (_subscription is not null && _subscription != s_creatingQuiet) - Frame.Owner?.OnBindingValueChanged(this, Frame.Priority); + instance.ClearValue(); + if (instance._subscription is not null && instance._subscription != s_creatingQuiet) + instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority); } } - else if (value.Type != BindingValueType.DoNothing) + + if (Dispatcher.UIThread.CheckAccess()) { - ClearValue(); - if (_subscription is not null && _subscription != s_creatingQuiet) - Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority); + Execute(this, value); + } + else + { + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. + var instance = this; + var newValue = value; + Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index 4dca6c0100..8acb885604 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Data; +using Avalonia.Threading; namespace Avalonia.PropertyStore { @@ -40,20 +41,56 @@ namespace Avalonia.PropertyStore public void OnNext(T value) { - if (Property.ValidateValue?.Invoke(value) != false) - _owner.SetValue(Property, value, BindingPriority.LocalValue); + static void Execute(ValueStore owner, StyledPropertyBase property, T value) + { + if (property.ValidateValue?.Invoke(value) != false) + owner.SetValue(property, value, BindingPriority.LocalValue); + else + owner.ClearLocalValue(property); + } + + if (Dispatcher.UIThread.CheckAccess()) + { + Execute(_owner, Property, value); + } else - _owner.ClearLocalValue(Property); + { + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. + var instance = _owner; + var property = Property; + var newValue = value; + Dispatcher.UIThread.Post(() => Execute(instance, property, newValue)); + } } public void OnNext(BindingValue value) { - LoggingUtils.LogIfNecessary(_owner.Owner, Property, value); + static void Execute(LocalValueBindingObserver instance, BindingValue value) + { + var owner = instance._owner; + var property = instance.Property; + + LoggingUtils.LogIfNecessary(owner.Owner, property, value); - if (value.HasValue) - _owner.SetValue(Property, value.Value, BindingPriority.LocalValue); - else if (value.Type != BindingValueType.DataValidationError) - _owner.ClearLocalValue(Property); + if (value.HasValue) + owner.SetValue(property, value.Value, BindingPriority.LocalValue); + else if (value.Type != BindingValueType.DataValidationError) + owner.ClearLocalValue(property); + } + + if (Dispatcher.UIThread.CheckAccess()) + { + Execute(this, value); + } + else + { + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. + var instance = this; + var newValue = value; + Dispatcher.UIThread.Post(() => Execute(instance, newValue)); + } } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs index 099e997d38..7c529591b6 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs @@ -1,5 +1,7 @@ using System; +using System.Security.Cryptography; using Avalonia.Data; +using Avalonia.Threading; namespace Avalonia.PropertyStore { @@ -34,28 +36,47 @@ namespace Avalonia.PropertyStore public void OnNext(object? value) { - if (value is BindingNotification n) + static void Execute(LocalValueUntypedBindingObserver instance, object? value) { - value = n.Value; - LoggingUtils.LogIfNecessary(_owner.Owner, Property, n); - } + var owner = instance._owner; + var property = instance.Property; - if (value == AvaloniaProperty.UnsetValue) - { - _owner.ClearLocalValue(Property); - } - else if (value == BindingOperations.DoNothing) - { - // Do nothing! + if (value is BindingNotification n) + { + value = n.Value; + LoggingUtils.LogIfNecessary(owner.Owner, property, n); + } + + if (value == AvaloniaProperty.UnsetValue) + { + owner.ClearLocalValue(property); + } + else if (value == BindingOperations.DoNothing) + { + // Do nothing! + } + else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue)) + { + owner.SetValue(property, typedValue, BindingPriority.LocalValue); + } + else + { + owner.ClearLocalValue(property); + LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value); + } } - else if (UntypedValueUtils.TryConvertAndValidate(Property, value, out var typedValue)) + + if (Dispatcher.UIThread.CheckAccess()) { - _owner.SetValue(Property, typedValue, BindingPriority.LocalValue); + Execute(this, value); } - else + else if (value != BindingOperations.DoNothing) { - _owner.ClearLocalValue(Property); - LoggingUtils.LogInvalidValue(_owner.Owner, Property, typeof(T), value); + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. + var instance = this; + var newValue = value; + Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } } diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index a91b089a33..831ab28adc 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -18,7 +18,8 @@ namespace Avalonia /// /// An empty rectangle. /// - public static readonly Rect Empty = default(Rect); + [Obsolete("Use the default keyword instead.")] + public static readonly Rect Empty = default; /// /// The X position. @@ -169,12 +170,16 @@ namespace Avalonia public Point Center => new Point(_x + (_width / 2), _y + (_height / 2)); /// - /// Gets a value that indicates whether the rectangle is empty. + /// Gets a value indicating whether the instance has default values (the rectangle is empty). /// // ReSharper disable CompareOfFloatsByEqualityOperator - public bool IsEmpty => _width == 0 && _height == 0; + public bool IsDefault => _width == 0 && _height == 0; // ReSharper restore CompareOfFloatsByEqualityOperator + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; + /// /// Checks for equality between two s. /// @@ -390,7 +395,7 @@ namespace Avalonia } else { - return Empty; + return default; } } @@ -457,13 +462,13 @@ namespace Avalonia /// public Rect Normalize() { - Rect rect = this; + Rect rect = this; if(double.IsNaN(rect.Right) || double.IsNaN(rect.Bottom) || double.IsNaN(rect.X) || double.IsNaN(rect.Y) || double.IsNaN(Height) || double.IsNaN(Width)) { - return Rect.Empty; + return default; } if (rect.Width < 0) @@ -493,11 +498,11 @@ namespace Avalonia /// The union. public Rect Union(Rect rect) { - if (IsEmpty) + if (IsDefault) { return rect; } - else if (rect.IsEmpty) + else if (rect.IsDefault) { return this; } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs index 889c1d34c8..80b6bb9d2a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs @@ -80,4 +80,6 @@ internal abstract class AnimationInstanceBase : IAnimationInstance _invalidated = true; TargetObject.NotifyAnimatedValueChanged(Property); } + + public void OnTick() => Invalidate(); } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs index 8ec4ec19bc..b14d2c0b19 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs @@ -6,7 +6,7 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Animations { - internal interface IAnimationInstance + internal interface IAnimationInstance : IServerClockItem { ServerObject TargetObject { get; } ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue); diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index ea8a408e6f..b2080aeb87 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -38,13 +38,12 @@ public class CompositingRenderer : IRendererWithCompositor /// public bool RenderOnlyOnRenderThread { get; set; } = true; - public CompositingRenderer(IRenderRoot root, - Compositor compositor) + public CompositingRenderer(IRenderRoot root, Compositor compositor, Func> surfaces) { _root = root; _compositor = compositor; _recordingContext = new DrawingContext(_recorder); - CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget); + CompositionTarget = compositor.CreateCompositionTarget(surfaces); CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor); _update = Update; } @@ -301,7 +300,9 @@ public class CompositingRenderer : IRendererWithCompositor { CompositionTarget.IsEnabled = false; } - + + public ValueTask TryGetRenderInterfaceFeature(Type featureType) => Compositor.TryGetRenderInterfaceFeature(featureType); + public void Dispose() { Stop(); diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisual.cs new file mode 100644 index 0000000000..f816231781 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisual.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Numerics; +using Avalonia.Rendering.Composition.Server; +using Avalonia.Rendering.Composition.Transport; + +namespace Avalonia.Rendering.Composition; + +public class CompositionCustomVisual : CompositionContainerVisual +{ + private List? _messages; + + internal CompositionCustomVisual(Compositor compositor, CompositionCustomVisualHandler handler) + : base(compositor, new ServerCompositionCustomVisual(compositor.Server, handler)) + { + + } + + public void SendHandlerMessage(object message) + { + (_messages ??= new()).Add(message); + RegisterForSerialization(); + } + + private protected override void SerializeChangesCore(BatchStreamWriter writer) + { + base.SerializeChangesCore(writer); + if (_messages == null || _messages.Count == 0) + writer.Write(0); + else + { + writer.Write(_messages.Count); + foreach (var m in _messages) + writer.WriteObject(m); + _messages.Clear(); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs new file mode 100644 index 0000000000..b7ed6fe612 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs @@ -0,0 +1,65 @@ +using System; +using System.Numerics; +using Avalonia.Media; +using Avalonia.Rendering.Composition.Server; + +namespace Avalonia.Rendering.Composition; + +public abstract class CompositionCustomVisualHandler +{ + private ServerCompositionCustomVisual? _host; + + public virtual void OnMessage(object message) + { + + } + + public virtual void OnAnimationFrameUpdate() + { + + } + + public abstract void OnRender(ImmediateDrawingContext drawingContext); + + void VerifyAccess() + { + if (_host == null) + throw new InvalidOperationException("Object is not yet attached to the compositor"); + _host.Compositor.VerifyAccess(); + } + + protected Vector2 EffectiveSize + { + get + { + VerifyAccess(); + return _host!.Size; + } + } + + protected TimeSpan CompositionNow + { + get + { + VerifyAccess(); + return _host!.Compositor.ServerNow; + } + } + + public virtual Rect GetRenderBounds() => + new(0, 0, EffectiveSize.X, EffectiveSize.Y); + + internal void Attach(ServerCompositionCustomVisual visual) => _host = visual; + + protected void Invalidate() + { + VerifyAccess(); + _host!.HandlerInvalidate(); + } + + protected void RegisterForNextAnimationFrameUpdate() + { + VerifyAccess(); + _host!.HandlerRegisterForNextAnimationFrameUpdate(); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs index 00fa7b3315..2b1b3f461f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.Platform; using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Server; @@ -10,11 +11,11 @@ public partial class Compositor /// /// Creates a new CompositionTarget /// - /// A factory method to create IRenderTarget to be called from the render thread + /// A factory method to create IRenderTarget to be called from the render thread /// - public CompositionTarget CreateCompositionTarget(Func renderTargetFactory) + public CompositionTarget CreateCompositionTarget(Func> surfaces) { - return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory)); + return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces)); } public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server)); @@ -32,4 +33,6 @@ public partial class Compositor public CompositionSolidColorVisual CreateSolidColorVisual() => new(this, new ServerCompositionSolidColorVisual(Server)); + + public CompositionCustomVisual CreateCustomVisual(CompositionCustomVisualHandler handler) => new(this, handler); } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index e6b9600e45..b4817bfe9a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -34,6 +34,7 @@ namespace Avalonia.Rendering.Composition internal ServerCompositor Server => _server; private Task? _pendingBatch; private readonly object _pendingBatchLock = new(); + private List _pendingServerCompositorJobs = new(); internal IEasing DefaultEasing { get; } @@ -43,7 +44,7 @@ namespace Avalonia.Rendering.Composition /// /// /// - public Compositor(IRenderLoop loop, IPlatformGpu? gpu) + public Compositor(IRenderLoop loop, IPlatformGraphics? gpu) { Loop = loop; _server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool); @@ -101,6 +102,13 @@ namespace Avalonia.Rendering.Composition #endif } _objectsForSerialization.Clear(); + if (_pendingServerCompositorJobs.Count > 0) + { + writer.WriteObject(ServerCompositor.RenderThreadJobsStartMarker); + foreach (var job in _pendingServerCompositorJobs) + writer.WriteObject(job); + writer.WriteObject(ServerCompositor.RenderThreadJobsEndMarker); + } } batch.CommitedAt = Server.Clock.Elapsed; @@ -136,5 +144,28 @@ namespace Avalonia.Rendering.Composition _invokeBeforeCommit.Enqueue(action); RequestCommitAsync(); } + + /// + /// Attempts to query for a feature from the platform render interface + /// + public ValueTask TryGetRenderInterfaceFeature(Type featureType) + { + var tcs = new TaskCompletionSource(); + _pendingServerCompositorJobs.Add(() => + { + try + { + using (Server.RenderInterface.EnsureCurrent()) + { + tcs.TrySetResult(Server.RenderInterface.Value.TryGetFeature(featureType)); + } + } + catch (Exception e) + { + tcs.TrySetException(e); + } + }); + return new ValueTask(tcs.Task); + } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 03859d241f..e6bbba6ec0 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -163,7 +163,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont public CompositionDrawList? VisualBrushDrawList { get; set; } public Size GetRenderTargetSize(IVisualBrush brush) { - return VisualBrushDrawList?.Size ?? Size.Empty; + return VisualBrushDrawList?.Size ?? default; } public void RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/IServerClockItem.cs b/src/Avalonia.Base/Rendering/Composition/Server/IServerClockItem.cs new file mode 100644 index 0000000000..d13f1158c8 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/IServerClockItem.cs @@ -0,0 +1,6 @@ +namespace Avalonia.Rendering.Composition.Server; + +internal interface IServerClockItem +{ + void OnTick(); +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 6cbd2797f9..8dc088fed1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -37,7 +37,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua { if (_contentBounds == null) { - var rect = Rect.Empty; + var rect = default(Rect); if(_renderCommands!=null) foreach (var cmd in _renderCommands) rect = rect.Union(cmd.Item.Bounds); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index f5a46506a3..b172430fbb 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition.Server internal partial class ServerCompositionTarget : IDisposable { private readonly ServerCompositor _compositor; - private readonly Func _renderTargetFactory; + private readonly Func> _surfaces; private static long s_nextId = 1; public long Id { get; } public ulong Revision { get; private set; } @@ -39,11 +39,11 @@ namespace Avalonia.Rendering.Composition.Server public ReadbackIndices Readback { get; } = new(); public int RenderedVisuals { get; set; } - public ServerCompositionTarget(ServerCompositor compositor, Func renderTargetFactory) : + public ServerCompositionTarget(ServerCompositor compositor, Func> surfaces) : base(compositor) { _compositor = compositor; - _renderTargetFactory = renderTargetFactory; + _surfaces = surfaces; Id = Interlocked.Increment(ref s_nextId); } @@ -79,17 +79,16 @@ namespace Avalonia.Rendering.Composition.Server if (Root == null) return; - if ((_renderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) + if (_renderTarget?.IsCorrupted == true) { _renderTarget!.Dispose(); _renderTarget = null; + _redrawRequested = true; } - _renderTarget ??= _renderTargetFactory(); - - Compositor.UpdateServerTime(); + _renderTarget ??= _compositor.CreateRenderTarget(_surfaces()); - if(_dirtyRect.IsEmpty && !_redrawRequested) + if(_dirtyRect.IsDefault && !_redrawRequested) return; Revision++; @@ -109,15 +108,16 @@ namespace Avalonia.Rendering.Composition.Server using (var targetContext = _renderTarget.CreateDrawingContext(null)) { var layerSize = Size * Scaling; - if (layerSize != _layerSize || _layer == null) + if (layerSize != _layerSize || _layer == null || _layer.IsCorrupted) { _layer?.Dispose(); _layer = null; _layer = targetContext.CreateLayer(Size); _layerSize = layerSize; + _dirtyRect = new Rect(0, 0, layerSize.Width, layerSize.Height); } - if (!_dirtyRect.IsEmpty) + if (!_dirtyRect.IsDefault) { var visualBrushHelper = new CompositorDrawingContextProxy.VisualBrushRenderer(); using (var context = _layer.CreateDrawingContext(visualBrushHelper)) @@ -160,7 +160,7 @@ namespace Avalonia.Rendering.Composition.Server } RenderedVisuals = 0; - _dirtyRect = Rect.Empty; + _dirtyRect = default; } } @@ -179,7 +179,7 @@ namespace Avalonia.Rendering.Composition.Server public void AddDirtyRect(Rect rect) { - if(rect.IsEmpty) + if(rect.IsDefault) return; var snapped = SnapToDevicePixels(rect, Scaling); DebugEvents?.RectInvalidated(rect); @@ -197,7 +197,7 @@ namespace Avalonia.Rendering.Composition.Server if(_disposed) return; _disposed = true; - using (_compositor.GpuContext?.EnsureCurrent()) + using (_compositor.RenderInterface.EnsureCurrent()) { if (_layer != null) { diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index d724e14298..0e15cbd54b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -37,7 +37,7 @@ namespace Avalonia.Rendering.Composition.Server return; currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds); - if(currentTransformedClip.IsEmpty) + if(currentTransformedClip.IsDefault) return; Root!.RenderedVisuals++; @@ -139,7 +139,7 @@ namespace Avalonia.Rendering.Composition.Server if (ownBounds != _oldOwnContentBounds || positionChanged) { _oldOwnContentBounds = ownBounds; - if (ownBounds.IsEmpty) + if (ownBounds.IsDefault) TransformedOwnContentBounds = default; else TransformedOwnContentBounds = @@ -163,7 +163,7 @@ namespace Avalonia.Rendering.Composition.Server EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1); IsVisibleInFrame = _parent?.IsVisibleInFrame != false && Visible && EffectiveOpacity > 0.04 && !_isBackface && - !_combinedTransformedClipBounds.IsEmpty; + !_combinedTransformedClipBounds.IsDefault; if (wasVisible != IsVisibleInFrame || positionChanged) { @@ -192,7 +192,7 @@ namespace Avalonia.Rendering.Composition.Server void AddDirtyRect(Rect rc) { - if(rc == Rect.Empty) + if(rc == default) return; Root?.AddDirtyRect(rc); } @@ -216,13 +216,29 @@ namespace Avalonia.Rendering.Composition.Server partial void OnRootChanging() { if (Root != null) + { Root.RemoveVisual(this); + OnDetachedFromRoot(Root); + } + } + + protected virtual void OnDetachedFromRoot(ServerCompositionTarget target) + { + } partial void OnRootChanged() { if (Root != null) + { Root.AddVisual(this); + OnAttachedToRoot(Root); + } + } + + protected virtual void OnAttachedToRoot(ServerCompositionTarget target) + { + } protected override void ValuesInvalidated() diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index bfc2b2d626..e7405995f5 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; +using Avalonia.Logging; using Avalonia.Platform; using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Expressions; @@ -19,23 +21,27 @@ namespace Avalonia.Rendering.Composition.Server internal class ServerCompositor : IRenderLoopTask { private readonly IRenderLoop _renderLoop; + private readonly Queue _batches = new Queue(); public long LastBatchId { get; private set; } public Stopwatch Clock { get; } = Stopwatch.StartNew(); public TimeSpan ServerNow { get; private set; } private List _activeTargets = new(); - private HashSet _activeAnimations = new(); - private List _animationsToUpdate = new(); + private HashSet _clockItems = new(); + private List _clockItemsToUpdate = new(); internal BatchStreamObjectPool BatchObjectPool; internal BatchStreamMemoryPool BatchMemoryPool; private object _lock = new object(); - public IPlatformGpuContext? GpuContext { get; } + private Thread? _safeThread; + public PlatformRenderInterfaceContextManager RenderInterface { get; } + internal static readonly object RenderThreadJobsStartMarker = new(); + internal static readonly object RenderThreadJobsEndMarker = new(); - public ServerCompositor(IRenderLoop renderLoop, IPlatformGpu? platformGpu, + public ServerCompositor(IRenderLoop renderLoop, IPlatformGraphics? platformGraphics, BatchStreamObjectPool batchObjectPool, BatchStreamMemoryPool batchMemoryPool) { - GpuContext = platformGpu?.PrimaryContext; _renderLoop = renderLoop; + RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics); BatchObjectPool = batchObjectPool; BatchMemoryPool = batchMemoryPool; _renderLoop.Add(this); @@ -66,7 +72,14 @@ namespace Avalonia.Rendering.Composition.Server { while (!stream.IsObjectEof) { - var target = (ServerObject)stream.ReadObject()!; + var readObject = stream.ReadObject(); + if (readObject == RenderThreadJobsStartMarker) + { + ReadAndExecuteJobs(stream); + continue; + } + + var target = (ServerObject)readObject!; target.DeserializeChanges(stream, batch); #if DEBUG_COMPOSITOR_SERIALIZATION if (stream.ReadObject() != BatchStreamDebugMarkers.ObjectEndMarker) @@ -84,6 +97,23 @@ namespace Avalonia.Rendering.Composition.Server } } + void ReadAndExecuteJobs(BatchStreamReader reader) + { + object? readObject; + while ((readObject = reader.ReadObject()) != RenderThreadJobsEndMarker) + { + var job = (Action)readObject!; + try + { + job(); + } + catch + { + // Ignore + } + } + } + void CompletePendingBatches() { foreach(var batch in _reusableToCompleteList) @@ -101,25 +131,42 @@ namespace Avalonia.Rendering.Composition.Server { lock (_lock) { - RenderCore(); + try + { + _safeThread = Thread.CurrentThread; + RenderCore(); + } + finally + { + _safeThread = null; + } } } private void RenderCore() { + UpdateServerTime(); ApplyPendingBatches(); CompletePendingBatches(); - foreach(var animation in _activeAnimations) - _animationsToUpdate.Add(animation); - - foreach(var animation in _animationsToUpdate) - animation.Invalidate(); + foreach(var animation in _clockItems) + _clockItemsToUpdate.Add(animation); + + foreach (var animation in _clockItemsToUpdate) + animation.OnTick(); - _animationsToUpdate.Clear(); + _clockItemsToUpdate.Clear(); - foreach (var t in _activeTargets) - t.Render(); + try + { + RenderInterface.EnsureValidBackendContext(); + foreach (var t in _activeTargets) + t.Render(); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Visual)?.Log(this, "Exception when rendering: {Error}", e); + } } public void AddCompositionTarget(ServerCompositionTarget target) @@ -132,10 +179,23 @@ namespace Avalonia.Rendering.Composition.Server _activeTargets.Remove(target); } - public void AddToClock(IAnimationInstance animationInstance) => - _activeAnimations.Add(animationInstance); + public void AddToClock(IServerClockItem item) => + _clockItems.Add(item); - public void RemoveFromClock(IAnimationInstance animationInstance) => - _activeAnimations.Remove(animationInstance); + public void RemoveFromClock(IServerClockItem item) => + _clockItems.Remove(item); + + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + using (RenderInterface.EnsureCurrent()) + return RenderInterface.CreateRenderTarget(surfaces); + } + + public bool CheckAccess() => _safeThread == Thread.CurrentThread; + public void VerifyAccess() + { + if (!CheckAccess()) + throw new InvalidOperationException("This object can be only accessed under compositor lock"); + } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs new file mode 100644 index 0000000000..227df87a87 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs @@ -0,0 +1,82 @@ +using System; +using System.Numerics; +using Avalonia.Logging; +using Avalonia.Media; +using Avalonia.Rendering.Composition.Transport; + +namespace Avalonia.Rendering.Composition.Server; + +internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, IServerClockItem +{ + private readonly CompositionCustomVisualHandler _handler; + private bool _wantsNextAnimationFrameAfterTick; + internal ServerCompositionCustomVisual(ServerCompositor compositor, CompositionCustomVisualHandler handler) : base(compositor) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + _handler.Attach(this); + } + + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + { + base.DeserializeChangesCore(reader, commitedAt); + var count = reader.Read(); + for (var c = 0; c < count; c++) + { + try + { + _handler.OnMessage(reader.ReadObject()!); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Visual) + ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnMessage)} {{0}}", e); + } + } + } + + public void OnTick() + { + _wantsNextAnimationFrameAfterTick = false; + _handler.OnAnimationFrameUpdate(); + if (!_wantsNextAnimationFrameAfterTick) + Compositor.RemoveFromClock(this); + } + + public override Rect OwnContentBounds => _handler.GetRenderBounds(); + + protected override void OnAttachedToRoot(ServerCompositionTarget target) + { + if (_wantsNextAnimationFrameAfterTick) + Compositor.AddToClock(this); + base.OnAttachedToRoot(target); + } + + protected override void OnDetachedFromRoot(ServerCompositionTarget target) + { + Compositor.RemoveFromClock(this); + base.OnDetachedFromRoot(target); + } + + internal void HandlerInvalidate() => ValuesInvalidated(); + + internal void HandlerRegisterForNextAnimationFrameUpdate() + { + _wantsNextAnimationFrameAfterTick = true; + if (Root != null) + Compositor.AddToClock(this); + } + + protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) + { + using var context = new ImmediateDrawingContext(canvas, false); + try + { + _handler.OnRender(context); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Visual) + ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnRender)} {{0}}", e); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index 8b68900994..a3cad3cebd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -21,7 +21,7 @@ internal class BatchStreamData public Queue> Structs { get; } = new(); } -public struct BatchStreamSegment +public record struct BatchStreamSegment { public TData Data { get; set; } public int ElementCount { get; set; } diff --git a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs index 6981dc39e3..93e9f4d740 100644 --- a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs @@ -69,7 +69,7 @@ namespace Avalonia.Rendering.Composition { if (item.Parent != null) throw new InvalidOperationException("Visual already has a parent"); - item.Parent = item; + item.Parent = _owner; } } } diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs index c3bf861c47..f0b993a2b0 100644 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Base/Rendering/DeferredRenderer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Threading.Tasks; using Avalonia.Logging; using Avalonia.Media; using Avalonia.Media.Immutable; @@ -22,6 +23,8 @@ namespace Avalonia.Rendering { private readonly IDispatcher? _dispatcher; private readonly IRenderLoop? _renderLoop; + private readonly Func? _renderTargetFactory; + private readonly PlatformRenderInterfaceContextManager? _renderInterface; private readonly Visual _root; private readonly ISceneBuilder _sceneBuilder; @@ -39,18 +42,23 @@ namespace Avalonia.Rendering private readonly object _startStopLock = new object(); private readonly object _renderLoopIsRenderingLock = new object(); private readonly Action _updateSceneIfNeededDelegate; + private List? _pendingRenderThreadJobs; /// /// Initializes a new instance of the class. /// /// The control to render. /// The render loop. + /// The target render factory. + /// The Platform Render Context. /// The scene builder to use. Optional. /// The dispatcher to use. Optional. /// Lock object used before trying to access render target public DeferredRenderer( IRenderRoot root, IRenderLoop renderLoop, + Func renderTargetFactory, + PlatformRenderInterfaceContextManager? renderInterface = null, ISceneBuilder? sceneBuilder = null, IDispatcher? dispatcher = null, IDeferredRendererLock? rendererLock = null) : base(true) @@ -60,6 +68,8 @@ namespace Avalonia.Rendering _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); _renderLoop = renderLoop; + _renderTargetFactory = renderTargetFactory; + _renderInterface = renderInterface; _lock = rendererLock ?? new ManagedDeferredRendererLock(); _updateSceneIfNeededDelegate = UpdateSceneIfNeeded; } @@ -256,6 +266,30 @@ namespace Avalonia.Rendering } } + public ValueTask TryGetRenderInterfaceFeature(Type featureType) + { + if (_renderInterface == null) + return new((object?)null); + + var tcs = new TaskCompletionSource(); + _pendingRenderThreadJobs ??= new(); + _pendingRenderThreadJobs.Add(() => + { + try + { + using (_renderInterface.EnsureCurrent()) + { + tcs.TrySetResult(_renderInterface.Value.TryGetFeature(featureType)); + } + } + catch (Exception e) + { + tcs.TrySetException(e); + } + }); + return new ValueTask(tcs.Task); + } + bool NeedsUpdate => _dirty == null || _dirty.Count > 0; bool IRenderLoopTask.NeedsUpdate => NeedsUpdate; @@ -277,7 +311,7 @@ namespace Avalonia.Rendering /// Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) { - return TryGetChildScene(_currentDraw)?.Size ?? Size.Empty; + return TryGetChildScene(_currentDraw)?.Size ?? default; } /// @@ -337,7 +371,16 @@ namespace Avalonia.Rendering } finally { - scene.Item.MarkAsRendered(); + try + { + if(scene.Item.RenderThreadJobs!=null) + foreach (var job in scene.Item.RenderThreadJobs) + job(); + } + finally + { + scene.Item.MarkAsRendered(); + } } } } @@ -420,7 +463,7 @@ namespace Avalonia.Rendering { clipBounds = node.ClipBounds.Intersect(clipBounds); - if (!clipBounds.IsEmpty && node.Opacity > 0) + if (!clipBounds.IsDefault && node.Opacity > 0) { var isLayerRoot = node.Visual == layer; @@ -604,7 +647,7 @@ namespace Avalonia.Rendering return; } - if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) + if (RenderTarget?.IsCorrupted == true) { RenderTarget!.Dispose(); RenderTarget = null; @@ -612,7 +655,7 @@ namespace Avalonia.Rendering if (RenderTarget == null) { - RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + RenderTarget = _renderTargetFactory!(); } context = RenderTarget.CreateDrawingContext(this); @@ -637,7 +680,11 @@ namespace Avalonia.Rendering } if (_root.IsVisible) { - var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root)); + var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root) + { + RenderThreadJobs = _pendingRenderThreadJobs + }); + _pendingRenderThreadJobs = null; var scene = sceneRef.Item; if (_dirty == null) diff --git a/src/Avalonia.Base/Rendering/DirtyRects.cs b/src/Avalonia.Base/Rendering/DirtyRects.cs index f4b6a6b6ce..723fe400b3 100644 --- a/src/Avalonia.Base/Rendering/DirtyRects.cs +++ b/src/Avalonia.Base/Rendering/DirtyRects.cs @@ -10,6 +10,9 @@ namespace Avalonia.Rendering { private List _rects = new List(); + /// + /// Gets a value indicating whether the collection of dirty rectangles is empty. + /// public bool IsEmpty => _rects.Count == 0; /// @@ -27,7 +30,7 @@ namespace Avalonia.Rendering /// public void Add(Rect rect) { - if (!rect.IsEmpty) + if (!rect.IsDefault) { for (var i = 0; i < _rects.Count; ++i) { diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs index aa06ce7bed..cabb1302cd 100644 --- a/src/Avalonia.Base/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs @@ -25,12 +25,6 @@ namespace Avalonia.Rendering /// double RenderScaling { get; } - /// - /// Creates a render target for the window. - /// - /// An . - IRenderTarget CreateRenderTarget(); - /// /// Adds a rectangle to the window's dirty region. /// diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs index cb36aad7cc..f3f5b5e99b 100644 --- a/src/Avalonia.Base/Rendering/IRenderer.cs +++ b/src/Avalonia.Base/Rendering/IRenderer.cs @@ -1,6 +1,7 @@ using System; using Avalonia.VisualTree; using System.Collections.Generic; +using System.Threading.Tasks; using Avalonia.Rendering.Composition; namespace Avalonia.Rendering @@ -87,6 +88,11 @@ namespace Avalonia.Rendering /// Stops the renderer. /// void Stop(); + + /// + /// Attempts to query for a feature from the platform render interface + /// + public ValueTask TryGetRenderInterfaceFeature(Type featureType); } public interface IRendererWithCompositor : IRenderer diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 9e1582ed43..4909c78ed1 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Avalonia.Logging; using Avalonia.Media; using Avalonia.Platform; @@ -19,6 +20,8 @@ namespace Avalonia.Rendering public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer { private readonly Visual _root; + private readonly Func _renderTargetFactory; + private readonly PlatformRenderInterfaceContextManager? _renderContext; private readonly IRenderRoot? _renderRoot; private bool _updateTransformedBounds = true; private IRenderTarget? _renderTarget; @@ -27,15 +30,21 @@ namespace Avalonia.Rendering /// Initializes a new instance of the class. /// /// The control to render. - public ImmediateRenderer(Visual root) + /// The target render factory. + /// The render contex. + public ImmediateRenderer(Visual root, Func renderTargetFactory, + PlatformRenderInterfaceContextManager? renderContext = null) { _root = root ?? throw new ArgumentNullException(nameof(root)); + _renderTargetFactory = renderTargetFactory; + _renderContext = renderContext; _renderRoot = root as IRenderRoot; } - private ImmediateRenderer(Visual root, bool updateTransformedBounds) + private ImmediateRenderer(Visual root, Func renderTargetFactory, bool updateTransformedBounds) { _root = root ?? throw new ArgumentNullException(nameof(root)); + _renderTargetFactory = renderTargetFactory; _renderRoot = root as IRenderRoot; _updateTransformedBounds = updateTransformedBounds; } @@ -54,7 +63,7 @@ namespace Avalonia.Rendering { if (_renderTarget == null) { - _renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); + _renderTarget = _renderTargetFactory(); } try @@ -104,7 +113,7 @@ namespace Avalonia.Rendering /// The render target. public static void Render(Visual visual, IRenderTarget target) { - using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false)) + using (var renderer = new ImmediateRenderer(visual, () => target, updateTransformedBounds: false)) using (var context = new DrawingContext(target.CreateDrawingContext(renderer))) { renderer.Render(context, visual, visual.Bounds); @@ -118,7 +127,9 @@ namespace Avalonia.Rendering /// The drawing context. public static void Render(Visual visual, DrawingContext context) { - using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false)) + using (var renderer = new ImmediateRenderer(visual, + () => throw new InvalidOperationException("This is not supposed to be called"), + updateTransformedBounds: false)) { renderer.Render(context, visual, visual.Bounds); } @@ -127,7 +138,7 @@ namespace Avalonia.Rendering /// public void AddDirty(Visual visual) { - if (visual.Bounds != Rect.Empty) + if (!visual.Bounds.IsDefault) { var m = visual.TransformToVisual(_root); @@ -185,11 +196,14 @@ namespace Avalonia.Rendering { } + public ValueTask TryGetRenderInterfaceFeature(Type featureType) => + new(_renderContext?.Value?.TryGetFeature(featureType)); + /// Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) { (brush.Visual as IVisualBrushInitialize)?.EnsureInitialized(); - return brush.Visual?.Bounds.Size ?? Size.Empty; + return brush.Visual?.Bounds.Size ?? default; } /// @@ -201,7 +215,9 @@ namespace Avalonia.Rendering internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds) { - using var renderer = new ImmediateRenderer(visual, updateTransformedBounds); + using var renderer = new ImmediateRenderer(visual, + () => throw new InvalidOperationException("This is not supposed to be called"), + updateTransformedBounds); renderer.Render(context, visual, visual.Bounds); } diff --git a/src/Avalonia.Base/Rendering/OwnedDisposable.cs b/src/Avalonia.Base/Rendering/OwnedDisposable.cs new file mode 100644 index 0000000000..27122751aa --- /dev/null +++ b/src/Avalonia.Base/Rendering/OwnedDisposable.cs @@ -0,0 +1,24 @@ +using System; + +namespace Avalonia.Rendering; + +struct OwnedDisposable :IDisposable where T : class, IDisposable +{ + private readonly bool _owns; + private T? _value; + + public T Value => _value ?? throw new ObjectDisposedException("OwnedDisposable"); + + public OwnedDisposable(T value, bool owns) + { + _owns = owns; + _value = value; + } + + public void Dispose() + { + if(_owns) + _value?.Dispose(); + _value = null; + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs new file mode 100644 index 0000000000..198b36564a --- /dev/null +++ b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; +using Avalonia.Metadata; +using Avalonia.Platform; + +namespace Avalonia.Rendering; + +[Unstable] +// TODO: Make it internal once legacy renderers are removed +public class PlatformRenderInterfaceContextManager +{ + private readonly IPlatformGraphics? _graphics; + private IPlatformRenderInterfaceContext? _backend; + private OwnedDisposable? _gpuContext; + + public PlatformRenderInterfaceContextManager(IPlatformGraphics? graphics) + { + _graphics = graphics; + } + + public void EnsureValidBackendContext() + { + if (_backend == null || _gpuContext?.Value.IsLost == true) + { + _backend?.Dispose(); + _backend = null; + _gpuContext?.Dispose(); + _gpuContext = null; + + if (_graphics != null) + { + if (_graphics.UsesSharedContext) + _gpuContext = new OwnedDisposable(_graphics.GetSharedContext(), false); + else + _gpuContext = new OwnedDisposable(_graphics.CreateContext(), true); + } + + _backend = AvaloniaLocator.Current.GetRequiredService() + .CreateBackendContext(_gpuContext?.Value); + } + } + + public IPlatformRenderInterfaceContext Value + { + get + { + EnsureValidBackendContext(); + return _backend!; + } + } + + public IDisposable EnsureCurrent() + { + EnsureValidBackendContext(); + if (_gpuContext.HasValue) + return _gpuContext.Value.Value.EnsureCurrent(); + return Disposable.Empty; + } + + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + EnsureValidBackendContext(); + return _backend!.CreateRenderTarget(surfaces); + } +} diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs index c66fec92aa..5a08bfc6a1 100644 --- a/src/Avalonia.Base/Rendering/RenderLoop.cs +++ b/src/Avalonia.Base/Rendering/RenderLoop.cs @@ -19,6 +19,7 @@ namespace Avalonia.Rendering private readonly IDispatcher _dispatcher; private List _items = new List(); private List _itemsCopy = new List(); + private List _updateItemsCopy = new List(); private IRenderTimer? _timer; private int _inTick; private int _inUpdate; @@ -97,7 +98,13 @@ namespace Avalonia.Rendering { bool needsUpdate = false; - foreach (IRenderLoopTask item in _items) + lock (_items) + { + _itemsCopy.Clear(); + _itemsCopy.AddRange(_items); + } + + foreach (IRenderLoopTask item in _itemsCopy) { if (item.NeedsUpdate) { @@ -112,10 +119,13 @@ namespace Avalonia.Rendering { _dispatcher.Post(() => { - for (var i = 0; i < _items.Count; ++i) + lock (_items) + { + _updateItemsCopy.Clear(); + _updateItemsCopy.AddRange(_items); + } + foreach (var item in _updateItemsCopy) { - var item = _items[i]; - if (item.NeedsUpdate) { try @@ -128,18 +138,12 @@ namespace Avalonia.Rendering } } } + _updateItemsCopy.Clear(); Interlocked.Exchange(ref _inUpdate, 0); }, DispatcherPriority.Render); } - lock (_items) - { - _itemsCopy.Clear(); - foreach (var i in _items) - _itemsCopy.Add(i); - } - for (int i = 0; i < _itemsCopy.Count; i++) { _itemsCopy[i].Render(); diff --git a/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs index 98e89f6549..b1190a159b 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs @@ -27,7 +27,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Empty; + public Rect Bounds => default; /// /// Gets the BitmapBlend to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs index 90430bed02..e1bfaa4aa3 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs @@ -40,7 +40,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Empty; + public Rect Bounds => default; /// /// Gets the clip to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs index 667b66420b..842edf2bcb 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs @@ -28,7 +28,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Empty; + public Rect Bounds => default; /// /// Gets the clip to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs index 5fd200ddff..3ecc07fa54 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The bounds of the mask. /// Auxiliary data required to draw the brush. public OpacityMaskNode(IBrush mask, Rect bounds, IDisposable? aux = null) - : base(Rect.Empty, Matrix.Identity, aux) + : base(default, Matrix.Identity, aux) { Mask = mask.ToImmutable(); MaskBounds = bounds; @@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph /// opacity mask pop. /// public OpacityMaskNode() - : base(Rect.Empty, Matrix.Identity, null) + : base(default, Matrix.Identity, null) { } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs index 8fc630588f..e41e639067 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs @@ -26,7 +26,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Empty; + public Rect Bounds => default; /// /// Gets the opacity to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs index 2da8923497..735eb3bb3f 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs @@ -346,5 +346,7 @@ namespace Avalonia.Rendering.SceneGraph } public void MarkAsRendered() => _rendered.TrySetResult(true); + + public List? RenderThreadJobs { get; set; } } } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs index d54bd3fad8..55ff772772 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs @@ -331,8 +331,8 @@ namespace Avalonia.Rendering.SceneGraph scene.Size = newSize; - Rect horizontalDirtyRect = Rect.Empty; - Rect verticalDirtyRect = Rect.Empty; + Rect horizontalDirtyRect = default; + Rect verticalDirtyRect = default; if (newSize.Width > oldSize.Width) { @@ -429,7 +429,7 @@ namespace Avalonia.Rendering.SceneGraph else { layer.OpacityMask = null; - layer.OpacityMaskRect = Rect.Empty; + layer.OpacityMaskRect = default; } layer.GeometryClip = node.HasAncestorGeometryClip ? diff --git a/src/Avalonia.Base/Size.cs b/src/Avalonia.Base/Size.cs index 5f20206200..aec237afae 100644 --- a/src/Avalonia.Base/Size.cs +++ b/src/Avalonia.Base/Size.cs @@ -28,8 +28,9 @@ namespace Avalonia public static readonly Size Infinity = new Size(double.PositiveInfinity, double.PositiveInfinity); /// - /// A size representing zero + /// A size representing zero. /// + [Obsolete("Use the default keyword instead.")] public static readonly Size Empty = new Size(0, 0); /// @@ -309,9 +310,6 @@ namespace Avalonia /// /// Gets a value indicating whether the Width and Height values are zero. /// - public bool IsDefault - { - get { return (_width == 0) && (_height == 0); } - } + public bool IsDefault => (_width == 0) && (_height == 0); } } diff --git a/src/Avalonia.Base/Styling/SelectorMatch.cs b/src/Avalonia.Base/Styling/SelectorMatch.cs index 26b525347e..2eac04301a 100644 --- a/src/Avalonia.Base/Styling/SelectorMatch.cs +++ b/src/Avalonia.Base/Styling/SelectorMatch.cs @@ -43,7 +43,7 @@ namespace Avalonia.Styling /// A selector match describes whether and how a matches a control, and /// in addition whether the selector can ever match a control of the same type. /// - public readonly struct SelectorMatch + public readonly record struct SelectorMatch { /// /// A selector match with the result of . diff --git a/src/Avalonia.Base/Thickness.cs b/src/Avalonia.Base/Thickness.cs index c8e6d7dfd2..f9e4355edd 100644 --- a/src/Avalonia.Base/Thickness.cs +++ b/src/Avalonia.Base/Thickness.cs @@ -97,10 +97,9 @@ namespace Avalonia /// public double Bottom => _bottom; - /// - /// Gets a value indicating whether all sides are set to 0. - /// - public bool IsEmpty => Left.Equals(0) && IsUniform; + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; /// /// Gets a value indicating whether all sides are equal. @@ -292,15 +291,13 @@ namespace Avalonia left = this._left; top = this._top; right = this._right; - bottom = this._bottom; + bottom = this._bottom; } /// - /// Gets a value indicating whether the left, top, right and bottom thickness values are zero. + /// Gets a value indicating whether the instance has default values + /// (the left, top, right and bottom values are zero). /// - public bool IsDefault - { - get { return (_left == 0) && (_top == 0) && (_right == 0) && (_bottom == 0); } - } + public bool IsDefault => (_left == 0) && (_top == 0) && (_right == 0) && (_bottom == 0); } } diff --git a/src/Avalonia.Base/Utilities/SmallDictionary.cs b/src/Avalonia.Base/Utilities/SmallDictionary.cs index 7d6a21c136..8aaf2200df 100644 --- a/src/Avalonia.Base/Utilities/SmallDictionary.cs +++ b/src/Avalonia.Base/Utilities/SmallDictionary.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; namespace Avalonia.Utilities; -public struct InlineDictionary : IEnumerable> where TKey : class where TValue : class +public record struct InlineDictionary : IEnumerable> where TKey : class where TValue : class { object? _data; TValue? _value; diff --git a/src/Avalonia.Base/Utilities/StringTokenizer.cs b/src/Avalonia.Base/Utilities/StringTokenizer.cs index 748eb09209..726c9735ef 100644 --- a/src/Avalonia.Base/Utilities/StringTokenizer.cs +++ b/src/Avalonia.Base/Utilities/StringTokenizer.cs @@ -8,7 +8,7 @@ namespace Avalonia.Utilities #if !BUILDTASK public #endif - struct StringTokenizer : IDisposable + record struct StringTokenizer : IDisposable { private const char DefaultSeparatorChar = ','; diff --git a/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs b/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs index fb8d3ed224..e574462e2c 100644 --- a/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs +++ b/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs @@ -8,7 +8,7 @@ namespace Avalonia.Utilities /// A task-like operation that is guaranteed to finish continuations synchronously, /// can be used for parametrized one-shot events /// - public struct SynchronousCompletionAsyncResult : INotifyCompletion + public record struct SynchronousCompletionAsyncResult : INotifyCompletion { private readonly SynchronousCompletionAsyncResultSource? _source; private readonly T? _result; diff --git a/src/Avalonia.Base/Utilities/ValueSpan.cs b/src/Avalonia.Base/Utilities/ValueSpan.cs index 7a10d865ef..4c2de2776d 100644 --- a/src/Avalonia.Base/Utilities/ValueSpan.cs +++ b/src/Avalonia.Base/Utilities/ValueSpan.cs @@ -3,7 +3,7 @@ /// /// Pairing of value and positions sharing that value. /// - public readonly struct ValueSpan + public readonly record struct ValueSpan { public ValueSpan(int start, int length, T value) { diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index e3dc4fbb75..5930df5483 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -367,7 +367,10 @@ namespace Avalonia /// The drawing context. public virtual void Render(DrawingContext context) { - Contract.Requires(context != null); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } } /// @@ -540,6 +543,9 @@ namespace Avalonia OnDetachedFromVisualTree(e); if (CompositionVisual != null) { + if (ChildCompositionVisual != null) + CompositionVisual.Children.Remove(ChildCompositionVisual); + CompositionVisual.DrawList = null; CompositionVisual = null; } diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index fad6ad397b..65681a2d28 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -7,7 +7,7 @@ using System.Text; using Avalonia.Markup.Xaml.PortableXaml; using Avalonia.Utilities; using Microsoft.Build.Framework; -using SPath=System.IO.Path; +using SPath = System.IO.Path; namespace Avalonia.Build.Tasks { public class GenerateAvaloniaResourcesTask : ITask @@ -30,12 +30,17 @@ namespace Avalonia.Build.Tasks private byte[] _data; private string _sourcePath; - public Source(string relativePath, string root) + public Source(ITaskItem avaloniaResourceItem, string root) { root = SPath.GetFullPath(root); - Path = "/" + relativePath.Replace('\\', '/'); + var relativePath = avaloniaResourceItem.ItemSpec; _sourcePath = SPath.Combine(root, relativePath); Size = (int)new FileInfo(_sourcePath).Length; + var link = avaloniaResourceItem.GetMetadata("Link"); + var path = !string.IsNullOrEmpty(link) + ? link + : relativePath; + Path = "/" + path.Replace('\\', '/'); } public string SystemPath => _sourcePath ?? Path; @@ -65,8 +70,7 @@ namespace Avalonia.Build.Tasks List BuildResourceSources() => Resources.Select(r => { - - var src = new Source(r.ItemSpec, Root); + var src = new Source(r, Root); BuildEngine.LogMessage(FormattableString.Invariant($"avares -> name:{src.Path}, path: {src.SystemPath}, size:{src.Size}, ItemSpec:{r.ItemSpec}"), _reportImportance); return src; }).ToList(); @@ -93,15 +97,15 @@ namespace Avalonia.Build.Tasks ms.CopyTo(output); foreach (var s in sources) { - using(var input = s.Open()) + using (var input = s.Open()) input.CopyTo(output); } } private bool PreProcessXamlFiles(List sources) { - var typeToXamlIndex = new Dictionary(); - + var typeToXamlIndex = new Dictionary(); + foreach (var s in sources.ToArray()) { if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml")) @@ -111,7 +115,7 @@ namespace Avalonia.Build.Tasks { info = XamlFileInfo.Parse(s.ReadAsString()); } - catch(Exception e) + catch (Exception e) { BuildEngine.LogError(BuildEngineErrorCode.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e); return false; @@ -121,7 +125,7 @@ namespace Avalonia.Build.Tasks { if (typeToXamlIndex.ContainsKey(info.XClass)) { - + BuildEngine.LogError(BuildEngineErrorCode.DuplicateXClass, s.SystemPath, $"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}"); return false; diff --git a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj index f3f8e4c82c..97f9efe8fa 100644 --- a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj +++ b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index 9d2a994e5b..2ccf20d460 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -3,10 +3,6 @@ xmlns:controls="using:Avalonia.Controls" xmlns:primitives="using:Avalonia.Controls.Primitives"> - - - - diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml index 85f4e417e6..2cc8a1d38a 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml @@ -50,13 +50,13 @@ - - - + + + - - + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml index 626ddd4b43..b82d36a288 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml @@ -3,10 +3,6 @@ xmlns:controls="using:Avalonia.Controls" xmlns:primitives="using:Avalonia.Controls.Primitives"> - - - - diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml index 7aefa23706..8c4dfa9c87 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml @@ -50,13 +50,13 @@ - - - + + + - - + + diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index 6369961f0f..efa38e49a7 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 454678c64b..5dcad83601 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -48,7 +48,6 @@ namespace Avalonia.Controls private const string DATAGRID_elementColumnHeadersPresenterName = "PART_ColumnHeadersPresenter"; private const string DATAGRID_elementFrozenColumnScrollBarSpacerName = "PART_FrozenColumnScrollBarSpacer"; private const string DATAGRID_elementHorizontalScrollbarName = "PART_HorizontalScrollbar"; - private const string DATAGRID_elementRowHeadersPresenterName = "PART_RowHeadersPresenter"; private const string DATAGRID_elementTopLeftCornerHeaderName = "PART_TopLeftCornerHeader"; private const string DATAGRID_elementTopRightCornerHeaderName = "PART_TopRightCornerHeader"; private const string DATAGRID_elementBottomRightCornerHeaderName = "PART_BottomRightCorner"; @@ -1124,7 +1123,7 @@ namespace Avalonia.Controls EnsureColumnHeadersVisibility(); if (!newValueCols) { - _columnHeadersPresenter.Measure(Size.Empty); + _columnHeadersPresenter.Measure(default(Size)); } else { @@ -1165,7 +1164,7 @@ namespace Avalonia.Controls _topLeftCornerHeader.IsVisible = newValueRows && newValueCols; if (_topLeftCornerHeader.IsVisible) { - _topLeftCornerHeader.Measure(Size.Empty); + _topLeftCornerHeader.Measure(default(Size)); } } diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index 6b1796e50b..c308312398 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -192,14 +192,14 @@ namespace Avalonia.Controls void OnLayoutUpdated(object sender, EventArgs e) { - if(!editingCheckBox.Bounds.IsEmpty) + if(!editingCheckBox.Bounds.IsDefault) { editingCheckBox.LayoutUpdated -= OnLayoutUpdated; ProcessPointerArgs(); } } - if(editingCheckBox.Bounds.IsEmpty) + if(editingCheckBox.Bounds.IsDefault) { editingCheckBox.LayoutUpdated += OnLayoutUpdated; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index d417c87746..75101dc487 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -50,10 +50,13 @@ namespace Avalonia.Controls InheritsWidth = true; } - internal DataGrid OwningGrid + /// + /// Gets the control that contains this column. + /// + protected internal DataGrid OwningGrid { get; - set; + internal set; } internal int Index @@ -638,7 +641,7 @@ namespace Avalonia.Controls public Control GetCellContent(DataGridRow dataGridRow) { - Contract.Requires(dataGridRow != null); + dataGridRow = dataGridRow ?? throw new ArgumentNullException(nameof(dataGridRow)); if (OwningGrid == null) { throw DataGridError.DataGrid.NoOwningGrid(GetType()); @@ -656,7 +659,7 @@ namespace Avalonia.Controls public Control GetCellContent(object dataItem) { - Contract.Requires(dataItem != null); + dataItem = dataItem ?? throw new ArgumentNullException(nameof(dataItem)); if (OwningGrid == null) { throw DataGridError.DataGrid.NoOwningGrid(GetType()); diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 6473c9c051..252868847a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -34,7 +34,6 @@ namespace Avalonia.Controls } private const int DATAGRIDCOLUMNHEADER_resizeRegionWidth = 5; - private const double DATAGRIDCOLUMNHEADER_separatorThickness = 1; private const int DATAGRIDCOLUMNHEADER_columnsDragTreshold = 5; private bool _areHandlersSuspended; diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs index fe3ba0abf6..c42d21d126 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs @@ -18,8 +18,6 @@ namespace Avalonia.Controls.Primitives public class DataGridRowHeader : ContentControl { private const string DATAGRIDROWHEADER_elementRootName = "PART_Root"; - private const double DATAGRIDROWHEADER_separatorThickness = 1; - private Control _rootElement; public static readonly StyledProperty SeparatorBrushProperty = diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 6c997f62c1..4d3bccee70 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -16,7 +16,6 @@ using System.Diagnostics; using System.Linq; using Avalonia.Data; using Avalonia.Styling; -using JetBrains.Annotations; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs b/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs index 6144679b60..c823ee1b9c 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs @@ -22,14 +22,18 @@ namespace Avalonia.Controls return DefaultValueConverter.Instance.Convert(value, targetType, parameter, culture); } - // This suppresses a warning saying that we should use String.IsNullOrEmpty instead of a string - // comparison, but in this case we want to explicitly check for Empty and not Null. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (targetType != null && targetType.IsNullableType()) { var strValue = value as string; - if (string.IsNullOrEmpty(strValue)) + + // This suppresses a warning saying that we should use String.IsNullOrEmpty instead of a string + // comparison, but in this case we want to explicitly check for Empty and not Null. +#pragma warning disable CA1820 + if (strValue == string.Empty) +#pragma warning restore CA1820 { return null; } diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs index 38d559a031..06a77f0894 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs @@ -161,7 +161,7 @@ namespace Avalonia.Controls.Primitives { // Clip RectangleGeometry rg = new RectangleGeometry(); - rg.Rect = Rect.Empty; + rg.Rect = default; cell.Clip = rg; } } diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs index b34f52f47d..f9b84793c6 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs @@ -305,7 +305,7 @@ namespace Avalonia.Controls.Primitives } if (!OwningGrid.AreColumnHeadersVisible) { - return Size.Empty; + return default; } double height = OwningGrid.ColumnHeaderHeight; bool autoSizeHeight; diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridDetailsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridDetailsPresenter.cs index 543485b311..07e7708003 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridDetailsPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridDetailsPresenter.cs @@ -112,7 +112,7 @@ namespace Avalonia.Controls.Primitives { if (OwningGrid == null || Children.Count == 0) { - return Size.Empty; + return default; } double desiredWidth = OwningGrid.AreRowDetailsFrozen ? diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs index 9feca71cda..4fd0ca3d26 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs @@ -26,7 +26,6 @@ namespace Avalonia.Controls.Primitives /// true if the grid is frozen; otherwise, false. The default is true. public static bool GetIsFrozen(Control element) { - Contract.Requires(element != null); return element.GetValue(IsFrozenProperty); } @@ -38,7 +37,6 @@ namespace Avalonia.Controls.Primitives /// is null. public static void SetIsFrozen(Control element, bool value) { - Contract.Requires(element != null); element.SetValue(IsFrozenProperty, value); } } diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index fb4335f4de..dd8575c989 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -6,8 +6,8 @@ M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z - M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z - M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z + M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z + M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z @@ -379,7 +379,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center"> @@ -387,7 +387,7 @@ diff --git a/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs b/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs index e1f1c4029e..2402de56ee 100644 --- a/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs +++ b/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs @@ -5,7 +5,7 @@ /// It controls the base opacity level of the 'tracing paper' layer that compensates /// for low blur radius. /// - public struct AcrylicPlatformCompensationLevels + public record struct AcrylicPlatformCompensationLevels { public AcrylicPlatformCompensationLevels(double transparent, double blurred, double acrylic) { diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 604f2369e7..a100d38d78 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -229,8 +229,8 @@ namespace Avalonia.Controls s_setupWasAlreadyCalled = true; _optionsInitializers?.Invoke(); RuntimePlatformServicesInitializer(); - WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); + WindowingSubsystemInitializer(); AfterPlatformServicesSetupCallback(Self); Instance = _appFactory(); Instance.ApplicationLifetime = _lifetime; diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index c59458311c..757db96799 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -83,12 +83,12 @@ namespace Avalonia.Controls.ApplicationLifetimes public void Shutdown(int exitCode = 0) { - DoShutdown(new ShutdownRequestedEventArgs(), true, exitCode); + DoShutdown(new ShutdownRequestedEventArgs(), true, true, exitCode); } public bool TryShutdown(int exitCode = 0) { - return DoShutdown(new ShutdownRequestedEventArgs(), false, exitCode); + return DoShutdown(new ShutdownRequestedEventArgs(), true, false, exitCode); } public int Start(string[] args) @@ -134,7 +134,11 @@ namespace Avalonia.Controls.ApplicationLifetimes _activeLifetime = null; } - private bool DoShutdown(ShutdownRequestedEventArgs e, bool force = false, int exitCode = 0) + private bool DoShutdown( + ShutdownRequestedEventArgs e, + bool isProgrammatic, + bool force = false, + int exitCode = 0) { if (!force) { @@ -159,7 +163,7 @@ namespace Avalonia.Controls.ApplicationLifetimes { if (w.Owner is null) { - w.Close(); + w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic); } } @@ -183,7 +187,7 @@ namespace Avalonia.Controls.ApplicationLifetimes return true; } - private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e) => DoShutdown(e); + private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e) => DoShutdown(e, false); } public class ClassicDesktopStyleApplicationLifetimeOptions diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 2710ac5cc2..cf4beab6d4 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f02df2e9c1..a96cfe4690 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -95,19 +95,26 @@ namespace Avalonia.Controls { ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); - SelectedItemProperty.Changed.AddClassHandler((x, e) => x.SelectedItemChanged(e)); - KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); IsTextSearchEnabledProperty.OverrideDefaultValue(true); - IsDropDownOpenProperty.Changed.AddClassHandler((x, e) => x.DropdownChanged(e)); } + /// + /// Occurs after the drop-down (popup) list of the closes. + /// + public event EventHandler? DropDownClosed; + + /// + /// Occurs after the drop-down (popup) list of the opens. + /// + public event EventHandler? DropDownOpened; + /// /// Gets or sets a value indicating whether the dropdown is currently open. /// public bool IsDropDownOpen { - get { return _isDropDownOpen; } - set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } + get => _isDropDownOpen; + set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } /// @@ -115,8 +122,8 @@ namespace Avalonia.Controls /// public double MaxDropDownHeight { - get { return GetValue(MaxDropDownHeightProperty); } - set { SetValue(MaxDropDownHeightProperty, value); } + get => GetValue(MaxDropDownHeightProperty); + set => SetValue(MaxDropDownHeightProperty, value); } /// @@ -124,8 +131,8 @@ namespace Avalonia.Controls /// protected object? SelectionBoxItem { - get { return _selectionBoxItem; } - set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } + get => _selectionBoxItem; + set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } /// @@ -133,8 +140,8 @@ namespace Avalonia.Controls /// public string? PlaceholderText { - get { return GetValue(PlaceholderTextProperty); } - set { SetValue(PlaceholderTextProperty, value); } + get => GetValue(PlaceholderTextProperty); + set => SetValue(PlaceholderTextProperty, value); } /// @@ -142,8 +149,8 @@ namespace Avalonia.Controls /// public IBrush? PlaceholderForeground { - get { return GetValue(PlaceholderForegroundProperty); } - set { SetValue(PlaceholderForegroundProperty, value); } + get => GetValue(PlaceholderForegroundProperty); + set => SetValue(PlaceholderForegroundProperty, value); } /// @@ -151,8 +158,8 @@ namespace Avalonia.Controls /// public ItemVirtualizationMode VirtualizationMode { - get { return GetValue(VirtualizationModeProperty); } - set { SetValue(VirtualizationModeProperty, value); } + get => GetValue(VirtualizationModeProperty); + set => SetValue(VirtualizationModeProperty, value); } /// @@ -160,8 +167,8 @@ namespace Avalonia.Controls /// public HorizontalAlignment HorizontalContentAlignment { - get { return GetValue(HorizontalContentAlignmentProperty); } - set { SetValue(HorizontalContentAlignmentProperty, value); } + get => GetValue(HorizontalContentAlignmentProperty); + set => SetValue(HorizontalContentAlignmentProperty, value); } /// @@ -169,8 +176,8 @@ namespace Avalonia.Controls /// public VerticalAlignment VerticalContentAlignment { - get { return GetValue(VerticalContentAlignmentProperty); } - set { SetValue(VerticalContentAlignmentProperty, value); } + get => GetValue(VerticalContentAlignmentProperty); + set => SetValue(VerticalContentAlignmentProperty, value); } /// @@ -182,6 +189,7 @@ namespace Avalonia.Controls ComboBoxItem.ContentTemplateProperty); } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -213,7 +221,12 @@ namespace Avalonia.Controls IsDropDownOpen = false; e.Handled = true; } - else if (IsDropDownOpen && e.Key == Key.Enter) + else if (!IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space)) + { + IsDropDownOpen = true; + e.Handled = true; + } + else if (IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space)) { SelectFocusedItem(); IsDropDownOpen = false; @@ -234,7 +247,7 @@ namespace Avalonia.Controls } // This part of code is needed just to acquire initial focus, subsequent focus navigation will be done by ItemsControl. else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 && - (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true) + (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true) { var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c)); if (firstChild != null) @@ -304,12 +317,11 @@ namespace Avalonia.Controls e.Handled = true; } } + PseudoClasses.Set(pcPressed, false); base.OnPointerReleased(e); - } - /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { @@ -324,6 +336,22 @@ namespace Avalonia.Controls _popup.Closed += PopupClosed; } + /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + if (change.Property == SelectedItemProperty) + { + UpdateSelectionBoxItem(change.NewValue); + TryFocusSelectedItem(); + } + else if (change.Property == IsDropDownOpenProperty) + { + PseudoClasses.Set(pcDropdownOpen, change.GetNewValue()); + } + + base.OnPropertyChanged(change); + } + protected override AutomationPeer OnCreateAutomationPeer() { return new ComboBoxAutomationPeer(this); @@ -345,6 +373,8 @@ namespace Avalonia.Controls { Focus(); } + + DropDownClosed?.Invoke(this, EventArgs.Empty); } private void PopupOpened(object? sender, EventArgs e) @@ -374,6 +404,8 @@ namespace Avalonia.Controls } UpdateFlowDirection(); + + DropDownOpened?.Invoke(this, EventArgs.Empty); } private void IsVisibleChanged(bool isVisible) @@ -384,12 +416,6 @@ namespace Avalonia.Controls } } - private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e) - { - UpdateSelectionBoxItem(e.NewValue); - TryFocusSelectedItem(); - } - private void TryFocusSelectedItem() { var selectedIndex = SelectedIndex; @@ -489,11 +515,5 @@ namespace Avalonia.Controls MoveSelection(NavigationDirection.Previous, WrapSelection); } } - - private void DropdownChanged(AvaloniaPropertyChangedEventArgs e) - { - bool newValue = e.GetNewValue(); - PseudoClasses.Set(pcDropdownOpen, newValue); - } } } diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index 5ff0fd1feb..b79fef55b9 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs @@ -4,7 +4,6 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Styling; -using JetBrains.Annotations; namespace Avalonia.Controls.Embedding { @@ -12,7 +11,6 @@ namespace Avalonia.Controls.Embedding { public EmbeddableControlRoot(ITopLevelImpl impl) : base(impl) { - } public EmbeddableControlRoot() : base(PlatformManager.CreateEmbeddableWindow()) diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 4f80892857..4a227a0c00 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -13,6 +13,7 @@ namespace Avalonia.Controls.Embedding.Offscreen { private double _scaling = 1; private Size _clientSize; + private PlatformRenderInterfaceContextManager _renderInterface = new(null); public IInputRoot? InputRoot { get; private set; } public bool IsDisposed { get; private set; } @@ -22,7 +23,8 @@ namespace Avalonia.Controls.Embedding.Offscreen IsDisposed = true; } - public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root); + public IRenderer CreateRenderer(IRenderRoot root) => + new ImmediateRenderer((Visual)root, () => _renderInterface.CreateRenderTarget(Surfaces), _renderInterface); public abstract void Invalidate(Rect rect); public abstract IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 3ff248f0be..8455495830 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -435,7 +435,7 @@ namespace Avalonia.Controls.Primitives { Size sz; // Popup.Child can't be null here, it was set in ShowAtCore. - if (Popup.Child!.DesiredSize == Size.Empty) + if (Popup.Child!.DesiredSize.IsDefault) { // Popup may not have been shown yet. Measure content sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness()); @@ -457,7 +457,7 @@ namespace Avalonia.Controls.Primitives PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; } - var trgtBnds = Target?.Bounds ?? Rect.Empty; + var trgtBnds = Target?.Bounds ?? default; switch (Placement) { diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 5668b79e81..766a712c88 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -91,7 +91,7 @@ namespace Avalonia.Controls arrangedsize = TransformRoot.Bounds.Size; // This is the first opportunity under Silverlight to find out the Child's true DesiredSize - if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && (Size.Empty == _childActualSize)) + if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && _childActualSize.IsDefault) { //// Unfortunately, all the work so far is invalid because the wrong DesiredSize was used //// Make a note of the actual DesiredSize @@ -102,7 +102,7 @@ namespace Avalonia.Controls else { // Clear the "need to measure/arrange again" flag - _childActualSize = Size.Empty; + _childActualSize = default; } // Return result to perform the transformation @@ -122,7 +122,7 @@ namespace Avalonia.Controls } Size measureSize; - if (_childActualSize == Size.Empty) + if (_childActualSize.IsDefault) { // Determine the largest size after the transformation measureSize = ComputeLargestTransformedSize(availableSize); @@ -206,7 +206,7 @@ namespace Avalonia.Controls /// /// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method). /// - private Size _childActualSize = Size.Empty; + private Size _childActualSize = default; /// /// RenderTransform/MatrixTransform applied to TransformRoot. @@ -281,7 +281,7 @@ namespace Avalonia.Controls private Size ComputeLargestTransformedSize(Size arrangeBounds) { // Computed largest transformed size - Size computedSize = Size.Empty; + Size computedSize = default; // Detect infinite bounds and constrain the scenario bool infiniteWidth = double.IsInfinity(arrangeBounds.Width); diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index bcf0866129..18dc1b1264 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -145,7 +145,7 @@ namespace Avalonia.Controls if (IsEffectivelyVisible && bounds.HasValue) { - if (bounds.Value.IsEmpty) + if (bounds.Value.IsDefault) return false; _attachment?.ShowInBounds(bounds.Value); } diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs index 8b3e875e5a..ac7a45a547 100644 --- a/src/Avalonia.Controls/NativeMenuBar.cs +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -23,15 +23,6 @@ namespace Avalonia.Controls }); } - [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenu))] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItem))] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemBase))] - [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NativeMenuItemSeparator))] - public NativeMenuBar() - { - - } - public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable) { menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable); diff --git a/src/Avalonia.Controls/NativeMenuItemSeparator.cs b/src/Avalonia.Controls/NativeMenuItemSeparator.cs index 97278130b1..f55d714884 100644 --- a/src/Avalonia.Controls/NativeMenuItemSeparator.cs +++ b/src/Avalonia.Controls/NativeMenuItemSeparator.cs @@ -1,7 +1,10 @@ namespace Avalonia.Controls { - public class NativeMenuItemSeparator : NativeMenuItemBase + public class NativeMenuItemSeparator : NativeMenuItem { - + public NativeMenuItemSeparator() + { + Header = "-"; + } } } diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index af9392d440..8d9d8e0e7b 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -68,7 +68,7 @@ namespace Avalonia.Platform /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. /// - Func Closing { get; set; } + Func Closing { get; set; } /// /// Gets a value to indicate if the platform was able to extend client area to non-client area. diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 924c4567f6..96c9b7b5d9 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -77,7 +77,7 @@ namespace Avalonia.Controls.Presenters } /// - Size IScrollable.Extent => Virtualizer?.Extent ?? Size.Empty; + Size IScrollable.Extent => Virtualizer?.Extent ?? default; /// Vector IScrollable.Offset @@ -136,12 +136,12 @@ namespace Avalonia.Controls.Presenters /// protected override Size MeasureOverride(Size availableSize) { - return Virtualizer?.MeasureOverride(availableSize) ?? Size.Empty; + return Virtualizer?.MeasureOverride(availableSize) ?? default; } protected override Size ArrangeOverride(Size finalSize) { - return Virtualizer?.ArrangeOverride(finalSize) ?? Size.Empty; + return Virtualizer?.ArrangeOverride(finalSize) ?? default; } /// diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index cc1fa6c513..480a8ee7a7 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -106,11 +106,10 @@ namespace Avalonia.Controls.Presenters private Rect _caretBounds; private Point _navigationPosition; private string? _preeditText; - private CharacterHit _compositionStartHit = new CharacterHit(-1); static TextPresenter() { - AffectsRender(CaretBrushProperty, SelectionBrushProperty); + AffectsRender(CaretBrushProperty, SelectionBrushProperty, TextElement.ForegroundProperty); } public TextPresenter() diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index 17dfec118f..2e70947457 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -64,7 +64,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// surface. /// [Unstable] - public struct PopupPositionerParameters + public record struct PopupPositionerParameters { private PopupGravity _gravity; private PopupAnchor _anchor; diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index a80a60350e..62e5f37273 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs @@ -112,7 +112,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning ?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry)) ?? screens.FirstOrDefault(); - if (targetScreen != null && targetScreen.WorkingArea.IsEmpty) + if (targetScreen != null && targetScreen.WorkingArea.IsDefault) { return targetScreen.Bounds; } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 44fa78ac21..795bc05e23 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -119,8 +119,6 @@ namespace Avalonia.Controls.Primitives /// public static readonly StyledProperty WrapSelectionProperty = AvaloniaProperty.Register(nameof(WrapSelection), defaultValue: false); - - private static readonly IList Empty = Array.Empty(); private string _textSearchTerm = string.Empty; private DispatcherTimer? _textSearchTimer; private ISelectionModel? _selection; diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index e24ed37f1e..10e3dd57a0 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -465,7 +465,7 @@ namespace Avalonia.Controls _pendingViewportShift = default; _unshiftableShift = default; - if (_visibleWindow.IsEmpty) + if (_visibleWindow.IsDefault) { // We got cleared. _layoutExtent = default; @@ -551,7 +551,7 @@ namespace Avalonia.Controls private void TryInvalidateMeasure() { // Don't invalidate measure if we have an invalid window. - if (!_visibleWindow.IsEmpty) + if (!_visibleWindow.IsDefault) { // We invalidate measure instead of just invalidating arrange because // we don't invalidate measure in UpdateViewport if the view is changing to diff --git a/src/Avalonia.Controls/Selection/ISelectionModel.cs b/src/Avalonia.Controls/Selection/ISelectionModel.cs index 3f4ae48263..4c2a355bb5 100644 --- a/src/Avalonia.Controls/Selection/ISelectionModel.cs +++ b/src/Avalonia.Controls/Selection/ISelectionModel.cs @@ -39,7 +39,7 @@ namespace Avalonia.Controls.Selection return new BatchUpdateOperation(model); } - public struct BatchUpdateOperation : IDisposable + public record struct BatchUpdateOperation : IDisposable { private readonly ISelectionModel _owner; private bool _isDisposed; diff --git a/src/Avalonia.Controls/Selection/SelectedItems.cs b/src/Avalonia.Controls/Selection/SelectedItems.cs index 4fbcfde438..ef642b7bdc 100644 --- a/src/Avalonia.Controls/Selection/SelectedItems.cs +++ b/src/Avalonia.Controls/Selection/SelectedItems.cs @@ -35,9 +35,9 @@ namespace Avalonia.Controls.Selection { return _owner.SelectedItem; } - else if (Items is object) + else if (Items is not null && Ranges is not null) { - return Items[index]; + return Items[IndexRange.GetAt(Ranges, index)]; } else { diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs index 154f1868f3..9bbddfcbb2 100644 --- a/src/Avalonia.Controls/Selection/SelectionModel.cs +++ b/src/Avalonia.Controls/Selection/SelectionModel.cs @@ -738,7 +738,7 @@ namespace Avalonia.Controls.Selection } } - public struct BatchUpdateOperation : IDisposable + public record struct BatchUpdateOperation : IDisposable { private readonly SelectionModel _owner; private bool _isDisposed; diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 1a7218ff2a..e2a13512a5 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -292,7 +292,7 @@ namespace Avalonia.Controls.Shapes return finalSize; } - return Size.Empty; + return default; } internal static (Size size, Matrix transform) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch) diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 08156ae00f..7a0da1ecc5 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -827,7 +827,7 @@ namespace Avalonia.Controls InvalidateTextLayout(); } - protected readonly struct SimpleTextSource : ITextSource + protected readonly record struct SimpleTextSource : ITextSource { private readonly CharacterBufferRange _text; private readonly TextRunProperties _defaultProperties; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index d2d5a8309c..2cd55dc3ab 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -323,17 +323,7 @@ namespace Avalonia.Controls ??= AvaloniaLocator.Current.GetService()?.CreateProvider(this) ?? (PlatformImpl as ITopLevelImplWithStorageProvider)?.StorageProvider ?? throw new InvalidOperationException("StorageProvider platform implementation is not available."); - - IRenderTarget IRenderRoot.CreateRenderTarget() => CreateRenderTarget(); - - /// - protected virtual IRenderTarget CreateRenderTarget() - { - if(PlatformImpl == null) - throw new InvalidOperationException("Can't create render target, PlatformImpl is null (might be already disposed)"); - return _renderInterface!.CreateRenderTarget(PlatformImpl.Surfaces); - } - + /// void IRenderRoot.Invalidate(Rect rect) { diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index eb9f38894d..6239a5120d 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -4,7 +4,6 @@ using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; using Avalonia.Utilities; -using JetBrains.Annotations; namespace Avalonia.Controls.Utils { diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index a893c74324..88f3b520ce 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -169,10 +169,6 @@ namespace Avalonia.Controls /// public static readonly RoutedEvent WindowOpenedEvent = RoutedEvent.Register("WindowOpened", RoutingStrategies.Direct); - - - - private readonly NameScope _nameScope = new NameScope(); private object? _dialogResult; private readonly Size _maxPlatformClientSize; private WindowStartupLocation _windowStartupLocation; @@ -235,7 +231,7 @@ namespace Avalonia.Controls impl.GotInputWhenDisabled = OnGotInputWhenDisabled; impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); - impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; + impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, PlatformResizeReason.Application)); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar); @@ -434,14 +430,14 @@ namespace Avalonia.Controls /// /// Fired before a window is closed. /// - public event EventHandler? Closing; + public event EventHandler? Closing; /// /// Closes the window. /// public void Close() { - Close(false); + CloseCore(WindowCloseReason.WindowClosing, true); } /// @@ -457,16 +453,16 @@ namespace Avalonia.Controls public void Close(object dialogResult) { _dialogResult = dialogResult; - Close(false); + CloseCore(WindowCloseReason.WindowClosing, true); } - internal void Close(bool ignoreCancel) + internal void CloseCore(WindowCloseReason reason, bool isProgrammatic) { bool close = true; try { - if (!ignoreCancel && ShouldCancelClose()) + if (ShouldCancelClose(new WindowClosingEventArgs(reason, isProgrammatic))) { close = false; } @@ -484,9 +480,10 @@ namespace Avalonia.Controls /// Handles a closing notification from . /// true if closing is cancelled. Otherwise false. /// - protected virtual bool HandleClosing() + /// The reason the window is closing. + private protected virtual bool HandleClosing(WindowCloseReason reason) { - if (!ShouldCancelClose()) + if (!ShouldCancelClose(new WindowClosingEventArgs(reason, false))) { CloseInternal(); return false; @@ -514,20 +511,22 @@ namespace Avalonia.Controls _showingAsDialog = false; } - private bool ShouldCancelClose(CancelEventArgs? args = null) + private bool ShouldCancelClose(WindowClosingEventArgs args) { - if (args is null) - { - args = new CancelEventArgs(); - } - bool canClose = true; - foreach (var (child, _) in _children.ToArray()) + if (_children.Count > 0) { - if (child.ShouldCancelClose(args)) + var childArgs = args.CloseReason == WindowCloseReason.WindowClosing ? + new WindowClosingEventArgs(WindowCloseReason.OwnerWindowClosing, args.IsProgrammatic) : + args; + + foreach (var (child, _) in _children.ToArray()) { - canClose = false; + if (child.ShouldCancelClose(childArgs)) + { + canClose = false; + } } } @@ -1037,7 +1036,7 @@ namespace Avalonia.Controls /// overridden method must call on the base class if the /// event needs to be raised. /// - protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); + protected virtual void OnClosing(WindowClosingEventArgs e) => Closing?.Invoke(this, e); protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index b71dc6df44..65325aac92 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -8,7 +8,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Platform; -using JetBrains.Annotations; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/WindowClosingEventArgs.cs b/src/Avalonia.Controls/WindowClosingEventArgs.cs new file mode 100644 index 0000000000..7cfae2d005 --- /dev/null +++ b/src/Avalonia.Controls/WindowClosingEventArgs.cs @@ -0,0 +1,57 @@ +using System.ComponentModel; + +namespace Avalonia.Controls +{ + /// + /// Specifies the reason that a window was closed. + /// + public enum WindowCloseReason + { + /// + /// The cause of the closure was not provided by the underlying platform. + /// + Undefined, + + /// + /// The window itself was requested to close. + /// + WindowClosing, + + /// + /// The window is closing due to a parent/owner window closing. + /// + OwnerWindowClosing, + + /// + /// The window is closing due to the application shutting down. + /// + ApplicationShutdown, + + /// + /// The window is closing due to the operating system shutting down. + /// + OSShutdown, + } + + /// + /// Provides data for the event. + /// + public class WindowClosingEventArgs : CancelEventArgs + { + internal WindowClosingEventArgs(WindowCloseReason reason, bool isProgrammatic) + { + CloseReason = reason; + IsProgrammatic = isProgrammatic; + } + + /// + /// Gets a value that indicates why the window is being closed. + /// + public WindowCloseReason CloseReason { get; } + + /// + /// Gets a value indicating whether the window is being closed programmatically. + /// + public bool IsProgrammatic { get; } + } +} diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 037c5e0c71..020e09526e 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -42,7 +42,7 @@ namespace Avalonia.DesignerSupport.Remote public Action PositionChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } - public Func Closing { get; set; } + public Func Closing { get; set; } public IPlatformHandle Handle { get; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 71389be1a6..94679e8ade 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -32,7 +32,7 @@ namespace Avalonia.DesignerSupport.Remote public Action Paint { get; set; } public Action Resized { get; set; } public Action ScalingChanged { get; set; } - public Func Closing { get; set; } + public Func Closing { get; set; } public Action Closed { get; set; } public Action LostFocus { get; set; } public IMouseDevice MouseDevice { get; } = new MouseDevice(); @@ -61,7 +61,10 @@ namespace Avalonia.DesignerSupport.Remote })); } - public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root); + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root, () => + new PlatformRenderInterfaceContextManager(null) + .CreateRenderTarget(Surfaces)); + public void Dispose() { } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index 42c5ea1fa9..00173dbb35 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -10,8 +10,7 @@ namespace Avalonia.Diagnostics.Controls { private readonly App _application; - private static readonly Version s_version = typeof(AvaloniaObject).Assembly?.GetName()?.Version - ?? Version.Parse("0.0.00"); + public event EventHandler? Closed; public Application(App application) diff --git a/src/Avalonia.FreeDesktop/IX11InputMethod.cs b/src/Avalonia.FreeDesktop/IX11InputMethod.cs index 5d91118978..9fa9c1809e 100644 --- a/src/Avalonia.FreeDesktop/IX11InputMethod.cs +++ b/src/Avalonia.FreeDesktop/IX11InputMethod.cs @@ -11,7 +11,9 @@ namespace Avalonia.FreeDesktop (ITextInputMethodImpl method, IX11InputMethodControl control) CreateClient(IntPtr xid); } +#pragma warning disable CA1815 // Override equals and operator equals on value types public struct X11InputMethodForwardedKey +#pragma warning restore CA1815 // Override equals and operator equals on value types { public int KeyVal { get; set; } public KeyModifiers Modifiers { get; set; } diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs index b69ea68a76..4624a9c340 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs @@ -9,8 +9,7 @@ namespace Avalonia.FreeDesktop { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); - return new LinuxMountedVolumeInfoListener(ref mountedDrives!); + return new LinuxMountedVolumeInfoListener(ref mountedDrives); } } } diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 7abc0ca131..9bb4b34976 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -12,7 +12,7 @@ using Avalonia.Media.Imaging; namespace Avalonia.Headless { - internal class HeadlessPlatformRenderInterface : IPlatformRenderInterface + internal class HeadlessPlatformRenderInterface : IPlatformRenderInterface, IPlatformRenderInterfaceContext { public static void Initialize() { @@ -22,6 +22,8 @@ namespace Avalonia.Headless public IEnumerable InstalledFontNames { get; } = new[] { "Tahoma" }; + public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this; + public bool SupportsIndividualRoundRects => false; public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; @@ -47,6 +49,7 @@ namespace Avalonia.Headless public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => throw new NotImplementedException(); public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => new HeadlessRenderTarget(); + public object TryGetFeature(Type featureType) => null; public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { @@ -212,7 +215,7 @@ namespace Avalonia.Headless class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl { - public HeadlessStreamingGeometryStub() : base(Rect.Empty) + public HeadlessStreamingGeometryStub() : base(default) { } @@ -313,6 +316,8 @@ namespace Avalonia.Headless return new HeadlessDrawingContextStub(); } + public bool IsCorrupted => false; + public void Blit(IDrawingContextImpl context) { @@ -474,6 +479,13 @@ namespace Avalonia.Headless { return new HeadlessDrawingContextStub(); } + + public bool IsCorrupted => false; + } + + public void Dispose() + { + } } } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 742df3324b..8eafce208b 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -56,8 +56,9 @@ namespace Avalonia.Headless public IRenderer CreateRenderer(IRenderRoot root) => AvaloniaHeadlessPlatform.Compositor != null - ? new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor) - : new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()); + ? new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces) + : new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService(), + () => new PlatformRenderInterfaceContextManager(null).CreateRenderTarget(Surfaces), null); public void Invalidate(Rect rect) { @@ -174,7 +175,7 @@ namespace Avalonia.Headless } - public Func Closing { get; set; } + public Func Closing { get; set; } class FramebufferProxy : ILockedFramebuffer { diff --git a/src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs similarity index 75% rename from src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs rename to src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs index 14d27a90e9..8e40960af6 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs +++ b/src/Avalonia.Native/AvaloniaNativeGlPlatformGraphics.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.OpenGL; using Avalonia.Native.Interop; using System.Drawing; @@ -8,18 +9,18 @@ using Avalonia.Threading; namespace Avalonia.Native { - class AvaloniaNativePlatformOpenGlInterface : IPlatformOpenGlInterface + class AvaloniaNativeGlPlatformGraphics : IPlatformGraphics { private readonly IAvnGlDisplay _display; - public AvaloniaNativePlatformOpenGlInterface(IAvnGlDisplay display) + public AvaloniaNativeGlPlatformGraphics(IAvnGlDisplay display) { _display = display; - var immediate = display.CreateContext(null); + var context = display.CreateContext(null); int major, minor; GlInterface glInterface; - using (immediate.MakeCurrent()) + using (context.MakeCurrent()) { var basic = new GlBasicInfoInterface(display.GetProcAddress); basic.GetIntegerv(GlConsts.GL_MAJOR_VERSION, out major); @@ -32,24 +33,23 @@ namespace Avalonia.Native }); } - GlDisplay = new GlDisplay(display, glInterface, immediate.SampleCount, immediate.StencilSize); - MainContext = new GlContext(GlDisplay, null, immediate, _version); + GlDisplay = new GlDisplay(display, glInterface, context.SampleCount, context.StencilSize); + SharedContext =(GlContext)CreateContext(); } - internal GlContext MainContext { get; } - public IGlContext PrimaryContext => MainContext; - IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext; + + public bool UsesSharedContext => true; + public IPlatformGraphicsContext CreateContext() => new GlContext(GlDisplay, + null, _display.CreateContext(null), _version); + + public IPlatformGraphicsContext GetSharedContext() => SharedContext; + public bool CanShareContexts => true; public bool CanCreateContexts => true; internal GlDisplay GlDisplay; private readonly GlVersion _version; - - public IGlContext CreateSharedContext() => new GlContext(GlDisplay, - MainContext, _display.CreateContext(MainContext.Context), _version); - - public IGlContext CreateContext() => new GlContext(GlDisplay, - null, _display.CreateContext(null), _version); + internal GlContext SharedContext { get; } } class GlDisplay @@ -71,6 +71,10 @@ namespace Avalonia.Native public int StencilSize { get; } public void ClearContext() => _display.LegacyClearCurrentContext(); + + public GlContext CreateSharedContext(GlContext share) => + new GlContext(this, share, _display.CreateContext(share.Context), share.Version); + } class GlContext : IGlContext @@ -91,7 +95,14 @@ namespace Avalonia.Native public GlInterface GlInterface => _display.GlInterface; public int SampleCount => _display.SampleCount; public int StencilSize => _display.StencilSize; - public IDisposable MakeCurrent() => Context.MakeCurrent(); + public IDisposable MakeCurrent() + { + if (IsLost) + throw new PlatformGraphicsContextLostException(); + return Context.MakeCurrent(); + } + + public bool IsLost => Context == null; public IDisposable EnsureCurrent() => MakeCurrent(); public bool IsSharedWith(IGlContext context) @@ -103,12 +114,18 @@ namespace Avalonia.Native || _sharedWith != null && _sharedWith == c._sharedWith; } + public bool CanCreateSharedContext => true; + + public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) => + _display.CreateSharedContext(_sharedWith ?? this); public void Dispose() { Context.Dispose(); Context = null; } + + public object TryGetFeature(Type featureType) => null; } @@ -125,7 +142,6 @@ namespace Avalonia.Native public IGlPlatformSurfaceRenderingSession BeginDraw() { - var feature = (AvaloniaNativePlatformOpenGlInterface)AvaloniaLocator.Current.GetService(); return new GlPlatformSurfaceRenderingSession(_context, _target.BeginDrawing()); } @@ -172,16 +188,14 @@ namespace Avalonia.Native class GlPlatformSurface : IGlPlatformSurface { private readonly IAvnWindowBase _window; - private readonly IGlContext _context; - - public GlPlatformSurface(IAvnWindowBase window, IGlContext context) + public GlPlatformSurface(IAvnWindowBase window) { _window = window; - _context = context; } - public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { - return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), _context); + var avnContext = (GlContext)context; + return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), avnContext); } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index db6b6f57cd..6d5925c0ae 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -9,22 +9,23 @@ using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; -using JetBrains.Annotations; using MicroCom.Runtime; +#nullable enable namespace Avalonia.Native { class AvaloniaNativePlatform : IWindowingPlatform { private readonly IAvaloniaNativeFactory _factory; - private AvaloniaNativePlatformOptions _options; - private AvaloniaNativePlatformOpenGlInterface _platformGl; + private AvaloniaNativePlatformOptions? _options; + private AvaloniaNativeGlPlatformGraphics? _platformGl; [DllImport("libAvaloniaNative")] static extern IntPtr CreateAvaloniaNative(); internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice(); - [CanBeNull] internal static Compositor Compositor { get; private set; } + internal static Compositor? Compositor { get; private set; } + internal static PlatformRenderInterfaceContextManager? RenderInterface { get; private set; } public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) { @@ -62,7 +63,7 @@ namespace Avalonia.Native public void SetupApplicationName() { - if (!string.IsNullOrWhiteSpace(Application.Current.Name)) + if (!string.IsNullOrWhiteSpace(Application.Current!.Name)) { _factory.MacOptions.SetApplicationTitle(Application.Current.Name); } @@ -117,19 +118,21 @@ namespace Avalonia.Native AvaloniaLocator.CurrentMutable.Bind().ToConstant(renderLoop); var hotkeys = AvaloniaLocator.Current.GetService(); - hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); - hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); - hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); - hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + if (hotkeys is not null) + { + hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); + hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); + hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + } if (_options.UseGpu) { try { - _platformGl = new AvaloniaNativePlatformOpenGlInterface(_factory.ObtainGlDisplay()); + _platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay()); AvaloniaLocator.CurrentMutable - .Bind().ToConstant(_platformGl) - .Bind().ToConstant(_platformGl); + .Bind().ToConstant(_platformGl); } catch (Exception) @@ -137,12 +140,14 @@ namespace Avalonia.Native // ignored } } - + if (_options.UseDeferredRendering && _options.UseCompositor) { Compositor = new Compositor(renderLoop, _platformGl); } + else + RenderInterface = new PlatformRenderInterfaceContextManager(_platformGl); } public ITrayIconImpl CreateTrayIcon() diff --git a/src/Avalonia.Native/ClipboardImpl.cs b/src/Avalonia.Native/ClipboardImpl.cs index 4ee590516b..9f1c8883aa 100644 --- a/src/Avalonia.Native/ClipboardImpl.cs +++ b/src/Avalonia.Native/ClipboardImpl.cs @@ -15,8 +15,7 @@ namespace Avalonia.Native private IAvnClipboard _native; private const string NSPasteboardTypeString = "public.utf8-plain-text"; private const string NSFilenamesPboardType = "NSFilenamesPboardType"; - private const string NSPasteboardTypeFileUrl = "public.file-url"; - + public ClipboardImpl(IAvnClipboard native) { _native = native; diff --git a/src/Avalonia.Native/Helpers.cs b/src/Avalonia.Native/Helpers.cs index 764ff789dc..6270cd30a0 100644 --- a/src/Avalonia.Native/Helpers.cs +++ b/src/Avalonia.Native/Helpers.cs @@ -1,5 +1,4 @@ using Avalonia.Native.Interop; -using JetBrains.Annotations; namespace Avalonia.Native { diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs index 3e46e0c5c6..e6a5a371d0 100644 --- a/src/Avalonia.Native/IAvnMenu.cs +++ b/src/Avalonia.Native/IAvnMenu.cs @@ -46,7 +46,6 @@ namespace Avalonia.Native.Interop.Impl private AvaloniaNativeMenuExporter _exporter; private List<__MicroComIAvnMenuItemProxy> _menuItems = new List<__MicroComIAvnMenuItemProxy>(); private Dictionary _menuItemLookup = new Dictionary(); - private CompositeDisposable _propertyDisposables = new CompositeDisposable(); public void RaiseNeedsUpdate() { diff --git a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs index 92b2915e2e..1907a3d129 100644 --- a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs +++ b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs @@ -72,7 +72,6 @@ namespace Avalonia.Native { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); return new MacOSMountedVolumeInfoListener(mountedDrives); } } diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 76d3905b47..0953527284 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -8,12 +8,12 @@ namespace Avalonia.Native class PopupImpl : WindowBaseImpl, IPopupImpl { private readonly AvaloniaNativePlatformOptions _opts; - private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; + private readonly AvaloniaNativeGlPlatformGraphics _glFeature; private readonly IWindowBaseImpl _parent; public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - AvaloniaNativePlatformOpenGlInterface glFeature, + AvaloniaNativeGlPlatformGraphics glFeature, IWindowBaseImpl parent) : base(factory, opts, glFeature) { _opts = opts; @@ -21,8 +21,7 @@ namespace Avalonia.Native _parent = parent; using (var e = new PopupEvents(this)) { - var context = _opts.UseGpu ? glFeature?.MainContext : null; - Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context); + Init(factory.CreatePopup(e, _glFeature.SharedContext.Context), factory.CreateScreens()); } PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 9de794ed53..880a385744 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -14,14 +14,14 @@ namespace Avalonia.Native internal class WindowImpl : WindowBaseImpl, IWindowImpl, ITopLevelImplWithNativeMenuExporter { private readonly AvaloniaNativePlatformOptions _opts; - private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; + private readonly AvaloniaNativeGlPlatformGraphics _glFeature; IAvnWindow _native; private double _extendTitleBarHeight = -1; private DoubleClickHelper _doubleClickHelper; internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - AvaloniaNativePlatformOpenGlInterface glFeature) : base(factory, opts, glFeature) + AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature) { _opts = opts; _glFeature = glFeature; @@ -29,8 +29,7 @@ namespace Avalonia.Native using (var e = new WindowEvents(this)) { - var context = _opts.UseGpu ? glFeature?.MainContext : null; - Init(_native = factory.CreateWindow(e, context?.Context), factory.CreateScreens(), context); + Init(_native = factory.CreateWindow(e, glFeature.SharedContext.Context), factory.CreateScreens()); } NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory); @@ -49,7 +48,7 @@ namespace Avalonia.Native { if (_parent.Closing != null) { - return _parent.Closing().AsComBool(); + return _parent.Closing(WindowCloseReason.WindowClosing).AsComBool(); } return true.AsComBool(); @@ -208,7 +207,7 @@ namespace Avalonia.Native // NO OP on OSX } - public Func Closing { get; set; } + public Func Closing { get; set; } public ITopLevelNativeMenuExporter NativeMenuExporter { get; } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 4744a20107..ca57e30b1c 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -63,10 +63,9 @@ namespace Avalonia.Native private double _savedScaling; private GlPlatformSurface _glSurface; private NativeControlHostImpl _nativeControlHost; - private IGlContext _glContext; internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - AvaloniaNativePlatformOpenGlInterface glFeature) + AvaloniaNativeGlPlatformGraphics glFeature) { _factory = factory; _gpu = opts.UseGpu && glFeature != null; @@ -78,14 +77,13 @@ namespace Avalonia.Native StorageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs()); } - protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext) + protected void Init(IAvnWindowBase window, IAvnScreens screens) { _native = window; - _glContext = glContext; Handle = new MacOSTopLevelWindowHandle(window); if (_gpu) - _glSurface = new GlPlatformSurface(window, _glContext); + _glSurface = new GlPlatformSurface(window); Screen = new ScreenImpl(screens); _savedLogicalSize = ClientSize; @@ -375,14 +373,17 @@ namespace Avalonia.Native if (_deferredRendering) { if (AvaloniaNativePlatform.Compositor != null) - return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor) + return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor, () => Surfaces) { RenderOnlyOnRenderThread = false }; - return new DeferredRenderer(root, loop); + return new DeferredRenderer(root, loop, + () => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces), + AvaloniaNativePlatform.RenderInterface); } - return new ImmediateRenderer((Visual)root); + return new ImmediateRenderer((Visual)root, + () => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces), AvaloniaNativePlatform.RenderInterface); } public virtual void Dispose() diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs deleted file mode 100644 index 3a8fdb8491..0000000000 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Avalonia.OpenGL.Egl; -using static Avalonia.OpenGL.Egl.EglConsts; - -namespace Avalonia.OpenGL.Angle -{ - public class AngleWin32EglDisplay : EglDisplay - { - struct AngleInfo - { - public IntPtr Display { get; set; } - public AngleOptions.PlatformApi PlatformApi { get; set; } - } - - static AngleInfo CreateAngleDisplay(EglInterface _egl) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - throw new PlatformNotSupportedException(); - var display = IntPtr.Zero; - AngleOptions.PlatformApi angleApi = default; - { - if (!_egl.IsGetPlatformDisplayExtAvailable) - throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); - - var allowedApis = AvaloniaLocator.Current.GetService()?.AllowedPlatformApis - ?? new [] { AngleOptions.PlatformApi.DirectX11, AngleOptions.PlatformApi.DirectX9 }; - - foreach (var platformApi in allowedApis) - { - int dapi; - if (platformApi == AngleOptions.PlatformApi.DirectX9) - dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE; - else if (platformApi == AngleOptions.PlatformApi.DirectX11) - dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE; - else - continue; - - display = _egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, - new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE }); - if (display != IntPtr.Zero) - { - angleApi = platformApi; - break; - } - } - - if (display == IntPtr.Zero) - throw new OpenGlException("Unable to create ANGLE display"); - return new AngleInfo { Display = display, PlatformApi = angleApi }; - } - } - - private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, false, info.Display) - { - PlatformApi = info.PlatformApi; - } - - public AngleWin32EglDisplay(EglInterface egl) : this(egl, CreateAngleDisplay(egl)) - { - - } - - public AngleWin32EglDisplay() : this(new AngleEglInterface()) - { - - } - - public AngleOptions.PlatformApi PlatformApi { get; } - - public IntPtr GetDirect3DDevice() - { - if (!EglInterface.QueryDisplayAttribExt(Handle, EglConsts.EGL_DEVICE_EXT, out var eglDevice)) - throw new OpenGlException("Unable to get EGL_DEVICE_EXT"); - if (!EglInterface.QueryDeviceAttribExt(eglDevice, PlatformApi == AngleOptions.PlatformApi.DirectX9 ? EGL_D3D9_DEVICE_ANGLE : EGL_D3D11_DEVICE_ANGLE, out var d3dDeviceHandle)) - throw new OpenGlException("Unable to get EGL_D3D9_DEVICE_ANGLE"); - return d3dDeviceHandle; - } - - public EglSurface WrapDirect3D11Texture(EglPlatformOpenGlInterface egl, IntPtr handle) - { - if (PlatformApi != AngleOptions.PlatformApi.DirectX11) - throw new InvalidOperationException("Current platform API is " + PlatformApi); - return egl.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE }); - } - - public EglSurface WrapDirect3D11Texture(EglPlatformOpenGlInterface egl, IntPtr handle, int offsetX, int offsetY, int width, int height) - { - if (PlatformApi != AngleOptions.PlatformApi.DirectX11) - throw new InvalidOperationException("Current platform API is " + PlatformApi); - return egl.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE }); - } - } -} diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index e13ee80864..c62a3d8d2f 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -1,8 +1,10 @@ using System; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Logging; using Avalonia.Media; using Avalonia.OpenGL.Imaging; +using Avalonia.VisualTree; using static Avalonia.OpenGL.GlConsts; namespace Avalonia.OpenGL.Controls @@ -14,8 +16,10 @@ namespace Avalonia.OpenGL.Controls private OpenGlBitmap _bitmap; private IOpenGlBitmapAttachment _attachment; private PixelSize _depthBufferSize; - private bool _glFailed; - private bool _initialized; + + private Task _initialization; + private IOpenGlTextureSharingRenderInterfaceContextFeature _feature; + protected GlVersion GlVersion { get; private set; } public sealed override void Render(DrawingContext context) { @@ -38,13 +42,6 @@ namespace Avalonia.OpenGL.Controls base.Render(context); } - private static void CheckError(GlInterface gl) - { - int err; - while ((err = gl.GetError()) != GL_NO_ERROR) - Console.WriteLine(err); - } - void EnsureTextureAttachment() { _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb); @@ -54,7 +51,7 @@ namespace Avalonia.OpenGL.Controls _attachment = null; _bitmap?.Dispose(); _bitmap = null; - _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96)); + _bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96)); _attachment = _bitmap.CreateFramebufferAttachment(_context); } } @@ -87,9 +84,11 @@ namespace Avalonia.OpenGL.Controls gl.ActiveTexture(GL_TEXTURE0); gl.BindTexture(GL_TEXTURE_2D, 0); gl.BindFramebuffer(GL_FRAMEBUFFER, 0); - gl.DeleteFramebuffer(_fb); + if (_fb != 0) + gl.DeleteFramebuffer(_fb); _fb = 0; - gl.DeleteRenderbuffer(_depthBuffer); + if (_depthBuffer != 0) + gl.DeleteRenderbuffer(_depthBuffer); _depthBuffer = 0; _attachment?.Dispose(); _attachment = null; @@ -98,10 +97,10 @@ namespace Avalonia.OpenGL.Controls try { - if (_initialized) + if (_initialization is { Status: TaskStatus.RanToCompletion, Result: true }) { - _initialized = false; OnOpenGlDeinit(_context.GlInterface, _fb); + _initialization = null; } } finally @@ -111,6 +110,11 @@ namespace Avalonia.OpenGL.Controls } } } + + _fb = _depthBuffer = 0; + _attachment = null; + _bitmap = null; + _feature = null; } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) @@ -119,26 +123,12 @@ namespace Avalonia.OpenGL.Controls base.OnDetachedFromVisualTree(e); } - private bool EnsureInitializedCore() + private bool EnsureInitializedCore(IOpenGlTextureSharingRenderInterfaceContextFeature feature) { - if (_context != null) - return true; - - if (_glFailed) - return false; - - var feature = AvaloniaLocator.Current.GetService(); - if (feature == null) - return false; - if (!feature.CanShareContexts) - { - Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", - "Unable to initialize OpenGL: current platform does not support multithreaded context sharing"); - return false; - } try { _context = feature.CreateSharedContext(); + _feature = feature; } catch (Exception e) { @@ -157,7 +147,7 @@ namespace Avalonia.OpenGL.Controls GlVersion = _context.Version; try { - _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96)); + _bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96)); if (!_bitmap.SupportsContext(_context)) { Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", @@ -212,15 +202,68 @@ namespace Avalonia.OpenGL.Controls return true; } + void ContextLost() + { + _context = null; + _feature = null; + _initialization = null; + _attachment = null; + _bitmap = null; + _fb = 0; + _depthBuffer = 0; + _depthBufferSize = default; + OnOpenGlLost(); + } + private bool EnsureInitialized() { - if (_initialized) - return true; - _glFailed = !(_initialized = EnsureInitializedCore()); - if (_glFailed) + if (_initialization != null) + { + // Check if we've previously failed to initialize OpenGL on this platform + if (_initialization is { IsCompleted: true, Result: false } || + _initialization?.IsFaulted == true) + return false; + + // Check if we are still waiting for init to complete + if (_initialization is { IsCompleted: false }) + return false; + + if (_context.IsLost) + ContextLost(); + else + return true; + } + + _initialization = InitializeAsync(); + return false; + + } + + private async Task InitializeAsync() + { + var contextSharingFeature = + (IOpenGlTextureSharingRenderInterfaceContextFeature) + await this.GetVisualRoot()!.Renderer.TryGetRenderInterfaceFeature( + typeof(IOpenGlTextureSharingRenderInterfaceContextFeature)); + + if (contextSharingFeature == null || !contextSharingFeature.CanCreateSharedContext) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL: current platform does not support multithreaded context sharing"); return false; + } + + if (!EnsureInitializedCore(contextSharingFeature)) + { + DoCleanup(); + return false; + } + using (_context.MakeCurrent()) OnOpenGlInit(_context.GlInterface, _fb); + + InvalidateVisual(); + return true; } @@ -242,6 +285,11 @@ namespace Avalonia.OpenGL.Controls } + protected virtual void OnOpenGlLost() + { + + } + protected abstract void OnOpenGlRender(GlInterface gl, int fb); } } diff --git a/src/Avalonia.OpenGL/Egl/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs index 1268845f0f..428d11857f 100644 --- a/src/Avalonia.OpenGL/Egl/EglConsts.cs +++ b/src/Avalonia.OpenGL/Egl/EglConsts.cs @@ -196,6 +196,11 @@ namespace Avalonia.OpenGL.Egl // public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B; // public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE = 0x320C; +// + // EXT_platform_device + public const int EGL_PLATFORM_DEVICE_EXT = 0x313F; + + //EXT_device_query public const int EGL_DEVICE_EXT = 0x322C; diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index e7b5dc7c83..4d75a776c3 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Reactive.Disposables; using System.Threading; +using Avalonia.Platform; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl @@ -10,24 +12,32 @@ namespace Avalonia.OpenGL.Egl private readonly EglDisplay _disp; private readonly EglInterface _egl; private readonly EglContext _sharedWith; - private readonly object _lock = new object(); + private bool _isLost; + private IntPtr _context; + private readonly Action _disposeCallback; + private readonly Dictionary _features; + private readonly object _lock; - public EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, Func offscreenSurface, - GlVersion version, int sampleCount, int stencilSize) + internal EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, EglSurface offscreenSurface, + GlVersion version, int sampleCount, int stencilSize, Action disposeCallback, Dictionary features) { _disp = display; _egl = egl; _sharedWith = sharedWith; - Context = ctx; - OffscreenSurface = offscreenSurface(this); + _context = ctx; + _disposeCallback = disposeCallback; + _features = features; + OffscreenSurface = offscreenSurface; Version = version; SampleCount = sampleCount; StencilSize = stencilSize; + _lock = display.ContextSharedSyncRoot ?? new object(); using (MakeCurrent()) GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress); } - public IntPtr Context { get; } + public IntPtr Context => + _context == IntPtr.Zero ? throw new ObjectDisposedException(nameof(EglContext)) : _context; public EglSurface OffscreenSurface { get; } public GlVersion Version { get; } public GlInterface GlInterface { get; } @@ -40,7 +50,7 @@ namespace Avalonia.OpenGL.Egl private readonly EglInterface _egl; private readonly object _l; private readonly IntPtr _display; - private IntPtr _context, _read, _draw; + public IntPtr _context, _read, _draw; public RestoreContext(EglInterface egl, IntPtr defDisplay, object l) { @@ -66,6 +76,9 @@ namespace Avalonia.OpenGL.Egl public IDisposable MakeCurrent(EglSurface surface) { + if (IsLost) + throw new PlatformGraphicsContextLostException(); + Monitor.Enter(_lock); var success = false; try @@ -74,8 +87,18 @@ namespace Avalonia.OpenGL.Egl var surf = surface ?? OffscreenSurface; _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); if (!_egl.MakeCurrent(_disp.Handle, surf?.DangerousGetHandle() ?? IntPtr.Zero, - surf?.DangerousGetHandle() ?? IntPtr.Zero, Context)) - throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); + surf?.DangerousGetHandle() ?? IntPtr.Zero, Context)) + { + var error = _egl.GetError(); + if (error == EGL_CONTEXT_LOST) + { + NotifyContextLost(); + throw new PlatformGraphicsContextLostException(); + } + + throw OpenGlException.GetFormattedEglException("eglMakeCurrent", error); + } + success = true; return old; } @@ -85,7 +108,15 @@ namespace Avalonia.OpenGL.Egl Monitor.Exit(_lock); } } + + public void NotifyContextLost() + { + _isLost = true; + _disp.OnContextLost(this); + } + public bool IsLost => _isLost || _disp.IsLost || Context == IntPtr.Zero; + public IDisposable EnsureCurrent() { if(IsCurrent) @@ -110,12 +141,32 @@ namespace Avalonia.OpenGL.Egl || _sharedWith != null && _sharedWith == c._sharedWith; } + public bool CanCreateSharedContext => _disp.SupportsSharing; + + public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) => + _disp.CreateContext(new EglContextOptions + { + ShareWith = _sharedWith ?? this + }); + public bool IsCurrent => _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == Context; public void Dispose() { + if(_context == IntPtr.Zero) + return; _egl.DestroyContext(_disp.Handle, Context); OffscreenSurface?.Dispose(); + _context = IntPtr.Zero; + _disp.OnContextDisposed(this); + _disposeCallback?.Invoke(); + } + + public object TryGetFeature(Type featureType) + { + if (_features?.TryGetValue(featureType, out var feature) == true) + return feature; + return null; } } } diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index d3d85e8c83..eea2587587 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -1,193 +1,179 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; +using System.Threading; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl { - public class EglDisplay + public class EglDisplay : IDisposable { private readonly EglInterface _egl; + private IntPtr _display; + private readonly EglDisplayOptions _options; + private EglConfigInfo _config; + private bool _isLost; + private object _lock = new(); + public bool SupportsSharing { get; } - private readonly IntPtr _display; - private readonly IntPtr _config; - private readonly int[] _contextAttributes; - private readonly int _surfaceType; public IntPtr Handle => _display; - public IntPtr Config => _config; - private int _sampleCount; - private int _stencilSize; - private GlVersion _version; - - public EglDisplay(EglInterface egl, bool supportsSharing) : this(egl, supportsSharing, -1, IntPtr.Zero, null) + public IntPtr Config => _config.Config; + internal bool SingleContext => !_options.SupportsMultipleContexts; + private List _contexts = new(); + + public EglDisplay() : this(new EglDisplayCreationOptions { - - } - - static IntPtr CreateDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs) + Egl = new EglInterface() + }) { - var display = IntPtr.Zero; - if (platformType == -1 && platformDisplay == IntPtr.Zero) - { - if (display == IntPtr.Zero) - display = egl.GetDisplay(IntPtr.Zero); - } - else - { - if (!egl.IsGetPlatformDisplayExtAvailable) - throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl"); - display = egl.GetPlatformDisplayExt(platformType, platformDisplay, attrs); - } - - if (display == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglGetDisplay", egl); - return display; + } - public EglDisplay(EglInterface egl, bool supportsSharing, int platformType, IntPtr platformDisplay, int[] attrs) - : this(egl, supportsSharing, CreateDisplay(egl, platformType, platformDisplay, attrs)) + public EglDisplay(EglDisplayCreationOptions options) : this(EglDisplayUtils.CreateDisplay(options), options) { - + } - - public EglDisplay(EglInterface egl, bool supportsSharing, IntPtr display) + + public EglDisplay(IntPtr display, EglDisplayOptions options) { - _egl = egl; - SupportsSharing = supportsSharing; + _egl = options.Egl; + SupportsSharing = options.SupportsContextSharing; _display = display; + _options = options; if(_display == IntPtr.Zero) throw new ArgumentException(); - - - if (!_egl.Initialize(_display, out var major, out var minor)) - throw OpenGlException.GetFormattedException("eglInitialize", _egl); - - var glProfiles = AvaloniaLocator.Current.GetService()?.GlProfiles - ?? new[] - { - new GlVersion(GlProfileType.OpenGLES, 3, 0), - new GlVersion(GlProfileType.OpenGLES, 2, 0) - }; - var cfgs = glProfiles.Select(x => + _config = EglDisplayUtils.InitializeAndGetConfig(_egl, display, options.GlVersions); + } + + public EglInterface EglInterface => _egl; + public EglContext CreateContext(EglContextOptions options) + { + if (SingleContext && _contexts.Any()) + throw new OpenGlException("This EGLDisplay can only have one active context"); + + options ??= new EglContextOptions(); + lock (_lock) { - var typeBit = EGL_OPENGL_ES3_BIT; + var share = options.ShareWith; + if (share != null && !SupportsSharing) + throw new NotSupportedException("Context sharing is not supported by this display"); - switch (x.Major) - { - case 2: - typeBit = EGL_OPENGL_ES2_BIT; - break; + var offscreenSurface = options.OffscreenSurface; - case 1: - typeBit = EGL_OPENGL_ES_BIT; - break; - } - - return new + if (offscreenSurface == null) { - Attributes = new[] + // Check if eglMakeCurrent can work with EGL_NONE as read-write surfaces + var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS); + if (extensions?.Contains("EGL_KHR_surfaceless_context") != true) { - EGL_CONTEXT_MAJOR_VERSION, x.Major, - EGL_CONTEXT_MINOR_VERSION, x.Minor, - EGL_NONE - }, - Api = EGL_OPENGL_ES_API, - RenderableTypeBit = typeBit, - Version = x - }; - }); - - foreach (var cfg in cfgs) - { - if (!_egl.BindApi(cfg.Api)) - continue; - foreach(var surfaceType in new[]{EGL_PBUFFER_BIT|EGL_WINDOW_BIT, EGL_WINDOW_BIT}) - foreach(var stencilSize in new[]{8, 1, 0}) - foreach (var depthSize in new []{8, 1, 0}) + // Attempt to create a PBuffer as a surface for offscreen rendering + if ((_config.SurfaceType | EGL_PBUFFER_BIT) == 0) + throw new InvalidOperationException( + "Platform doesn't support EGL_KHR_surfaceless_context and PBUFFER surfaces"); + + var pBufferSurface = _egl.CreatePBufferSurface(_display, Config, + new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }); + if (pBufferSurface == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); + + offscreenSurface = new EglSurface(this, pBufferSurface); + } + } + + var ctx = _egl.CreateContext(_display, Config, share?.Context ?? IntPtr.Zero, _config.Attributes); + if (ctx == IntPtr.Zero) { - var attribs = new[] - { - EGL_SURFACE_TYPE, surfaceType, - EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_STENCIL_SIZE, stencilSize, - EGL_DEPTH_SIZE, depthSize, - EGL_NONE - }; - if (!_egl.ChooseConfig(_display, attribs, out _config, 1, out int numConfigs)) - continue; - if (numConfigs == 0) - continue; - _contextAttributes = cfg.Attributes; - _surfaceType = surfaceType; - _version = cfg.Version; - _egl.GetConfigAttrib(_display, _config, EGL_SAMPLES, out _sampleCount); - _egl.GetConfigAttrib(_display, _config, EGL_STENCIL_SIZE, out _stencilSize); - goto Found; + var ex = OpenGlException.GetFormattedException("eglCreateContext", _egl); + offscreenSurface?.Dispose(); + throw ex; } + var rv = new EglContext(this, _egl, share, ctx, offscreenSurface, + _config.Version, _config.SampleCount, _config.StencilSize, + options.DisposeCallback, options.ExtraFeatures); + _contexts.Add(rv); + return rv; } - Found: - if (_contextAttributes == null) - throw new OpenGlException("No suitable EGL config was found"); } - public EglDisplay() : this(false) + public EglSurface CreateWindowSurface(IntPtr window) { - + if (window == IntPtr.Zero) + throw new OpenGlException($"Window {window} is invalid."); + + using (Lock()) + { + var s = EglInterface.CreateWindowSurface(Handle, Config, window, + new[] { EGL_NONE, EGL_NONE }); + if (s == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreateWindowSurface", EglInterface); + return new EglSurface(this, s); + } } - public EglDisplay(bool supportsSharing) : this(new EglInterface(), supportsSharing) + public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs) { - + using (Lock()) + { + var s = EglInterface.CreatePbufferFromClientBuffer(Handle, bufferType, handle, + Config, attribs); + + if (s == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", EglInterface); + return new EglSurface(this, s); + } } + + protected virtual bool DisplayLockIsSharedWithContexts => false; - public EglInterface EglInterface => _egl; - public EglContext CreateContext(IGlContext share) + internal object ContextSharedSyncRoot => DisplayLockIsSharedWithContexts ? _lock : null; + + internal void OnContextLost(EglContext context) { - if (share != null && !SupportsSharing) - throw new NotSupportedException("Context sharing is not supported by this display"); - - if((_surfaceType|EGL_PBUFFER_BIT) == 0) - throw new InvalidOperationException("Platform doesn't support PBUFFER surfaces"); - var shareCtx = (EglContext)share; - var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes); - if (ctx == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreateContext", _egl); - - var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS); - - IntPtr surf = IntPtr.Zero; - if (extensions?.Contains("EGL_KHR_surfaceless_context") != true) + if (_options.ContextLossIsDisplayLoss) + _isLost = true; + } + + internal void OnContextDisposed(EglContext context) + { + lock (_lock) + _contexts.Remove(context); + } + + public bool IsLost + { + get { - surf = _egl.CreatePBufferSurface(_display, _config, - new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }); - if (surf == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); + if (_isLost || _display == IntPtr.Zero) + return true; + if (_options.DeviceLostCheckCallback?.Invoke() == true) + return _isLost = true; + return false; } - - var rv = new EglContext(this, _egl, shareCtx, ctx, - context => - surf == IntPtr.Zero ? null : new EglSurface(this, context, surf), - _version, _sampleCount, _stencilSize); - return rv; + } + + public IDisposable Lock() + { + Monitor.Enter(_lock); + return Disposable.Create(() => { Monitor.Exit(_lock); }); } - public EglContext CreateContext(EglContext share, EglSurface offscreenSurface) + public void Dispose() { - if (share != null && !SupportsSharing) - throw new NotSupportedException("Context sharing is not supported by this display"); - - var ctx = _egl.CreateContext(_display, _config, share?.Context ?? IntPtr.Zero, _contextAttributes); - if (ctx == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreateContext", _egl); - var rv = new EglContext(this, _egl, share, ctx, _ => offscreenSurface, _version, _sampleCount, _stencilSize); - rv.MakeCurrent(null); - return rv; + lock (_lock) + { + foreach(var ctx in _contexts) + ctx.Dispose(); + _contexts.Clear(); + if (_display != IntPtr.Zero) + _egl.Terminate(_display); + _display = IntPtr.Zero; + _config = null; + _options.DisposeCallback?.Invoke(); + } } } } diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs new file mode 100644 index 0000000000..5648645c54 --- /dev/null +++ b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.OpenGL.Egl; + +public class EglDisplayOptions +{ + public EglInterface Egl { get; set; } + public bool SupportsContextSharing { get; set; } + public bool SupportsMultipleContexts { get; set; } + public bool ContextLossIsDisplayLoss { get; set; } + public Func DeviceLostCheckCallback { get; set; } + public Action DisposeCallback { get; set; } + public IEnumerable GlVersions { get; set; } +} + +public class EglContextOptions +{ + public EglContext ShareWith { get; set; } + public EglSurface OffscreenSurface { get; set; } + public Action DisposeCallback { get; set; } + public Dictionary ExtraFeatures { get; set; } +} + +public class EglDisplayCreationOptions : EglDisplayOptions +{ + public int? PlatformType { get; set; } + public IntPtr PlatformDisplay { get; set; } + public int[] PlatformDisplayAttrs { get; set; } +} diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs new file mode 100644 index 0000000000..fbfaf1bd3d --- /dev/null +++ b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; +using System.Threading; +using static Avalonia.OpenGL.Egl.EglConsts; +namespace Avalonia.OpenGL.Egl; + +static class EglDisplayUtils +{ + public static IntPtr CreateDisplay(EglDisplayCreationOptions options) + { + var egl = options.Egl; + var display = IntPtr.Zero; + if (options.PlatformType == null) + { + if (display == IntPtr.Zero) + display = egl.GetDisplay(IntPtr.Zero); + } + else + { + if (!egl.IsGetPlatformDisplayExtAvailable) + throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl"); + + display = egl.GetPlatformDisplayExt(options.PlatformType.Value, options.PlatformDisplay, + options.PlatformDisplayAttrs); + } + + if (display == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglGetDisplay", egl); + return display; + } + + public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable versions) + { + if (!egl.Initialize(display, out var major, out var minor)) + throw OpenGlException.GetFormattedException("eglInitialize", egl); + + // TODO: AvaloniaLocator.Current.GetService()?.GlProfiles + versions ??= new[] + { + new GlVersion(GlProfileType.OpenGLES, 3, 0), + new GlVersion(GlProfileType.OpenGLES, 2, 0) + }; + + var cfgs = versions + .Where(x => x.Type == GlProfileType.OpenGLES) + .Select(x => + { + var typeBit = EGL_OPENGL_ES3_BIT; + + switch (x.Major) + { + case 2: + typeBit = EGL_OPENGL_ES2_BIT; + break; + + case 1: + typeBit = EGL_OPENGL_ES_BIT; + break; + } + + return new + { + Attributes = new[] + { + EGL_CONTEXT_MAJOR_VERSION, x.Major, + EGL_CONTEXT_MINOR_VERSION, x.Minor, + EGL_NONE + }, + Api = EGL_OPENGL_ES_API, + RenderableTypeBit = typeBit, + Version = x + }; + }); + + foreach (var cfg in cfgs) + { + if (!egl.BindApi(cfg.Api)) + continue; + foreach (var surfaceType in new[] { EGL_PBUFFER_BIT | EGL_WINDOW_BIT, EGL_WINDOW_BIT }) + foreach (var stencilSize in new[] { 8, 1, 0 }) + foreach (var depthSize in new[] { 8, 1, 0 }) + { + var attribs = new[] + { + EGL_SURFACE_TYPE, surfaceType, + EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_STENCIL_SIZE, stencilSize, + EGL_DEPTH_SIZE, depthSize, + EGL_NONE + }; + if (!egl.ChooseConfig(display, attribs, out var config, 1, out int numConfigs)) + continue; + if (numConfigs == 0) + continue; + + + egl.GetConfigAttrib(display, config, EGL_SAMPLES, out var sampleCount); + egl.GetConfigAttrib(display, config, EGL_STENCIL_SIZE, out var returnedStencilSize); + return new EglConfigInfo(config, cfg.Version, surfaceType, cfg.Attributes, sampleCount, + returnedStencilSize); + } + } + + throw new OpenGlException("No suitable EGL config was found"); + } + + +} + +class EglConfigInfo +{ + public IntPtr Config { get; } + public GlVersion Version { get; } + public int SurfaceType { get; } + public int[] Attributes { get; } + public int SampleCount { get; } + public int StencilSize { get; } + + public EglConfigInfo(IntPtr config, GlVersion version, int surfaceType, int[] attributes, int sampleCount, + int stencilSize) + { + Config = config; + Version = version; + SurfaceType = surfaceType; + Attributes = attributes; + SampleCount = sampleCount; + StencilSize = stencilSize; + } +} \ No newline at end of file diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs index 3d58660d47..348028bd08 100644 --- a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs @@ -1,52 +1,61 @@ +using System; using Avalonia.OpenGL.Surfaces; namespace Avalonia.OpenGL.Egl { public class EglGlPlatformSurface : EglGlPlatformSurfaceBase { - private readonly EglPlatformOpenGlInterface _egl; + public interface IEglWindowGlPlatformSurfaceInfo + { + IntPtr Handle { get; } + PixelSize Size { get; } + double Scaling { get; } + } + private readonly IEglWindowGlPlatformSurfaceInfo _info; - public EglGlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) : base() + public EglGlPlatformSurface(IEglWindowGlPlatformSurfaceInfo info) { - _egl = egl; _info = info; } - public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { - var glSurface = _egl.CreateWindowSurface(_info.Handle); - return new RenderTarget(_egl, glSurface, _info); + var eglContext = (EglContext)context; + + var glSurface = eglContext.Display.CreateWindowSurface(_info.Handle); + return new RenderTarget(glSurface, eglContext, _info); } class RenderTarget : EglPlatformSurfaceRenderTargetBase { - private readonly EglPlatformOpenGlInterface _egl; private EglSurface _glSurface; private readonly IEglWindowGlPlatformSurfaceInfo _info; private PixelSize _currentSize; + private readonly IntPtr _handle; - public RenderTarget(EglPlatformOpenGlInterface egl, - EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(egl) + public RenderTarget(EglSurface glSurface, EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base(context) { - _egl = egl; _glSurface = glSurface; _info = info; _currentSize = info.Size; + _handle = _info.Handle; } public override void Dispose() => _glSurface.Dispose(); - - public override IGlPlatformSurfaceRenderingSession BeginDraw() + + public override IGlPlatformSurfaceRenderingSession BeginDrawCore() { - if (_info.Size != _currentSize || _glSurface == null) + if (_info.Size != _currentSize + || _handle != _info.Handle + || _glSurface == null) { _glSurface?.Dispose(); _glSurface = null; - _glSurface = _egl.CreateWindowSurface(_info.Handle); + _glSurface = Context.Display.CreateWindowSurface(_info.Handle); _currentSize = _info.Size; } - return base.BeginDraw(_glSurface, _info); + return base.BeginDraw(_glSurface, _info.Size, _info.Scaling); } } } diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs index 4ea6766de2..f66f630556 100644 --- a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs @@ -5,23 +5,16 @@ namespace Avalonia.OpenGL.Egl { public abstract class EglGlPlatformSurfaceBase : IGlPlatformSurface { - public interface IEglWindowGlPlatformSurfaceInfo - { - IntPtr Handle { get; } - PixelSize Size { get; } - double Scaling { get; } - } - - public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(); + public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context); } - public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTarget + public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTargetWithCorruptionInfo { - private readonly EglPlatformOpenGlInterface _egl; + protected EglContext Context { get; } - protected EglPlatformSurfaceRenderTargetBase(EglPlatformOpenGlInterface egl) + protected EglPlatformSurfaceRenderTargetBase(EglContext context) { - _egl = egl; + Context = context; } public virtual void Dispose() @@ -29,25 +22,33 @@ namespace Avalonia.OpenGL.Egl } - public abstract IGlPlatformSurfaceRenderingSession BeginDraw(); + public IGlPlatformSurfaceRenderingSession BeginDraw() + { + if (Context.IsLost) + throw new RenderTargetCorruptedException(); + + return BeginDrawCore(); + } + + public abstract IGlPlatformSurfaceRenderingSession BeginDrawCore(); protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface, - EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, Action onFinish = null, bool isYFlipped = false) + PixelSize size, double scaling, Action onFinish = null, bool isYFlipped = false) { - var restoreContext = _egl.PrimaryEglContext.MakeCurrent(surface); + var restoreContext = Context.MakeCurrent(surface); var success = false; try { - var egli = _egl.Display.EglInterface; + var egli = Context.Display.EglInterface; egli.WaitClient(); egli.WaitGL(); egli.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); - _egl.PrimaryContext.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + Context.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); success = true; - return new Session(_egl.Display, _egl.PrimaryEglContext, surface, info, restoreContext, onFinish, isYFlipped); + return new Session(Context.Display, Context, surface, size, scaling, restoreContext, onFinish, isYFlipped); } finally { @@ -60,21 +61,21 @@ namespace Avalonia.OpenGL.Egl { private readonly EglContext _context; private readonly EglSurface _glSurface; - private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; private readonly EglDisplay _display; private readonly IDisposable _restoreContext; private readonly Action _onFinish; public Session(EglDisplay display, EglContext context, - EglSurface glSurface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, + EglSurface glSurface, PixelSize size, double scaling, IDisposable restoreContext, Action onFinish, bool isYFlipped) { + Size = size; + Scaling = scaling; IsYFlipped = isYFlipped; _context = context; _display = display; _glSurface = glSurface; - _info = info; _restoreContext = restoreContext; _onFinish = onFinish; } @@ -92,9 +93,11 @@ namespace Avalonia.OpenGL.Egl } public IGlContext Context => _context; - public PixelSize Size => _info.Size; - public double Scaling => _info.Scaling; + public PixelSize Size { get; } + public double Scaling { get; } public bool IsYFlipped { get; } } + + public virtual bool IsCorrupted => Context.IsLost; } } diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index 4fec8e5356..ad4b55a686 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -53,6 +53,9 @@ namespace Avalonia.OpenGL.Egl [GetProcAddress("eglInitialize")] public partial bool Initialize(IntPtr display, out int major, out int minor); + + [GetProcAddress("eglTerminate")] + public partial void Terminate(IntPtr display); [GetProcAddress("eglGetProcAddress")] public partial IntPtr GetProcAddress(IntPtr proc); diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs new file mode 100644 index 0000000000..faa9f279a6 --- /dev/null +++ b/src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs @@ -0,0 +1,48 @@ +using System; +using Avalonia.Logging; +using Avalonia.Platform; +using static Avalonia.OpenGL.Egl.EglConsts; + +namespace Avalonia.OpenGL.Egl +{ + public sealed class EglPlatformGraphics : IPlatformGraphics + { + private readonly EglDisplay _display; + public bool UsesSharedContext => false; + public IPlatformGraphicsContext CreateContext() => _display.CreateContext(null); + public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException(); + + public EglPlatformGraphics(EglDisplay display) + { + _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(), + // Those are expected to be supported by most EGL implementations + SupportsMultipleContexts = true, + SupportsContextSharing = true + })); + + public static EglPlatformGraphics TryCreate(Func displayFactory) + { + try + { + return new EglPlatformGraphics(displayFactory()); + } + catch(Exception e) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e); + return null; + } + } + } +} diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs b/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs deleted file mode 100644 index a1ac2a9d37..0000000000 --- a/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using Avalonia.Logging; -using Avalonia.Platform; -using static Avalonia.OpenGL.Egl.EglConsts; - -namespace Avalonia.OpenGL.Egl -{ - public class EglPlatformOpenGlInterface : IPlatformOpenGlInterface - { - public EglDisplay Display { get; private set; } - public bool CanCreateContexts => true; - public bool CanShareContexts => Display.SupportsSharing; - - public EglContext PrimaryEglContext { get; } - public IGlContext PrimaryContext => PrimaryEglContext; - IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext; - - public EglPlatformOpenGlInterface(EglDisplay display) - { - Display = display; - PrimaryEglContext = display.CreateContext(null); - } - - public static void TryInitialize() - { - var feature = TryCreate(); - if (feature != null) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); - } - - public static EglPlatformOpenGlInterface TryCreate() => TryCreate(() => new EglDisplay()); - public static EglPlatformOpenGlInterface TryCreate(Func displayFactory) - { - try - { - return new EglPlatformOpenGlInterface(displayFactory()); - } - catch(Exception e) - { - Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e); - return null; - } - } - - public IGlContext CreateContext() => Display.CreateContext(null); - public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryEglContext); - - - public EglSurface CreateWindowSurface(IntPtr window) - { - if (window == IntPtr.Zero) - throw new OpenGlException($"Window {window} is invalid."); - - using (PrimaryContext.MakeCurrent()) - { - var s = Display.EglInterface.CreateWindowSurface(Display.Handle, Display.Config, window, - new[] { EGL_NONE, EGL_NONE }); - if (s == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreateWindowSurface", Display.EglInterface); - return new EglSurface(Display, PrimaryEglContext, s); - } - } - - public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs) - { - using (PrimaryContext.MakeCurrent()) - { - var s = Display.EglInterface.CreatePbufferFromClientBuffer(Display.Handle, bufferType, handle, - Display.Config, attribs); - - if (s == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", Display.EglInterface); - return new EglSurface(Display, PrimaryEglContext, s); - } - } - } -} diff --git a/src/Avalonia.OpenGL/Egl/EglSurface.cs b/src/Avalonia.OpenGL/Egl/EglSurface.cs index a93751ca9e..f3ee85b3f6 100644 --- a/src/Avalonia.OpenGL/Egl/EglSurface.cs +++ b/src/Avalonia.OpenGL/Egl/EglSurface.cs @@ -6,19 +6,17 @@ namespace Avalonia.OpenGL.Egl public class EglSurface : SafeHandle { private readonly EglDisplay _display; - private readonly EglContext _context; private readonly EglInterface _egl; - public EglSurface(EglDisplay display, EglContext context, IntPtr surface) : base(surface, true) + public EglSurface(EglDisplay display, IntPtr surface) : base(surface, true) { _display = display; - _context = context; _egl = display.EglInterface; } protected override bool ReleaseHandle() { - using (_context.MakeCurrent()) + using (_display.Lock()) _egl.DestroySurface(_display.Handle, handle); return true; } diff --git a/src/Avalonia.OpenGL/GlVersion.cs b/src/Avalonia.OpenGL/GlVersion.cs index 042ff4c2f0..16ed18e45a 100644 --- a/src/Avalonia.OpenGL/GlVersion.cs +++ b/src/Avalonia.OpenGL/GlVersion.cs @@ -6,7 +6,7 @@ namespace Avalonia.OpenGL OpenGLES } - public struct GlVersion + public record struct GlVersion { public GlProfileType Type { get; } public int Major { get; } diff --git a/src/Avalonia.OpenGL/IGlContext.cs b/src/Avalonia.OpenGL/IGlContext.cs index a52a6535da..d45ea931d0 100644 --- a/src/Avalonia.OpenGL/IGlContext.cs +++ b/src/Avalonia.OpenGL/IGlContext.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; namespace Avalonia.OpenGL { - public interface IGlContext : IPlatformGpuContext + public interface IGlContext : IPlatformGraphicsContext { GlVersion Version { get; } GlInterface GlInterface { get; } @@ -12,5 +14,13 @@ namespace Avalonia.OpenGL IDisposable MakeCurrent(); IDisposable EnsureCurrent(); bool IsSharedWith(IGlContext context); + bool CanCreateSharedContext { get; } + IGlContext CreateSharedContext(IEnumerable preferredVersions = null); + } + + public interface IGlPlatformSurfaceRenderTargetFactory + { + bool CanRenderToSurface(IGlContext context, object surface); + IGlPlatformSurfaceRenderTarget CreateRenderTarget(IGlContext context, object surface); } } diff --git a/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs b/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs deleted file mode 100644 index fdb9162164..0000000000 --- a/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Avalonia.OpenGL.Imaging; - -namespace Avalonia.OpenGL -{ - public interface IOpenGlAwarePlatformRenderInterface - { - IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi); - } -} diff --git a/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs b/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs new file mode 100644 index 0000000000..9c22d446ef --- /dev/null +++ b/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Avalonia.OpenGL.Imaging; +using Avalonia.Platform; + +namespace Avalonia.OpenGL +{ + public interface IOpenGlTextureSharingRenderInterfaceContextFeature + { + bool CanCreateSharedContext { get; } + IGlContext CreateSharedContext(IEnumerable preferredVersions = null); + IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi); + } +} diff --git a/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs b/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs deleted file mode 100644 index 4ff7997b03..0000000000 --- a/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Platform; - -namespace Avalonia.OpenGL -{ - public interface IPlatformOpenGlInterface : IPlatformGpu - { - new IGlContext PrimaryContext { get; } - IGlContext CreateSharedContext(); - bool CanShareContexts { get; } - bool CanCreateContexts { get; } - IGlContext CreateContext(); - /*IGlContext TryCreateContext(GlVersion version); - */ - } -} diff --git a/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs index 7af44cd624..53013ae5a3 100644 --- a/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs +++ b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs @@ -10,19 +10,15 @@ namespace Avalonia.OpenGL.Imaging { private IOpenGlBitmapImpl _impl; - public OpenGlBitmap(PixelSize size, Vector dpi) - : base(CreateOrThrow(size, dpi)) + public OpenGlBitmap(IOpenGlTextureSharingRenderInterfaceContextFeature feature, + PixelSize size, Vector dpi) + : base(CreateOrThrow(feature, size, dpi)) { _impl = (IOpenGlBitmapImpl)PlatformImpl.Item; } - - static IOpenGlBitmapImpl CreateOrThrow(PixelSize size, Vector dpi) - { - if (!(AvaloniaLocator.Current.GetService() is IOpenGlAwarePlatformRenderInterface - glAware)) - throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration"); - return glAware.CreateOpenGlBitmap(size, dpi); - } + + static IOpenGlBitmapImpl CreateOrThrow(IOpenGlTextureSharingRenderInterfaceContextFeature feature, + PixelSize size, Vector dpi) => feature.CreateOpenGlBitmap(size, dpi); public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context) => _impl.CreateFramebufferAttachment(context, SetIsDirty); diff --git a/src/Avalonia.OpenGL/OpenGlException.cs b/src/Avalonia.OpenGL/OpenGlException.cs index 196f507ad8..efe305dba5 100644 --- a/src/Avalonia.OpenGL/OpenGlException.cs +++ b/src/Avalonia.OpenGL/OpenGlException.cs @@ -18,25 +18,33 @@ namespace Avalonia.OpenGL public static OpenGlException GetFormattedException(string funcName, EglInterface egl) { - return GetFormattedException(typeof(EglErrors), funcName, egl.GetError()); + return GetFormattedEglException(funcName, egl.GetError()); } public static OpenGlException GetFormattedException(string funcName, GlInterface gl) { - return GetFormattedException(typeof(GlErrors), funcName, gl.GetError()); + var err = gl.GetError(); + return GetFormattedException(funcName, (GlErrors)err, err); } - private static OpenGlException GetFormattedException(Type consts, string funcName, int errorCode) + public static OpenGlException GetFormattedEglException(string funcName, int errorCode) => + GetFormattedException(funcName, (EglErrors)errorCode,errorCode); + + private static OpenGlException GetFormattedException(string funcName, T errorCode, int intErrorCode) where T : struct, Enum { try { - string errorName = Enum.GetName(consts, errorCode); +#if NET6_0_OR_GREATER + var errorName = Enum.GetName(errorCode); +#else + var errorName = Enum.GetName(typeof(T), errorCode); +#endif return new OpenGlException( - $"{funcName} failed with error {errorName} (0x{errorCode.ToString("X")})", errorCode); + $"{funcName} failed with error {errorName} (0x{errorCode.ToString("X")})", intErrorCode); } catch (ArgumentException) { - return new OpenGlException($"{funcName} failed with error 0x{errorCode.ToString("X")}", errorCode); + return new OpenGlException($"{funcName} failed with error 0x{errorCode.ToString("X")}", intErrorCode); } } } diff --git a/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs index 875c215336..d80e72e4e7 100644 --- a/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs @@ -2,6 +2,6 @@ namespace Avalonia.OpenGL.Surfaces { public interface IGlPlatformSurface { - IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(); + IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context); } } diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index d011a963be..c0263b3518 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -1370,7 +1370,7 @@ namespace Metsys.Bson var pattern = ReadName(); var optionsString = ReadName(); - var options = RegexOptions.None; + var options = RegexOptions.Compiled; if (optionsString.Contains('e')) options = options | RegexOptions.ECMAScript; if (optionsString.Contains('i')) options = options | RegexOptions.IgnoreCase; if (optionsString.Contains('l')) options = options | RegexOptions.CultureInvariant; diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml index 259d107b5c..479bcd8531 100644 --- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml @@ -22,6 +22,7 @@ 10,6,6,5 20 20 + 8,5,8,6 3 diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml index 89841c92c0..0192fb1b54 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml @@ -36,6 +36,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml index 89b646fb52..a9e5ed949a 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml @@ -36,6 +36,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/Button.xaml b/src/Avalonia.Themes.Fluent/Controls/Button.xaml index 7828fd52ed..126f2c22e0 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Button.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Button.xaml @@ -8,8 +8,6 @@ - - 8,5,8,6 diff --git a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml index f8b4854553..67e70d154f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/EmbeddableControlRoot.xaml @@ -1,6 +1,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 9156d11a96..2c3550a72f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -4,72 +4,71 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml index d1707d0af2..63b3d46a41 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml @@ -153,9 +153,13 @@ - + @@ -322,7 +326,6 @@ diff --git a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml index a54187104b..fd04c85fed 100644 --- a/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml @@ -10,8 +10,6 @@ - 8,5,8,6 - diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml index 7a46f21534..da2021790a 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml @@ -10,8 +10,6 @@ - 8,5,8,6 - diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml b/src/Avalonia.Themes.Fluent/FluentTheme.xaml index d8f8267fe5..44ca60e2fa 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml @@ -4,8 +4,8 @@ - - + + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs index eea3d3ad08..a8297953a8 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs @@ -99,15 +99,15 @@ namespace Avalonia.Themes.Fluent var themeVariantResource1 = Mode == FluentThemeMode.Dark ? _baseDark : _baseLight; var themeVariantResource2 = Mode == FluentThemeMode.Dark ? _fluentDark : _fluentLight; var dict = Resources.MergedDictionaries; - if (dict.Count == 2) + if (dict.Count == 0) { - dict.Insert(1, themeVariantResource1); + dict.Add(themeVariantResource1); dict.Add(themeVariantResource2); } else { - dict[1] = themeVariantResource1; - dict[3] = themeVariantResource2; + dict[0] = themeVariantResource1; + dict[1] = themeVariantResource2; } } diff --git a/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml index 79d6c6d917..3d52464858 100644 --- a/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Simple/Controls/EmbeddableControlRoot.xaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + diff --git a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml index 61dae9b445..5a1fa4ec6d 100644 --- a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml @@ -11,6 +11,12 @@ + + + + @@ -54,6 +60,14 @@ + + + diff --git a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml index ba1b35e2ee..945622f22f 100644 --- a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml +++ b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml @@ -9,17 +9,16 @@ - - diff --git a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml index 4bad556338..093adaeab2 100644 --- a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml +++ b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml @@ -3,70 +3,70 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml b/src/Avalonia.Themes.Simple/SimpleTheme.xaml index fe296bd288..5b0cae7fd2 100644 --- a/src/Avalonia.Themes.Simple/SimpleTheme.xaml +++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml @@ -4,7 +4,7 @@ - + diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs index 56c4cbeac6..42dfafd7e0 100644 --- a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs +++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs @@ -58,13 +58,13 @@ namespace Avalonia.Themes.Simple { var themeVariantResource = Mode == SimpleThemeMode.Dark ? _simpleDark : _simpleLight; var dict = Resources.MergedDictionaries; - if (dict.Count == 1) + if (dict.Count == 0) { dict.Add(themeVariantResource); } else { - dict[1] = themeVariantResource; + dict[0] = themeVariantResource; } } } diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs index e9cb88cb8f..def5228e94 100644 --- a/src/Avalonia.X11/Glx/GlxContext.cs +++ b/src/Avalonia.X11/Glx/GlxContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reactive.Disposables; using System.Threading; using Avalonia.OpenGL; @@ -70,6 +71,8 @@ namespace Avalonia.X11.Glx } public IDisposable MakeCurrent() => MakeCurrent(_defaultXid); + public bool IsLost => false; + public IDisposable EnsureCurrent() { if(IsCurrent) @@ -86,6 +89,11 @@ namespace Avalonia.X11.Glx || _sharedWith != null && _sharedWith == c._sharedWith; } + public bool CanCreateSharedContext => true; + + public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) => + Display.CreateContext(_sharedWith ?? this); + public IDisposable MakeCurrent(IntPtr xid) { Monitor.Enter(_lock); @@ -114,5 +122,7 @@ namespace Avalonia.X11.Glx if (_ownsPBuffer) Glx.DestroyPbuffer(_x11.Display, _defaultXid); } + + public object TryGetFeature(Type featureType) => null; } } diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index 1e70608168..9b8a8f0b5b 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -111,8 +111,8 @@ namespace Avalonia.X11.Glx return Glx.CreatePbuffer(_x11.Display, _fbconfig, new[] { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 }); } - - public GlxContext CreateContext() => CreateContext(); + public GlxContext CreateContext() => CreateContext(CreatePBuffer(), null, DeferredContext.SampleCount, + DeferredContext.StencilSize, true); public GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share, share.SampleCount, share.StencilSize, true); diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs index cb4ab4aca0..ebb1e18723 100644 --- a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs +++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs @@ -9,20 +9,16 @@ namespace Avalonia.X11.Glx class GlxGlPlatformSurface: IGlPlatformSurface { - private readonly GlxDisplay _display; - private readonly GlxContext _context; private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; - public GlxGlPlatformSurface(GlxDisplay display, GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) + public GlxGlPlatformSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) { - _display = display; - _context = context; _info = info; } - public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { - return new RenderTarget(_context, _info); + return new RenderTarget((GlxContext)context, _info); } class RenderTarget : IGlPlatformSurfaceRenderTarget diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs index 0968adc799..bc50fa61aa 100644 --- a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs +++ b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs @@ -6,38 +6,36 @@ using Avalonia.Platform; namespace Avalonia.X11.Glx { - class GlxPlatformOpenGlInterface : IPlatformOpenGlInterface + class GlxPlatformGraphics : IPlatformGraphics { public GlxDisplay Display { get; private set; } public bool CanCreateContexts => true; public bool CanShareContexts => true; - public IGlContext CreateContext() => Display.CreateContext(); - public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryContext); - public GlxContext DeferredContext { get; private set; } - public IGlContext PrimaryContext => DeferredContext; - IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext; + public bool UsesSharedContext => false; + 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); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); return true; } return false; } - public static GlxPlatformOpenGlInterface TryCreate(X11Info x11, IList glProfiles) + public static GlxPlatformGraphics TryCreate(X11Info x11, IList glProfiles) { try { var disp = new GlxDisplay(x11, glProfiles); - return new GlxPlatformOpenGlInterface + return new GlxPlatformGraphics { - Display = disp, - DeferredContext = disp.DeferredContext + Display = disp }; } catch(Exception e) diff --git a/src/Avalonia.X11/NativeDialogs/Gtk.cs b/src/Avalonia.X11/NativeDialogs/Gtk.cs index d5eae037a9..4e56ae73cf 100644 --- a/src/Avalonia.X11/NativeDialogs/Gtk.cs +++ b/src/Avalonia.X11/NativeDialogs/Gtk.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Avalonia.Platform.Interop; -using JetBrains.Annotations; // ReSharper disable IdentifierTypo namespace Avalonia.X11.NativeDialogs diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index d428d82744..2aa7797067 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -18,8 +18,6 @@ namespace Avalonia.X11 private TaskCompletionSource _requestedDataTcs; private readonly IntPtr[] _textAtoms; private readonly IntPtr _avaloniaSaveTargetsAtom; - private readonly Dictionary _formatAtoms = new Dictionary(); - private readonly Dictionary _atomFormats = new Dictionary(); public X11Clipboard(AvaloniaX11Platform platform) { diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 6041b53a62..16de10e163 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -113,7 +113,8 @@ namespace Avalonia.X11 image->yhot = hotSpot.Y; image->pixels = (IntPtr)(image + 1); - using (var renderTarget = platformRenderInterface.CreateRenderTarget(new[] { this })) + using (var cpuContext = platformRenderInterface.CreateBackendContext(null)) + using (var renderTarget = cpuContext.CreateRenderTarget(new[] { this })) using (var ctx = renderTarget.CreateDrawingContext(null)) { var r = new Rect(_pixelSize.ToSize(1)); diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index 4ae1c1599f..bf59d72c0f 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -41,7 +41,8 @@ namespace Avalonia.X11 _width = Math.Min(bitmap.PixelSize.Width, 128); _height = Math.Min(bitmap.PixelSize.Height, 128); _bdata = new uint[_width * _height]; - using(var rt = AvaloniaLocator.Current.GetService().CreateRenderTarget(new[]{this})) + using(var cpuContext = AvaloniaLocator.Current.GetRequiredService().CreateBackendContext(null)) + using(var rt = cpuContext.CreateRenderTarget(new[]{this})) using (var ctx = rt.CreateDrawingContext(null)) ctx.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(0, 0, _width, _height)); diff --git a/src/Avalonia.X11/X11ImmediateRendererProxy.cs b/src/Avalonia.X11/X11ImmediateRendererProxy.cs index 293e5110f7..ef061cbe5c 100644 --- a/src/Avalonia.X11/X11ImmediateRendererProxy.cs +++ b/src/Avalonia.X11/X11ImmediateRendererProxy.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; using Avalonia.VisualTree; @@ -14,11 +16,11 @@ namespace Avalonia.X11 private bool _running; private object _lock = new object(); - public X11ImmediateRendererProxy(Visual root, IRenderLoop loop) + public X11ImmediateRendererProxy(Visual root, IRenderLoop loop, Func renderTargetFactory, + PlatformRenderInterfaceContextManager renderContext) { _loop = loop; - _renderer = new ImmediateRenderer(root); - + _renderer = new ImmediateRenderer(root, renderTargetFactory, renderContext); } public void Dispose() @@ -92,6 +94,9 @@ namespace Avalonia.X11 _renderer.Stop(); } + public ValueTask TryGetRenderInterfaceFeature(Type featureType) => + _renderer.TryGetRenderInterfaceFeature(featureType); + public bool NeedsUpdate => false; public void Update(TimeSpan time) { diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 13dc460f45..72f3bf3137 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using JetBrains.Annotations; using static Avalonia.X11.XLib; // ReSharper disable UnusedAutoPropertyAccessor.Local namespace Avalonia.X11 diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index cbb782edd0..e44b5ded14 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -30,6 +30,7 @@ namespace Avalonia.X11 public X11Info Info { get; private set; } public IX11Screens X11Screens { get; private set; } public Compositor Compositor { get; private set; } + public PlatformRenderInterfaceContextManager RenderInterface { get; private set; } public IScreenImpl Screens { get; private set; } public X11PlatformOptions Options { get; private set; } public IntPtr OrphanedWindow { get; private set; } @@ -96,17 +97,17 @@ namespace Avalonia.X11 if (options.UseGpu) { if (options.UseEGL) - EglPlatformOpenGlInterface.TryInitialize(); + EglPlatformGraphics.TryInitialize(); else - GlxPlatformOpenGlInterface.TryInitialize(Info, Options.GlProfiles); + GlxPlatformGraphics.TryInitialize(Info, Options.GlProfiles); } - var gl = AvaloniaLocator.Current.GetService(); - if (gl != null) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); - + var gl = AvaloniaLocator.Current.GetService(); + if (options.UseCompositor) Compositor = new Compositor(AvaloniaLocator.Current.GetService()!, gl); + else + RenderInterface = new(gl); } diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index ba6029b350..87899b11ed 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Runtime.InteropServices; using Avalonia.Platform; using static Avalonia.X11.XLib; -using JetBrains.Annotations; namespace Avalonia.X11 { diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 7e46606c36..26515762b4 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1768,7 +1768,9 @@ namespace Avalonia.X11 { } [StructLayout(LayoutKind.Sequential)] +#pragma warning disable CA1815 // Override equals and operator equals on value types public unsafe struct XImage +#pragma warning restore CA1815 // Override equals and operator equals on value types { public int width, height; /* size of image */ public int xoffset; /* number of pixels offset in X direction */ @@ -1799,7 +1801,7 @@ namespace Avalonia.X11 { internal IntPtr green_mask; internal IntPtr blue_mask; internal int colormap_size; - internal int bits_per_rgb; + internal int bits_per_rgb; } internal enum XIMFeedback diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index 257580a5ec..e6066c7964 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/src/Avalonia.X11/X11Window.Ime.cs @@ -7,7 +7,6 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Platform.Interop; -using JetBrains.Annotations; using static Avalonia.X11.XLib; namespace Avalonia.X11 @@ -206,7 +205,7 @@ namespace Avalonia.X11 // This class is used to attach the text value of the key to an asynchronously dispatched KeyDown event class RawKeyEventArgsWithText : RawKeyEventArgs { - public RawKeyEventArgsWithText([NotNull] IKeyboardDevice device, ulong timestamp, [NotNull] IInputRoot root, + public RawKeyEventArgsWithText(IKeyboardDevice device, ulong timestamp, IInputRoot root, RawKeyEventType type, Key key, RawInputModifiers modifiers, string text) : base(device, timestamp, root, type, key, modifiers) { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2f84e15b32..810a806c8a 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -57,6 +57,7 @@ namespace Avalonia.X11 private TransparencyHelper _transparencyHelper; private RawEventGrouper _rawEventGrouper; private bool _useRenderWindow = false; + private bool _usePositioningFlags = false; enum XSyncState { @@ -74,7 +75,7 @@ namespace Avalonia.X11 _touch = new TouchDevice(); _keyboard = platform.KeyboardDevice; - var glfeature = AvaloniaLocator.Current.GetService(); + var glfeature = AvaloniaLocator.Current.GetService(); XSetWindowAttributes attr = new XSetWindowAttributes(); var valueMask = default(SetWindowValuemask); @@ -96,13 +97,13 @@ namespace Avalonia.X11 // OpenGL seems to be do weird things to it's current window which breaks resize sometimes _useRenderWindow = glfeature != null; - var glx = glfeature as GlxPlatformOpenGlInterface; + var glx = glfeature as GlxPlatformGraphics; if (glx != null) visualInfo = *glx.Display.VisualInfo; else if (glfeature == null) visualInfo = _x11.TransparentVisualInfo; - var egl = glfeature as EglPlatformOpenGlInterface; + var egl = glfeature as EglPlatformGraphics; var visual = IntPtr.Zero; var depth = 24; @@ -176,11 +177,9 @@ namespace Avalonia.X11 if (egl != null) surfaces.Insert(0, - new EglGlPlatformSurface(egl, - new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); + new EglGlPlatformSurface(new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); if (glx != null) - surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext, - new SurfaceInfo(this, _x11.Display, _handle, _renderHandle))); + surfaces.Insert(0, new GlxGlPlatformSurface(new SurfaceInfo(this, _x11.Display, _handle, _renderHandle))); surfaces.Add(Handle); @@ -300,7 +299,10 @@ namespace Avalonia.X11 min_height = min.Height }; hints.height_inc = hints.width_inc = 1; - var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc | XSizeHintsFlags.PPosition | XSizeHintsFlags.PSize; + var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc; + if (_usePositioningFlags) + flags |= XSizeHintsFlags.PPosition | XSizeHintsFlags.PSize; + // People might be passing double.MaxValue if (max.Width < 100000 && max.Height < 100000) { @@ -356,7 +358,7 @@ namespace Avalonia.X11 public Action ScalingChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } - public Func Closing { get; set; } + public Func Closing { get; set; } public Action WindowStateChanged { get; set; } public Action TransparencyLevelChanged @@ -391,12 +393,14 @@ namespace Avalonia.X11 return _platform.Options.UseDeferredRendering ? _platform.Options.UseCompositor - ? new CompositingRenderer(root, this._platform.Compositor) - : new DeferredRenderer(root, loop) + ? new CompositingRenderer(root, this._platform.Compositor, () => Surfaces) + : new DeferredRenderer(root, loop, () => _platform.RenderInterface.CreateRenderTarget(Surfaces), _platform.RenderInterface) { RenderOnlyOnRenderThread = true } - : (IRenderer)new X11ImmediateRendererProxy((Visual)root, loop); + : new X11ImmediateRendererProxy((Visual)root, loop, + () => _platform.RenderInterface.CreateRenderTarget(Surfaces), + _platform.RenderInterface); } void OnEvent(ref XEvent ev) @@ -542,7 +546,7 @@ namespace Avalonia.X11 { if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_DELETE_WINDOW) { - if (Closing?.Invoke() != true) + if (Closing?.Invoke(WindowCloseReason.WindowClosing) != true) Dispose(); } else if (ev.ClientMessageEvent.ptr1 == _x11.Atoms._NET_WM_SYNC_REQUEST) @@ -958,6 +962,12 @@ namespace Avalonia.X11 get => _position ?? default; set { + if(!_usePositioningFlags) + { + _usePositioningFlags = true; + UpdateSizeHints(null); + } + var changes = new XWindowChanges { x = (int)value.X, diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs index a407e1e4d8..abda618b0d 100644 --- a/src/Browser/Avalonia.Browser/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs @@ -11,6 +11,7 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; +using Avalonia.Platform; using Avalonia.Rendering.Composition; using Avalonia.Threading; using SkiaSharp; @@ -106,7 +107,7 @@ namespace Avalonia.Browser _dpi = DomHelper.ObserveDpi(OnDpiChanged); - _useGL = skiaOptions?.CustomGpuFactory != null; + _useGL = AvaloniaLocator.Current.GetRequiredService() != null; if (_useGL) { diff --git a/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs b/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs index 7e91d29019..c2e54c7ed7 100644 --- a/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs +++ b/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs @@ -55,12 +55,6 @@ namespace Avalonia.Browser private class Attachment : INativeControlHostControlTopLevelAttachment { - private const string InitializeWithChildHandleSymbol = "InitializeWithChildHandle"; - private const string AttachToSymbol = "AttachTo"; - private const string ShowInBoundsSymbol = "ShowInBounds"; - private const string HideWithSizeSymbol = "HideWithSize"; - private const string ReleaseChildSymbol = "ReleaseChild"; - private JSObject? _native; private BrowserNativeControlHost? _attachedTo; diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs index ee4f6eca9b..b6c766b2a5 100644 --- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs @@ -48,7 +48,6 @@ public static class WebAppBuilder { return builder .UseWindowingSubsystem(BrowserWindowingPlatform.Register) - .UseSkia() - .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() }); + .UseSkia(); } } diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 69e2d27181..85725e4cf3 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -166,7 +166,8 @@ namespace Avalonia.Browser public IRenderer CreateRenderer(IRenderRoot root) { var loop = AvaloniaLocator.Current.GetRequiredService(); - return new CompositingRenderer(root, new Compositor(loop, null)); + return new CompositingRenderer(root, + new Compositor(loop, AvaloniaLocator.Current.GetRequiredService()), () => Surfaces); } public void Invalidate(Rect rect) diff --git a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs index a96ead93cb..e56efdb4a8 100644 --- a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs @@ -1,4 +1,7 @@ +using System; using System.Collections.Generic; +using System.Reactive.Disposables; +using Avalonia.Platform; using Avalonia.Skia; namespace Avalonia.Browser.Skia @@ -22,5 +25,28 @@ namespace Avalonia.Browser.Skia { return null; } + + public void Dispose() + { + + } + + public object? TryGetFeature(Type t) => null; + + public bool IsLost => false; + + public IDisposable EnsureCurrent() + { + return Disposable.Empty; + } + } + + class BrowserSkiaGraphics : IPlatformGraphics + { + private BrowserSkiaGpu _skia = new(); + public bool UsesSharedContext => true; + public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException(); + + public IPlatformGraphicsContext GetSharedContext() => _skia; } } diff --git a/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs index e389ee98ea..df1a24fa0f 100644 --- a/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs +++ b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs @@ -18,7 +18,7 @@ namespace Avalonia.Browser _onFirstRender = onFirstRender; } - public Rect Bounds => Rect.Empty; + public Rect Bounds => default; public bool HasRendered => _hasRendered; diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs index 6535e9534c..6493374a50 100644 --- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs +++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using Avalonia.Browser.Skia; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; @@ -43,6 +44,7 @@ namespace Avalonia.Browser .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(ManualTriggerRenderTimer.Instance) .Bind().ToConstant(instance) + .Bind().ToConstant(new BrowserSkiaGraphics()) .Bind().ToSingleton() .Bind().ToSingleton(); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index ce843952e7..3439f0edae 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -1,6 +1,5 @@ using Avalonia.LinuxFramebuffer.Output; using Avalonia.Media; -using JetBrains.Annotations; namespace Avalonia.LinuxFramebuffer { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index b64423ec10..ac54365f51 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -1,4 +1,4 @@ -using System; + using System; using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; @@ -33,7 +33,7 @@ namespace Avalonia.LinuxFramebuffer { var factory = AvaloniaLocator.Current.GetService(); var renderLoop = AvaloniaLocator.Current.GetService(); - return factory?.Create(root, renderLoop) ?? new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor); + return factory?.Create(root, renderLoop) ?? new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor, () => Surfaces); } public void Dispose() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs index 1e3c4bed48..686050e7c2 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Threading; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.EvDev @@ -13,7 +12,6 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev private readonly EvDevDeviceDescription[] _deviceDescriptions; private readonly List _handlers = new List(); private int _epoll; - private object _lock = new object(); private Action _onInput; private IInputRoot _inputRoot; private RawEventGroupingThreadingHelper _inputQueue; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 77e8202fac..bff9ddc55c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -11,7 +11,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput { private IScreenInfoProvider _screen; private IInputRoot _inputRoot; - private readonly Queue _inputThreadActions = new Queue(); private TouchDevice _touch = new TouchDevice(); private const string LibInput = nameof(Avalonia.LinuxFramebuffer) + "/" + nameof(Avalonia.LinuxFramebuffer.Input) + "/" + nameof(LibInput); private readonly RawEventGroupingThreadingHelper _inputQueue; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index d881a97af2..e2e9ec8fb3 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -17,7 +17,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Threading; -using JetBrains.Annotations; +#nullable enable namespace Avalonia.LinuxFramebuffer { @@ -26,9 +26,9 @@ namespace Avalonia.LinuxFramebuffer IOutputBackend _fb; private static readonly Stopwatch St = Stopwatch.StartNew(); internal static uint Timestamp => (uint)St.ElapsedTicks; - public static InternalPlatformThreadingInterface Threading; + public static InternalPlatformThreadingInterface? Threading; - internal static Compositor Compositor { get; private set; } + internal static Compositor? Compositor { get; private set; } LinuxFramebufferPlatform(IOutputBackend backend) @@ -41,7 +41,7 @@ namespace Avalonia.LinuxFramebuffer { Threading = new InternalPlatformThreadingInterface(); if (_fb is IGlOutputBackend gl) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformOpenGlInterface); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformGraphics); var opts = AvaloniaLocator.Current.GetService() ?? new LinuxFramebufferPlatformOptions(); @@ -56,11 +56,11 @@ namespace Avalonia.LinuxFramebuffer Compositor = new Compositor( AvaloniaLocator.Current.GetRequiredService(), - AvaloniaLocator.Current.GetService()); + AvaloniaLocator.Current.GetService()); } - internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend inputBackend) where T : AppBuilderBase, new() + internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend? inputBackend) where T : AppBuilderBase, new() { var platform = new LinuxFramebufferPlatform(outputBackend); builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev"); @@ -71,8 +71,8 @@ namespace Avalonia.LinuxFramebuffer class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime { private readonly IOutputBackend _fb; - [CanBeNull] private readonly IInputBackend _inputBackend; - private TopLevel _topLevel; + private readonly IInputBackend? _inputBackend; + private TopLevel? _topLevel; private readonly CancellationTokenSource _cts = new CancellationTokenSource(); public CancellationToken Token => _cts.Token; @@ -81,15 +81,15 @@ namespace Avalonia.LinuxFramebuffer _fb = fb; } - public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend input) + public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend? input) { _fb = fb; _inputBackend = input; } - public Control MainView + public Control? MainView { - get => (Control)_topLevel?.Content; + get => (Control?)_topLevel?.Content; set { if (_topLevel == null) @@ -119,8 +119,8 @@ namespace Avalonia.LinuxFramebuffer } public int ExitCode { get; private set; } - public event EventHandler Startup; - public event EventHandler Exit; + public event EventHandler? Startup; + public event EventHandler? Exit; public void Start(string[] args) { @@ -140,19 +140,19 @@ namespace Avalonia.LinuxFramebuffer public static class LinuxFramebufferPlatformExtensions { - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1, IInputBackend inputBackend = default) + public static int StartLinuxFbDev(this T builder, string[] args, string? fbdev = null, double scaling = 1, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend inputBackend = default) + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); - public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1, IInputBackend inputBackend = default) + public static int StartLinuxDrm(this T builder, string[] args, string? card = null, double scaling = 1, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); - public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null, IInputBackend inputBackend = default) + public static int StartLinuxDrm(this T builder, string[] args, string? card = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); - public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend inputBackend = default) + public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() { var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index a4d7d8b1eb..d61dcd4f91 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -6,8 +6,8 @@ using System.Runtime.InteropServices; using Avalonia.OpenGL; using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; +using Avalonia.Platform; using Avalonia.Platform.Interop; -using JetBrains.Annotations; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; using static Avalonia.LinuxFramebuffer.Output.LibDrm; using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats; @@ -25,10 +25,22 @@ namespace Avalonia.LinuxFramebuffer.Output get => _outputOptions.Scaling; set => _outputOptions.Scaling = value; } - public IGlContext PrimaryContext => _deferredContext; - private EglPlatformOpenGlInterface _platformGl; - public IPlatformOpenGlInterface PlatformOpenGlInterface => _platformGl; + class SharedContextGraphics : IPlatformGraphics + { + private readonly IPlatformGraphicsContext _context; + + public SharedContextGraphics(IPlatformGraphicsContext context) + { + _context = context; + } + public bool UsesSharedContext => true; + public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException(); + + public IPlatformGraphicsContext GetSharedContext() => _context; + } + + public IPlatformGraphics PlatformGraphics { get; private set; } public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo, DrmOutputOptions? options = null) @@ -37,7 +49,7 @@ namespace Avalonia.LinuxFramebuffer.Output _outputOptions = options; Init(card, resources, connector, modeInfo); } - public DrmOutput(string path = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null) + public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null) { if(options != null) _outputOptions = options; @@ -161,11 +173,22 @@ namespace Avalonia.LinuxFramebuffer.Output if(_gbmTargetSurface == IntPtr.Zero) throw new InvalidOperationException("Unable to create GBM surface"); - _eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), false, 0x31D7, device, null); - _platformGl = new EglPlatformOpenGlInterface(_eglDisplay); - _eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface); + _eglDisplay = new EglDisplay( + new EglDisplayCreationOptions + { + Egl = new EglInterface(eglGetProcAddress), + PlatformType = 0x31D7, + PlatformDisplay = device, + SupportsMultipleContexts = true, + SupportsContextSharing = true + }); + + var surface = _eglDisplay.EglInterface.CreateWindowSurface(_eglDisplay.Handle, _eglDisplay.Config, _gbmTargetSurface, new[] { EglConsts.EGL_NONE, EglConsts.EGL_NONE }); + + _eglSurface = new EglSurface(_eglDisplay, surface); - _deferredContext = _platformGl.PrimaryEglContext; + _deferredContext = _eglDisplay.CreateContext(null); + PlatformGraphics = new SharedContextGraphics(_deferredContext); var initialBufferSwappingColorR = _outputOptions.InitialBufferSwappingColor.R / 255.0f; var initialBufferSwappingColorG = _outputOptions.InitialBufferSwappingColor.G / 255.0f; @@ -206,11 +229,17 @@ namespace Avalonia.LinuxFramebuffer.Output } - public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => new RenderTarget(this); + + + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { - return new RenderTarget(this); + if (context != _deferredContext) + throw new InvalidOperationException( + "This platform backend can only create render targets for its primary context"); + return CreateGlRenderTarget(); } - + class RenderTarget : IGlPlatformSurfaceRenderTarget { private readonly DrmOutput _parent; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs index 7bc73d590c..e415b1782e 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs @@ -1,9 +1,10 @@ using Avalonia.OpenGL; +using Avalonia.Platform; namespace Avalonia.LinuxFramebuffer.Output { public interface IGlOutputBackend : IOutputBackend { - public IPlatformOpenGlInterface PlatformOpenGlInterface { get; } + public IPlatformGraphics PlatformGraphics { get; } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs index b4c951fc5e..6f6420f66d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs @@ -56,8 +56,8 @@ namespace Avalonia.Markup.Xaml /// /// Collection of documents. /// Xaml loader configuration. - /// The loaded objects per each input document. - public static IReadOnlyList LoadGroup(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration? configuration = null) + /// The loaded objects per each input document. If document was removed, the element by index is null. + public static IReadOnlyList LoadGroup(IReadOnlyCollection documents, RuntimeXamlLoaderConfiguration? configuration = null) => AvaloniaXamlIlRuntimeCompiler.LoadGroup(documents, configuration ?? new RuntimeXamlLoaderConfiguration()); /// diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index b8350c3f11..aaaee39b0d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -85,6 +85,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions GroupTransformers = new() { + new XamlMergeResourceGroupTransformer(), new AvaloniaXamlIncludeTransformer() }; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs index 32bf37431f..eeb5293325 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs @@ -18,7 +18,7 @@ internal class AstGroupTransformationContext : AstTransformationContext public IXamlDocumentResource CurrentDocument { get; set; } public IReadOnlyCollection Documents { get; } - + public new IXamlAstNode ParseError(string message, IXamlAstNode node) => Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node)); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs index 1bb05e238c..afc7b569a8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs @@ -15,6 +15,7 @@ using XamlX.TypeSystem; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; +#nullable enable internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer { public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node) @@ -34,40 +35,26 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer { throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined"); } - + if (valueNode.Manipulation is not XamlObjectInitializationNode { Manipulation: XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty }) { - return context.ParseError($"Source property must be set on the \"{nodeTypeName}\" node.", node); + throw new XamlDocumentParseException(context.CurrentDocument, + $"Source property must be set on the \"{nodeTypeName}\" node.", valueNode); } - // We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`. - if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode - || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri - || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath } - || sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind }) + var (assetPathUri, sourceUriNode) = ResolveSourceFromXamlInclude(context, nodeTypeName, sourceProperty, false); + if (assetPathUri is null) { - // TODO: make it a compiler warning - // Source value can be set with markup extension instead of the Uri object node, we don't support it here yet. return node; } - - var uriPath = new Uri(originalAssetPath, (UriKind)uriKind); - if (!uriPath.IsAbsoluteUri) + else { - var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null."); - uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath); - } - else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase)) - { - return context.ParseError( - $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", - sourceUriNode, node); + sourceUriNode ??= valueNode; } - var assetPathUri = Uri.UnescapeDataString(uriPath.AbsoluteUri); var assetPath = assetPathUri.Replace("avares://", ""); var assemblyNameSeparator = assetPath.IndexOf('/'); var assembly = assetPath.Substring(0, assemblyNameSeparator); @@ -119,7 +106,7 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer $"Unable to resolve XAML resource \"{assetPathUri}\" in the \"{assembly}\" assembly.", sourceUriNode, node); } - + private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlAstNode li, IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly) { @@ -151,7 +138,49 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer new[] { new NewServiceProviderNode(sp, li) }); } - internal class NewServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack, + internal static (string?, IXamlAstNode?) ResolveSourceFromXamlInclude( + AstGroupTransformationContext context, string nodeTypeName, XamlPropertyAssignmentNode sourceProperty, + bool strictSourceValueType) + { + // We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`. + if (sourceProperty.Values.OfType().FirstOrDefault() is not { } sourceUriNode + || sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri + || sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath } + || sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind }) + { + // Source value can be set with markup extension instead of the Uri object node, we don't support it here yet. + var anyPropValue = sourceProperty.Values.FirstOrDefault(); + if (strictSourceValueType) + { + context.Error(anyPropValue, + new XamlDocumentParseException(context.CurrentDocument, + $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", anyPropValue)); + } + else + { + // TODO: make it a compiler warning + } + return (null, anyPropValue); + } + + var uriPath = new Uri(originalAssetPath, (UriKind)uriKind); + if (!uriPath.IsAbsoluteUri) + { + var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null."); + uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath); + } + else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase)) + { + context.Error(sourceUriNode, + new XamlDocumentParseException(context.CurrentDocument, + $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", sourceUriNode)); + return (null, sourceUriNode); + } + + return (Uri.UnescapeDataString(uriPath.AbsoluteUri), sourceUriNode); + } + + private class NewServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack, IXamlAstEmitableNode { public NewServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs new file mode 100644 index 0000000000..8c83c74248 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using XamlX.Ast; +using XamlX.IL.Emitters; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; +#nullable enable + +internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer +{ + public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node) + { + var resourceDictionaryType = context.GetAvaloniaTypes().ResourceDictionary; + if (node is not XamlObjectInitializationNode resourceDictionaryNode + || resourceDictionaryNode.Type != resourceDictionaryType + || resourceDictionaryNode.Manipulation is not XamlManipulationGroupNode resourceDictionaryManipulation) + { + return node; + } + + var mergeResourceIncludeType = context.GetAvaloniaTypes().MergeResourceInclude; + var mergeSourceNodes = new List(); + var hasAnyNonMergedResource = false; + foreach (var manipulationNode in resourceDictionaryManipulation.Children.ToArray()) + { + void ProcessXamlPropertyAssignmentNode(XamlManipulationGroupNode parent, XamlPropertyAssignmentNode assignmentNode) + { + if (assignmentNode.Property.Name == "MergedDictionaries" + && assignmentNode.Values.FirstOrDefault() is XamlValueWithManipulationNode valueNode) + { + if (valueNode.Type.GetClrType() == mergeResourceIncludeType) + { + if (valueNode.Manipulation is XamlObjectInitializationNode objectInitialization + && objectInitialization.Manipulation is XamlPropertyAssignmentNode sourceAssignmentNode) + { + parent.Children.Remove(assignmentNode); + mergeSourceNodes.Add(sourceAssignmentNode); + } + else + { + throw new XamlDocumentParseException(context.CurrentDocument, + "Invalid MergeResourceInclude node found. Make sure that Source property is set.", + valueNode); + } + } + else + { + hasAnyNonMergedResource = true; + } + + if (hasAnyNonMergedResource && mergeSourceNodes.Any()) + { + throw new XamlDocumentParseException(context.CurrentDocument, + "Mix of MergeResourceInclude and other dictionaries inside of the ResourceDictionary.MergedDictionaries is not allowed", + valueNode); + } + } + } + + if (manipulationNode is XamlPropertyAssignmentNode singleValueAssignment) + { + ProcessXamlPropertyAssignmentNode(resourceDictionaryManipulation, singleValueAssignment); + } + else if (manipulationNode is XamlManipulationGroupNode groupNodeValues) + { + foreach (var groupNodeValue in groupNodeValues.Children.OfType().ToArray()) + { + ProcessXamlPropertyAssignmentNode(groupNodeValues, groupNodeValue); + } + } + } + + var manipulationGroup = new XamlManipulationGroupNode(node, new List()); + foreach (var sourceNode in mergeSourceNodes) + { + var (originalAssetPath, propertyNode) = + AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true); + if (originalAssetPath is null) + { + return node; + } + + var targetDocument = context.Documents.FirstOrDefault(d => + string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase)) + ?.XamlDocument.Root as XamlValueWithManipulationNode; + if (targetDocument is null) + { + return context.ParseError( + $"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node); + } + + var singleRootObject = ((XamlManipulationGroupNode)targetDocument.Manipulation) + .Children.OfType().Single(); + if (singleRootObject.Type != resourceDictionaryType) + { + return context.ParseError( + $"MergeResourceInclude can only include another ResourceDictionary", propertyNode, node); + } + + manipulationGroup.Children.Add(singleRootObject.Manipulation); + } + + if (manipulationGroup.Children.Any()) + { + // MergedDictionaries are read first, so we need ot inject our merged values in the beginning. + resourceDictionaryManipulation.Children.Insert(0, manipulationGroup); + } + + return node; + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 87a037c16a..5753a1008c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -104,6 +104,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType IStyle { get; } public IXamlType StyleInclude { get; } public IXamlType ResourceInclude { get; } + public IXamlType MergeResourceInclude { get; } public IXamlType IResourceDictionary { get; } public IXamlType ResourceDictionary { get; } public IXamlMethod ResourceDictionaryDeferredAdd { get; } @@ -236,6 +237,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle"); StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude"); ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude"); + MergeResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.MergeResourceInclude"); IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary"); ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary"); ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object, diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs index d031a6086b..0532287a67 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs @@ -18,4 +18,9 @@ internal class XamlDocumentParseException : XamlParseException { FilePath = path; } + + public XamlDocumentParseException(IXamlDocumentResource document, string message, IXamlLineInfo lineInfo) + : this(document.FileSource?.FilePath, message, lineInfo) + { + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index c59542c0f3..267bd2520e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -43,6 +43,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs index 3084964d44..4478a79d27 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs @@ -44,9 +44,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public CommandAccessor(WeakReference reference, Action execute, Func canExecute, ISet dependsOnProperties) { - Contract.Requires(reference != null); - - _reference = reference; + _reference = reference ?? throw new ArgumentNullException(nameof(reference)); _dependsOnProperties = dependsOnProperties; _command = new Command(reference, execute, canExecute); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs index ef11b06369..81f1224650 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs @@ -28,11 +28,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public AvaloniaPropertyAccessor(WeakReference reference, AvaloniaProperty property) { - Contract.Requires(reference != null); - Contract.Requires(property != null); - - _reference = reference; - _property = property; + _reference = reference ?? throw new ArgumentNullException(nameof(reference));; + _property = property ?? throw new ArgumentNullException(nameof(property));; } public AvaloniaObject Instance @@ -77,11 +74,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public InpcPropertyAccessor(WeakReference reference, IPropertyInfo property) { - Contract.Requires(reference != null); - Contract.Requires(property != null); - - _reference = reference; - _property = property; + _reference = reference ?? throw new ArgumentNullException(nameof(reference)); + _property = property ?? throw new ArgumentNullException(nameof(property)); } public override Type PropertyType => _property.PropertyType; diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs new file mode 100644 index 0000000000..d231a1d221 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/MergeResourceInclude.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Markup.Xaml.Styling; + +/// +/// Loads a resource dictionary from a specified URL. +/// +/// +/// If used from the XAML code, it is merged into the parent dictionary in the compile time. +/// When used in runtime, this type behaves like . +/// +[RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)] +public class MergeResourceInclude : ResourceInclude +{ + public MergeResourceInclude(Uri? baseUri) : base(baseUri) + { + } + + public MergeResourceInclude(IServiceProvider serviceProvider) : base(serviceProvider) + { + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs index 3d8a61364a..595b37f7d1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs @@ -9,6 +9,10 @@ namespace Avalonia.Markup.Xaml.Styling /// /// Loads a resource dictionary from a specified URL. /// + /// + /// If used from the XAML code, it is replaced with direct resource dictionary reference. + /// When used in runtime, this type might be unsafe with trimming and AOT. + /// [RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)] public class ResourceInclude : IResourceProvider { diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index bdde6e725c..b87aa64297 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -11,6 +11,10 @@ namespace Avalonia.Markup.Xaml.Styling /// /// Includes a style from a URL. /// + /// + /// If used from the XAML code, it is replaced with direct style reference. + /// When used in runtime, this type might be unsafe with trimming and AOT. + /// [RequiresUnreferencedCode(TrimmingMessages.StyleResourceIncludeRequiresUnreferenceCodeMessage)] public class StyleInclude : IStyle, IResourceProvider { diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index f18330ba8a..48e8c761eb 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -10,7 +10,6 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; using Avalonia.Media.Imaging; -using JetBrains.Annotations; using SkiaSharp; namespace Avalonia.Skia @@ -362,7 +361,7 @@ namespace Avalonia.Skia foreach (var boxShadow in boxShadows) { - if (!boxShadow.IsEmpty && !boxShadow.IsInset) + if (!boxShadow.IsDefault && !boxShadow.IsInset) { using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _currentOpacity)) { @@ -418,7 +417,7 @@ namespace Avalonia.Skia foreach (var boxShadow in boxShadows) { - if (!boxShadow.IsEmpty && boxShadow.IsInset) + if (!boxShadow.IsDefault && boxShadow.IsInset) { using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _currentOpacity)) { @@ -675,13 +674,14 @@ namespace Avalonia.Skia } } - [CanBeNull] - public object GetFeature(Type t) +#nullable enable + public object? GetFeature(Type t) { if (t == typeof(ISkiaSharpApiLeaseFeature)) return new SkiaLeaseFeature(this); return null; } +#nullable restore /// /// Configure paint wrapper for using gradient brush. @@ -1137,11 +1137,14 @@ namespace Avalonia.Skia if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0) { var srcDashes = pen.DashStyle.Dashes; - var dashesArray = new float[srcDashes.Count]; - for (var i = 0; i < srcDashes.Count; ++i) + var count = srcDashes.Count % 2 == 0 ? srcDashes.Count : srcDashes.Count * 2; + + var dashesArray = new float[count]; + + for (var i = 0; i < count; ++i) { - dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth; + dashesArray[i] = (float) srcDashes[i % srcDashes.Count] * paint.StrokeWidth; } var offset = (float)(pen.DashStyle.Offset * pen.Thickness); diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 9dbfd0f8d9..1ae47c8b7a 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -61,6 +61,8 @@ namespace Avalonia.Skia return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer); } + public bool IsCorrupted => false; + /// /// Check if two images info are compatible. /// diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 4037cc4a35..1141763097 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -244,7 +244,7 @@ namespace Avalonia.Skia public void Invalidate() { CachedStrokePath?.Dispose(); - CachedGeometryRenderBounds = Rect.Empty; + CachedGeometryRenderBounds = default; _cachedStrokeWidth = default(float); } } diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs index bdc3d075cf..484fd9f219 100644 --- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs @@ -1,8 +1,8 @@ using System; using Avalonia.Metadata; using Avalonia.Platform; -using JetBrains.Annotations; using SkiaSharp; +#nullable enable namespace Avalonia.Skia { @@ -10,7 +10,7 @@ namespace Avalonia.Skia [Unstable] public class GlyphRunImpl : IGlyphRunImpl { - public GlyphRunImpl([NotNull] SKTextBlob textBlob) + public GlyphRunImpl(SKTextBlob textBlob) { TextBlob = textBlob ?? throw new ArgumentNullException (nameof (textBlob)); } diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index 4a7d4d4ac8..b064445a0b 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using Avalonia.OpenGL; using Avalonia.OpenGL.Imaging; +using Avalonia.Platform; using SkiaSharp; namespace Avalonia.Skia @@ -8,7 +10,7 @@ namespace Avalonia.Skia /// /// Custom Skia gpu instance. /// - public interface ISkiaGpu + public interface ISkiaGpu : IPlatformGraphicsContext { /// /// Attempts to create custom render target from given surfaces. diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs index 5cc338a469..e19379df09 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -1,11 +1,13 @@ using System; using Avalonia.OpenGL; +using Avalonia.Platform; using SkiaSharp; using static Avalonia.OpenGL.GlConsts; namespace Avalonia.Skia { - public class FboSkiaSurface : ISkiaSurface + internal class FboSkiaSurface : ISkiaSurface { + private readonly GlSkiaGpu _gpu; private readonly GRContext _grContext; private readonly IGlContext _glContext; private readonly PixelSize _pixelSize; @@ -14,8 +16,9 @@ namespace Avalonia.Skia private int _texture; private static readonly bool[] TrueFalse = new[] { true, false }; - public FboSkiaSurface(GRContext grContext, IGlContext glContext, PixelSize pixelSize, GRSurfaceOrigin surfaceOrigin) + public FboSkiaSurface(GlSkiaGpu gpu, GRContext grContext, IGlContext glContext, PixelSize pixelSize, GRSurfaceOrigin surfaceOrigin) { + _gpu = gpu; _grContext = grContext; _glContext = glContext; _pixelSize = pixelSize; @@ -93,19 +96,33 @@ namespace Avalonia.Skia public void Dispose() { - using (_glContext.EnsureCurrent()) + try { - Surface?.Dispose(); - Surface = null; - var gl = _glContext.GlInterface; - if (_fbo != 0) + using (_glContext.EnsureCurrent()) { - gl.DeleteFramebuffer(_fbo); - gl.DeleteTexture(_texture); - gl.DeleteRenderbuffer(_depthStencil); - _fbo = _texture = _depthStencil = 0; + Surface?.Dispose(); + Surface = null; + var gl = _glContext.GlInterface; + if (_fbo != 0) + { + gl.DeleteFramebuffer(_fbo); + gl.DeleteTexture(_texture); + gl.DeleteRenderbuffer(_depthStencil); + } } } + catch (PlatformGraphicsContextLostException) + { + if (Surface != null) + // We need to dispose SKSurface _after_ GRContext.Abandon was called, + // otherwise it will try to do OpenGL calls without a proper context + _gpu.AddPostDispose(Surface.Dispose); + Surface = null; + } + finally + { + _fbo = _texture = _depthStencil = 0; + } } public SKSurface Surface { get; private set; } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 476ecc4a39..5bf1272c2f 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -14,10 +14,11 @@ namespace Avalonia.Skia private readonly GRContext _grContext; private IGlPlatformSurfaceRenderTarget _surface; - public GlRenderTarget(GRContext grContext, IGlPlatformSurface glSurface) + public GlRenderTarget(GRContext grContext, IGlContext glContext, IGlPlatformSurface glSurface) { _grContext = grContext; - _surface = glSurface.CreateGlRenderTarget(); + using (glContext.EnsureCurrent()) + _surface = glSurface.CreateGlRenderTarget(glContext); } public void Dispose() => _surface.Dispose(); diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index 08e0fbd808..cdba3b9ea2 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -5,21 +5,22 @@ using Avalonia.Logging; using Avalonia.OpenGL; using Avalonia.OpenGL.Imaging; using Avalonia.OpenGL.Surfaces; +using Avalonia.Platform; using SkiaSharp; namespace Avalonia.Skia { - class GlSkiaGpu : IOpenGlAwareSkiaGpu + class GlSkiaGpu : IOpenGlAwareSkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature { private GRContext _grContext; private IGlContext _glContext; + private List _postDisposeCallbacks = new(); private bool? _canCreateSurfaces; - public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes) + public GlSkiaGpu(IGlContext context, long? maxResourceBytes) { - var context = openGl.PrimaryContext; _glContext = context; - using (context.MakeCurrent()) + using (_glContext.EnsureCurrent()) { using (var iface = context.Version.Type == GlProfileType.OpenGL ? GRGlInterface.CreateOpenGl(proc => context.GlInterface.GetProcAddress(proc)) : @@ -34,13 +35,34 @@ namespace Avalonia.Skia } } + class SurfaceWrapper : IGlPlatformSurface + { + private readonly object _surface; + + public SurfaceWrapper( object surface) + { + _surface = surface; + } + + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) + { + var feature = context.TryGetFeature()!; + return feature.CreateRenderTarget(context, _surface); + } + } + public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable surfaces) { + var customRenderTargetFactory = _glContext.TryGetFeature(); foreach (var surface in surfaces) { + if (customRenderTargetFactory?.CanRenderToSurface(_glContext, surface) == true) + { + return new GlRenderTarget(_grContext, _glContext, new SurfaceWrapper(surface)); + } if (surface is IGlPlatformSurface glSurface) { - return new GlRenderTarget(_grContext, glSurface); + return new GlRenderTarget(_grContext, _glContext, glSurface); } } @@ -62,7 +84,8 @@ namespace Avalonia.Skia return null; try { - var surface = new FboSkiaSurface(_grContext, _glContext, size, session?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft); + var surface = new FboSkiaSurface(this, _grContext, _glContext, size, + session?.SurfaceOrigin ?? GRSurfaceOrigin.TopLeft); _canCreateSurfaces = true; return surface; } @@ -75,6 +98,40 @@ namespace Avalonia.Skia } } + public bool CanCreateSharedContext => _glContext.CanCreateSharedContext; + + public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) => + _glContext.CreateSharedContext(preferredVersions); + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi); + + public void Dispose() + { + if (_glContext.IsLost) + _grContext.AbandonContext(); + else + _grContext.AbandonContext(true); + _grContext.Dispose(); + + lock(_postDisposeCallbacks) + foreach (var cb in _postDisposeCallbacks) + cb(); + } + + public bool IsLost => _glContext.IsLost; + public IDisposable EnsureCurrent() => _glContext.EnsureCurrent(); + + public object TryGetFeature(Type featureType) + { + if (featureType == typeof(IOpenGlTextureSharingRenderInterfaceContextFeature)) + return this; + return null; + } + + public void AddPostDispose(Action dispose) + { + lock (_postDisposeCallbacks) + _postDisposeCallbacks.Add(dispose); + } } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index 5e02500bf0..d8bff7cfc8 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -109,7 +109,9 @@ namespace Avalonia.Skia using (_context.EnsureCurrent()) { var glVersion = _context.Version; - InternalFormat = glVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8; + InternalFormat = glVersion.Type == GlProfileType.OpenGLES && glVersion.Major == 2 + ? GL_RGBA + : GL_RGBA8; _context.GlInterface.GetIntegerv(GL_FRAMEBUFFER_BINDING, out _fbo); if (_fbo == 0) @@ -145,7 +147,7 @@ namespace Avalonia.Skia public void Present() { - using (_context.MakeCurrent()) + using (_context.EnsureCurrent()) { if (_disposed) throw new ObjectDisposedException(nameof(SharedOpenGlBitmapAttachment)); diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index 6626546c0c..6b4a7a3409 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs @@ -6,7 +6,7 @@ namespace Avalonia.Skia /// /// Adapts to be used within our rendering pipeline. /// - internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo + internal class SkiaGpuRenderTarget : IRenderTarget { private readonly ISkiaGpu _skiaGpu; private readonly ISkiaGpuRenderTarget _renderTarget; diff --git a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs index b4dd754822..4cb1430a3b 100644 --- a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs @@ -34,6 +34,7 @@ namespace Avalonia.Skia.Helpers /// Save Skia image to a stream. /// /// Image to save + /// The output stream to save the image. /// /// The optional quality for PNG compression. /// The quality value is interpreted from 0 - 100. If quality is null diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index f34e25299c..b202b60cdf 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -18,26 +18,28 @@ namespace Avalonia.Skia /// /// Skia platform render interface. /// - internal class PlatformRenderInterface : IPlatformRenderInterface, IOpenGlAwarePlatformRenderInterface + internal class PlatformRenderInterface : IPlatformRenderInterface { - private readonly ISkiaGpu _skiaGpu; + private readonly long? _maxResourceBytes; - public PlatformRenderInterface(ISkiaGpu skiaGpu, long? maxResourceBytes = null) + public PlatformRenderInterface(long? maxResourceBytes = null) { + _maxResourceBytes = maxResourceBytes; DefaultPixelFormat = SKImageInfo.PlatformColorType.ToPixelFormat(); + } - if (skiaGpu != null) - { - _skiaGpu = skiaGpu; - return; - } - var gl = AvaloniaLocator.Current.GetService(); - if (gl != null) - _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes); + public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) + { + if (graphicsContext == null) + return new SkiaContext(null); + if (graphicsContext is ISkiaGpu skiaGpu) + return new SkiaContext(skiaGpu); + if (graphicsContext is IGlContext gl) + return new SkiaContext(new GlSkiaGpu(gl, _maxResourceBytes)); + throw new ArgumentException("Graphics context of type is not supported"); } - public bool SupportsIndividualRoundRects => true; public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; @@ -205,43 +207,12 @@ namespace Avalonia.Skia return new SurfaceRenderTarget(createInfo); } - /// - public IRenderTarget CreateRenderTarget(IEnumerable surfaces) - { - if (!(surfaces is IList)) - surfaces = surfaces.ToList(); - var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces); - if (gpuRenderTarget != null) - { - return new SkiaGpuRenderTarget(_skiaGpu, gpuRenderTarget); - } - - foreach (var surface in surfaces) - { - if (surface is IFramebufferPlatformSurface framebufferSurface) - return new FramebufferRenderTarget(framebufferSurface); - } - - throw new NotSupportedException( - "Don't know how to create a Skia render target from any of provided surfaces"); - } - /// public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat format, AlphaFormat alphaFormat) { return new WriteableBitmapImpl(size, dpi, format, alphaFormat); } - public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) - { - if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) - return glAware.CreateOpenGlBitmap(size, dpi); - if (_skiaGpu == null) - throw new PlatformNotSupportedException("GPU acceleration is not available"); - throw new PlatformNotSupportedException( - "Current GPU acceleration backend does not support OpenGL integration"); - } - public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { diff --git a/src/Skia/Avalonia.Skia/SkiaBackendContext.cs b/src/Skia/Avalonia.Skia/SkiaBackendContext.cs new file mode 100644 index 0000000000..4949f4a50d --- /dev/null +++ b/src/Skia/Avalonia.Skia/SkiaBackendContext.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; + +namespace Avalonia.Skia; + +internal class SkiaContext : IPlatformRenderInterfaceContext +{ + private ISkiaGpu _gpu; + + public SkiaContext(ISkiaGpu gpu) + { + _gpu = gpu; + } + + public void Dispose() + { + _gpu?.Dispose(); + _gpu = null; + } + + /// + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + if (!(surfaces is IList)) + surfaces = surfaces.ToList(); + var gpuRenderTarget = _gpu?.TryCreateRenderTarget(surfaces); + if (gpuRenderTarget != null) + { + return new SkiaGpuRenderTarget(_gpu, gpuRenderTarget); + } + + foreach (var surface in surfaces) + { + if (surface is IFramebufferPlatformSurface framebufferSurface) + return new FramebufferRenderTarget(framebufferSurface); + } + + throw new NotSupportedException( + "Don't know how to create a Skia render target from any of provided surfaces"); + } + + public object TryGetFeature(Type featureType) => _gpu?.TryGetFeature(featureType); +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/SkiaOptions.cs b/src/Skia/Avalonia.Skia/SkiaOptions.cs index 493263677d..b3c3056a58 100644 --- a/src/Skia/Avalonia.Skia/SkiaOptions.cs +++ b/src/Skia/Avalonia.Skia/SkiaOptions.cs @@ -8,11 +8,6 @@ namespace Avalonia /// public class SkiaOptions { - /// - /// Custom gpu factory to use. Can be used to customize behavior of Skia renderer. - /// - public Func CustomGpuFactory { get; set; } - /// /// The maximum number of bytes for video memory to store textures and resources. /// diff --git a/src/Skia/Avalonia.Skia/SkiaPlatform.cs b/src/Skia/Avalonia.Skia/SkiaPlatform.cs index 9a5725e06f..27f2631db8 100644 --- a/src/Skia/Avalonia.Skia/SkiaPlatform.cs +++ b/src/Skia/Avalonia.Skia/SkiaPlatform.cs @@ -17,8 +17,7 @@ namespace Avalonia.Skia public static void Initialize(SkiaOptions options) { - var customGpu = options.CustomGpuFactory?.Invoke(); - var renderInterface = new PlatformRenderInterface(customGpu, options.MaxGpuResourceSizeBytes); + var renderInterface = new PlatformRenderInterface(options.MaxGpuResourceSizeBytes); AvaloniaLocator.CurrentMutable .Bind().ToConstant(renderInterface) diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs index 86450690e6..df847d2224 100644 --- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs @@ -34,7 +34,7 @@ namespace Avalonia.Skia /// /// Initializes a new instance of the class. /// - public StreamGeometryImpl() : this(CreateEmptyPath(), Rect.Empty) + public StreamGeometryImpl() : this(CreateEmptyPath(), default) { } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 74e3ebc51d..a7998353d9 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -107,6 +107,8 @@ namespace Avalonia.Skia return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++)); } + public bool IsCorrupted => _gpu?.IsLost == true; + /// public Vector Dpi { get; } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index d437f514bb..dcb267b2a3 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -217,8 +217,7 @@ namespace Avalonia.Skia public int RowBytes => _bitmap.RowBytes; /// - public Vector Dpi { get; } = SkiaPlatform.DefaultDpi; - + public Vector Dpi => _parent.Dpi; /// public PixelFormat Format => _bitmap.ColorType.ToPixelFormat(); } diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index fb7831415d..5fecaef100 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 4d307c9762..a5f77230b7 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -113,7 +113,7 @@ namespace Avalonia.Direct2D1 SharpDX.Configuration.EnableReleaseOnFinalizer = true; } - public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + private IRenderTarget CreateRenderTarget(IEnumerable surfaces) { foreach (var s in surfaces) { @@ -223,6 +223,26 @@ namespace Avalonia.Direct2D1 return new GlyphRunImpl(run); } + class D2DApi : IPlatformRenderInterfaceContext + { + private readonly Direct2D1Platform _platform; + + public D2DApi(Direct2D1Platform platform) + { + _platform = platform; + } + public object TryGetFeature(Type featureType) => null; + + public void Dispose() + { + } + + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => _platform.CreateRenderTarget(surfaces); + } + + public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => + new D2DApi(this); + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface) diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index 2c0adcac32..02932c52da 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -38,6 +38,8 @@ namespace Avalonia.Direct2D1 }); } + public bool IsCorrupted => false; + public IDrawingContextLayerImpl CreateLayer(Size size) { var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget(); diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs index f7e7ed3dc2..984a24fb30 100644 --- a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs @@ -35,6 +35,8 @@ namespace Avalonia.Direct2D1 .CreateDrawingContext(visualBrushRenderer); } + public bool IsCorrupted => false; + class FramebufferShim : WicRenderTargetBitmapImpl { private readonly ILockedFramebuffer _target; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index c5f8e837ce..84f11acdd7 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -37,6 +37,8 @@ namespace Avalonia.Direct2D1.Media.Imaging return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, null, () => Version++); } + public bool IsCorrupted => false; + public void Blit(IDrawingContextImpl context) => throw new NotSupportedException(); public bool CanBlit => false; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index 8c9d01f37d..9f0d48dbc7 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -39,6 +39,8 @@ namespace Avalonia.Direct2D1.Media public virtual IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) => CreateDrawingContext(visualBrushRenderer, null); + public bool IsCorrupted => false; + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback) { return new DrawingContextImpl(visualBrushRenderer, null, _renderTarget, finishedCallback: () => diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index 885be132a4..2e40bdd9d1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -52,7 +52,7 @@ namespace Avalonia.Direct2D1.Media.Imaging public IntPtr Address => _lock.Data.DataPointer; public PixelSize Size => _lock.Size.ToAvalonia(); public int RowBytes => _lock.Stride; - public Vector Dpi { get; } = new Vector(96, 96); + public Vector Dpi => _parent.Dpi; public PixelFormat Format => _format; } diff --git a/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs b/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs index e302e71102..1cdf7661df 100644 --- a/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs +++ b/src/Windows/Avalonia.Direct2D1/OptionalDispose.cs @@ -2,7 +2,7 @@ namespace Avalonia.Direct2D1 { - public readonly struct OptionalDispose : IDisposable where T : IDisposable + public readonly record struct OptionalDispose : IDisposable where T : IDisposable { private readonly bool _dispose; diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index d04c616bd9..1a749c1a7f 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -30,6 +30,8 @@ namespace Avalonia.Direct2D1 return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); } + public bool IsCorrupted => false; + public IDrawingContextLayerImpl CreateLayer(Size size) { return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size); diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index f319cfae03..4935e3db48 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -35,6 +35,8 @@ namespace Avalonia.Direct2D1 return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); } + public bool IsCorrupted => false; + public IDrawingContextLayerImpl CreateLayer(Size size) { if (_deviceContext == null) diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 1dcf45aa4e..16a20c855f 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -87,9 +87,11 @@ namespace Avalonia.Win32.Interop.Wpf _ttl.ScalingChanged?.Invoke(_ttl.RenderScaling); } + public IRenderer CreateRenderer(IRenderRoot root) { - return new ImmediateRenderer((Visual)root); + var mgr = new PlatformRenderInterfaceContextManager(null); + return new ImmediateRenderer((Visual)root, () => mgr.CreateRenderTarget(_surfaces), mgr); } public void Dispose() diff --git a/src/Avalonia.OpenGL/AngleOptions.cs b/src/Windows/Avalonia.Win32/AngleOptions.cs similarity index 90% rename from src/Avalonia.OpenGL/AngleOptions.cs rename to src/Windows/Avalonia.Win32/AngleOptions.cs index 0807eb7ab4..076ddd2a5d 100644 --- a/src/Avalonia.OpenGL/AngleOptions.cs +++ b/src/Windows/Avalonia.Win32/AngleOptions.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using Avalonia.OpenGL; -namespace Avalonia.OpenGL +namespace Avalonia.Win32 { public class AngleOptions { diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index e69e0ee219..eb51b7fd07 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -16,11 +16,13 @@ + + + + - - $(NoWarn);CA1416 diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs index 3a67da530f..a749ed1b45 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Avalonia.Win32.DxgiSwapchain +namespace Avalonia.Win32.DirectX { internal enum D3D_FEATURE_LEVEL { @@ -136,4 +136,53 @@ namespace Avalonia.Win32.DxgiSwapchain DXGI_ALPHA_MODE_FORCE_DWORD = (unchecked((int)0xffffffff)), } + + internal enum D3D_DRIVER_TYPE + { + D3D_DRIVER_TYPE_UNKNOWN = 0, + D3D_DRIVER_TYPE_HARDWARE, + D3D_DRIVER_TYPE_REFERENCE, + D3D_DRIVER_TYPE_NULL, + D3D_DRIVER_TYPE_SOFTWARE, + D3D_DRIVER_TYPE_WARP + } + + internal enum DXGI_ERROR : uint + { + DXGI_ERROR_ACCESS_DENIED = 0x887A002B, + DXGI_ERROR_ACCESS_LOST = 0x887A0026, + DXGI_ERROR_ALREADY_EXISTS = 0x887A0036, + DXGI_ERROR_CANNOT_PROTECT_CONTENT = 0x887A002A, + DXGI_ERROR_DEVICE_HUNG = 0x887A0006, + DXGI_ERROR_DEVICE_REMOVED = 0x887A0005, + DXGI_ERROR_DEVICE_RESET = 0x887A0007, + DXGI_ERROR_DRIVER_INTERNAL_ERROR = 0x887A0020, + DXGI_ERROR_FRAME_STATISTICS_DISJOINT = 0x887A000B, + DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE = 0x887A000C, + DXGI_ERROR_INVALID_CALL = 0x887A0001, + DXGI_ERROR_MORE_DATA = 0x887A0003, + DXGI_ERROR_NAME_ALREADY_EXISTS = 0x887A002C, + DXGI_ERROR_NONEXCLUSIVE = 0x887A0021, + DXGI_ERROR_NOT_CURRENTLY_AVAILABLE = 0x887A0022, + DXGI_ERROR_NOT_FOUND = 0x887A0002, + DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED = 0x887A0023, + DXGI_ERROR_REMOTE_OUTOFMEMORY = 0x887A0024, + DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE = 0x887A0029, + DXGI_ERROR_SDK_COMPONENT_MISSING = 0x887A002D, + DXGI_ERROR_SESSION_DISCONNECTED = 0x887A0028, + DXGI_ERROR_UNSUPPORTED = 0x887A0004, + DXGI_ERROR_WAIT_TIMEOUT = 0x887A0027, + DXGI_ERROR_WAS_STILL_DRAWING = 0x887A000A + } + + internal static class DxgiErrorExtensions + { + public static bool IsDeviceLostError(this DXGI_ERROR error) + { + return error is DXGI_ERROR.DXGI_ERROR_DEVICE_REMOVED + or DXGI_ERROR.DXGI_ERROR_DEVICE_HUNG + or DXGI_ERROR.DXGI_ERROR_DEVICE_RESET + or DXGI_ERROR.DXGI_ERROR_NOT_CURRENTLY_AVAILABLE; + } + } } diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs index d606b00109..47451831a6 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs @@ -6,8 +6,10 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using static Avalonia.Win32.Interop.UnmanagedMethods; +// ReSharper disable InconsistentNaming +#pragma warning disable CS0649 -namespace Avalonia.Win32.DxgiSwapchain +namespace Avalonia.Win32.DirectX { #nullable enable public unsafe struct HANDLE @@ -36,1092 +38,13 @@ namespace Avalonia.Win32.DxgiSwapchain public override string ToString() => ((IntPtr)Value).ToString(); } - internal unsafe partial struct MONITORINFOEXW + internal unsafe struct MONITORINFOEXW { internal MONITORINFO Base; internal fixed ushort szDevice[32]; } - - internal unsafe struct DXGI_GAMMA_CONTROL - { - public DXGI_RGB Scale; - - public DXGI_RGB Offset; - - public _GammaCurve_e__FixedBuffer GammaCurve; - - public partial struct _GammaCurve_e__FixedBuffer - { - public DXGI_RGB e0; - public DXGI_RGB e1; - public DXGI_RGB e2; - public DXGI_RGB e3; - public DXGI_RGB e4; - public DXGI_RGB e5; - public DXGI_RGB e6; - public DXGI_RGB e7; - public DXGI_RGB e8; - public DXGI_RGB e9; - public DXGI_RGB e10; - public DXGI_RGB e11; - public DXGI_RGB e12; - public DXGI_RGB e13; - public DXGI_RGB e14; - public DXGI_RGB e15; - public DXGI_RGB e16; - public DXGI_RGB e17; - public DXGI_RGB e18; - public DXGI_RGB e19; - public DXGI_RGB e20; - public DXGI_RGB e21; - public DXGI_RGB e22; - public DXGI_RGB e23; - public DXGI_RGB e24; - public DXGI_RGB e25; - public DXGI_RGB e26; - public DXGI_RGB e27; - public DXGI_RGB e28; - public DXGI_RGB e29; - public DXGI_RGB e30; - public DXGI_RGB e31; - public DXGI_RGB e32; - public DXGI_RGB e33; - public DXGI_RGB e34; - public DXGI_RGB e35; - public DXGI_RGB e36; - public DXGI_RGB e37; - public DXGI_RGB e38; - public DXGI_RGB e39; - public DXGI_RGB e40; - public DXGI_RGB e41; - public DXGI_RGB e42; - public DXGI_RGB e43; - public DXGI_RGB e44; - public DXGI_RGB e45; - public DXGI_RGB e46; - public DXGI_RGB e47; - public DXGI_RGB e48; - public DXGI_RGB e49; - public DXGI_RGB e50; - public DXGI_RGB e51; - public DXGI_RGB e52; - public DXGI_RGB e53; - public DXGI_RGB e54; - public DXGI_RGB e55; - public DXGI_RGB e56; - public DXGI_RGB e57; - public DXGI_RGB e58; - public DXGI_RGB e59; - public DXGI_RGB e60; - public DXGI_RGB e61; - public DXGI_RGB e62; - public DXGI_RGB e63; - public DXGI_RGB e64; - public DXGI_RGB e65; - public DXGI_RGB e66; - public DXGI_RGB e67; - public DXGI_RGB e68; - public DXGI_RGB e69; - public DXGI_RGB e70; - public DXGI_RGB e71; - public DXGI_RGB e72; - public DXGI_RGB e73; - public DXGI_RGB e74; - public DXGI_RGB e75; - public DXGI_RGB e76; - public DXGI_RGB e77; - public DXGI_RGB e78; - public DXGI_RGB e79; - public DXGI_RGB e80; - public DXGI_RGB e81; - public DXGI_RGB e82; - public DXGI_RGB e83; - public DXGI_RGB e84; - public DXGI_RGB e85; - public DXGI_RGB e86; - public DXGI_RGB e87; - public DXGI_RGB e88; - public DXGI_RGB e89; - public DXGI_RGB e90; - public DXGI_RGB e91; - public DXGI_RGB e92; - public DXGI_RGB e93; - public DXGI_RGB e94; - public DXGI_RGB e95; - public DXGI_RGB e96; - public DXGI_RGB e97; - public DXGI_RGB e98; - public DXGI_RGB e99; - public DXGI_RGB e100; - public DXGI_RGB e101; - public DXGI_RGB e102; - public DXGI_RGB e103; - public DXGI_RGB e104; - public DXGI_RGB e105; - public DXGI_RGB e106; - public DXGI_RGB e107; - public DXGI_RGB e108; - public DXGI_RGB e109; - public DXGI_RGB e110; - public DXGI_RGB e111; - public DXGI_RGB e112; - public DXGI_RGB e113; - public DXGI_RGB e114; - public DXGI_RGB e115; - public DXGI_RGB e116; - public DXGI_RGB e117; - public DXGI_RGB e118; - public DXGI_RGB e119; - public DXGI_RGB e120; - public DXGI_RGB e121; - public DXGI_RGB e122; - public DXGI_RGB e123; - public DXGI_RGB e124; - public DXGI_RGB e125; - public DXGI_RGB e126; - public DXGI_RGB e127; - public DXGI_RGB e128; - public DXGI_RGB e129; - public DXGI_RGB e130; - public DXGI_RGB e131; - public DXGI_RGB e132; - public DXGI_RGB e133; - public DXGI_RGB e134; - public DXGI_RGB e135; - public DXGI_RGB e136; - public DXGI_RGB e137; - public DXGI_RGB e138; - public DXGI_RGB e139; - public DXGI_RGB e140; - public DXGI_RGB e141; - public DXGI_RGB e142; - public DXGI_RGB e143; - public DXGI_RGB e144; - public DXGI_RGB e145; - public DXGI_RGB e146; - public DXGI_RGB e147; - public DXGI_RGB e148; - public DXGI_RGB e149; - public DXGI_RGB e150; - public DXGI_RGB e151; - public DXGI_RGB e152; - public DXGI_RGB e153; - public DXGI_RGB e154; - public DXGI_RGB e155; - public DXGI_RGB e156; - public DXGI_RGB e157; - public DXGI_RGB e158; - public DXGI_RGB e159; - public DXGI_RGB e160; - public DXGI_RGB e161; - public DXGI_RGB e162; - public DXGI_RGB e163; - public DXGI_RGB e164; - public DXGI_RGB e165; - public DXGI_RGB e166; - public DXGI_RGB e167; - public DXGI_RGB e168; - public DXGI_RGB e169; - public DXGI_RGB e170; - public DXGI_RGB e171; - public DXGI_RGB e172; - public DXGI_RGB e173; - public DXGI_RGB e174; - public DXGI_RGB e175; - public DXGI_RGB e176; - public DXGI_RGB e177; - public DXGI_RGB e178; - public DXGI_RGB e179; - public DXGI_RGB e180; - public DXGI_RGB e181; - public DXGI_RGB e182; - public DXGI_RGB e183; - public DXGI_RGB e184; - public DXGI_RGB e185; - public DXGI_RGB e186; - public DXGI_RGB e187; - public DXGI_RGB e188; - public DXGI_RGB e189; - public DXGI_RGB e190; - public DXGI_RGB e191; - public DXGI_RGB e192; - public DXGI_RGB e193; - public DXGI_RGB e194; - public DXGI_RGB e195; - public DXGI_RGB e196; - public DXGI_RGB e197; - public DXGI_RGB e198; - public DXGI_RGB e199; - public DXGI_RGB e200; - public DXGI_RGB e201; - public DXGI_RGB e202; - public DXGI_RGB e203; - public DXGI_RGB e204; - public DXGI_RGB e205; - public DXGI_RGB e206; - public DXGI_RGB e207; - public DXGI_RGB e208; - public DXGI_RGB e209; - public DXGI_RGB e210; - public DXGI_RGB e211; - public DXGI_RGB e212; - public DXGI_RGB e213; - public DXGI_RGB e214; - public DXGI_RGB e215; - public DXGI_RGB e216; - public DXGI_RGB e217; - public DXGI_RGB e218; - public DXGI_RGB e219; - public DXGI_RGB e220; - public DXGI_RGB e221; - public DXGI_RGB e222; - public DXGI_RGB e223; - public DXGI_RGB e224; - public DXGI_RGB e225; - public DXGI_RGB e226; - public DXGI_RGB e227; - public DXGI_RGB e228; - public DXGI_RGB e229; - public DXGI_RGB e230; - public DXGI_RGB e231; - public DXGI_RGB e232; - public DXGI_RGB e233; - public DXGI_RGB e234; - public DXGI_RGB e235; - public DXGI_RGB e236; - public DXGI_RGB e237; - public DXGI_RGB e238; - public DXGI_RGB e239; - public DXGI_RGB e240; - public DXGI_RGB e241; - public DXGI_RGB e242; - public DXGI_RGB e243; - public DXGI_RGB e244; - public DXGI_RGB e245; - public DXGI_RGB e246; - public DXGI_RGB e247; - public DXGI_RGB e248; - public DXGI_RGB e249; - public DXGI_RGB e250; - public DXGI_RGB e251; - public DXGI_RGB e252; - public DXGI_RGB e253; - public DXGI_RGB e254; - public DXGI_RGB e255; - public DXGI_RGB e256; - public DXGI_RGB e257; - public DXGI_RGB e258; - public DXGI_RGB e259; - public DXGI_RGB e260; - public DXGI_RGB e261; - public DXGI_RGB e262; - public DXGI_RGB e263; - public DXGI_RGB e264; - public DXGI_RGB e265; - public DXGI_RGB e266; - public DXGI_RGB e267; - public DXGI_RGB e268; - public DXGI_RGB e269; - public DXGI_RGB e270; - public DXGI_RGB e271; - public DXGI_RGB e272; - public DXGI_RGB e273; - public DXGI_RGB e274; - public DXGI_RGB e275; - public DXGI_RGB e276; - public DXGI_RGB e277; - public DXGI_RGB e278; - public DXGI_RGB e279; - public DXGI_RGB e280; - public DXGI_RGB e281; - public DXGI_RGB e282; - public DXGI_RGB e283; - public DXGI_RGB e284; - public DXGI_RGB e285; - public DXGI_RGB e286; - public DXGI_RGB e287; - public DXGI_RGB e288; - public DXGI_RGB e289; - public DXGI_RGB e290; - public DXGI_RGB e291; - public DXGI_RGB e292; - public DXGI_RGB e293; - public DXGI_RGB e294; - public DXGI_RGB e295; - public DXGI_RGB e296; - public DXGI_RGB e297; - public DXGI_RGB e298; - public DXGI_RGB e299; - public DXGI_RGB e300; - public DXGI_RGB e301; - public DXGI_RGB e302; - public DXGI_RGB e303; - public DXGI_RGB e304; - public DXGI_RGB e305; - public DXGI_RGB e306; - public DXGI_RGB e307; - public DXGI_RGB e308; - public DXGI_RGB e309; - public DXGI_RGB e310; - public DXGI_RGB e311; - public DXGI_RGB e312; - public DXGI_RGB e313; - public DXGI_RGB e314; - public DXGI_RGB e315; - public DXGI_RGB e316; - public DXGI_RGB e317; - public DXGI_RGB e318; - public DXGI_RGB e319; - public DXGI_RGB e320; - public DXGI_RGB e321; - public DXGI_RGB e322; - public DXGI_RGB e323; - public DXGI_RGB e324; - public DXGI_RGB e325; - public DXGI_RGB e326; - public DXGI_RGB e327; - public DXGI_RGB e328; - public DXGI_RGB e329; - public DXGI_RGB e330; - public DXGI_RGB e331; - public DXGI_RGB e332; - public DXGI_RGB e333; - public DXGI_RGB e334; - public DXGI_RGB e335; - public DXGI_RGB e336; - public DXGI_RGB e337; - public DXGI_RGB e338; - public DXGI_RGB e339; - public DXGI_RGB e340; - public DXGI_RGB e341; - public DXGI_RGB e342; - public DXGI_RGB e343; - public DXGI_RGB e344; - public DXGI_RGB e345; - public DXGI_RGB e346; - public DXGI_RGB e347; - public DXGI_RGB e348; - public DXGI_RGB e349; - public DXGI_RGB e350; - public DXGI_RGB e351; - public DXGI_RGB e352; - public DXGI_RGB e353; - public DXGI_RGB e354; - public DXGI_RGB e355; - public DXGI_RGB e356; - public DXGI_RGB e357; - public DXGI_RGB e358; - public DXGI_RGB e359; - public DXGI_RGB e360; - public DXGI_RGB e361; - public DXGI_RGB e362; - public DXGI_RGB e363; - public DXGI_RGB e364; - public DXGI_RGB e365; - public DXGI_RGB e366; - public DXGI_RGB e367; - public DXGI_RGB e368; - public DXGI_RGB e369; - public DXGI_RGB e370; - public DXGI_RGB e371; - public DXGI_RGB e372; - public DXGI_RGB e373; - public DXGI_RGB e374; - public DXGI_RGB e375; - public DXGI_RGB e376; - public DXGI_RGB e377; - public DXGI_RGB e378; - public DXGI_RGB e379; - public DXGI_RGB e380; - public DXGI_RGB e381; - public DXGI_RGB e382; - public DXGI_RGB e383; - public DXGI_RGB e384; - public DXGI_RGB e385; - public DXGI_RGB e386; - public DXGI_RGB e387; - public DXGI_RGB e388; - public DXGI_RGB e389; - public DXGI_RGB e390; - public DXGI_RGB e391; - public DXGI_RGB e392; - public DXGI_RGB e393; - public DXGI_RGB e394; - public DXGI_RGB e395; - public DXGI_RGB e396; - public DXGI_RGB e397; - public DXGI_RGB e398; - public DXGI_RGB e399; - public DXGI_RGB e400; - public DXGI_RGB e401; - public DXGI_RGB e402; - public DXGI_RGB e403; - public DXGI_RGB e404; - public DXGI_RGB e405; - public DXGI_RGB e406; - public DXGI_RGB e407; - public DXGI_RGB e408; - public DXGI_RGB e409; - public DXGI_RGB e410; - public DXGI_RGB e411; - public DXGI_RGB e412; - public DXGI_RGB e413; - public DXGI_RGB e414; - public DXGI_RGB e415; - public DXGI_RGB e416; - public DXGI_RGB e417; - public DXGI_RGB e418; - public DXGI_RGB e419; - public DXGI_RGB e420; - public DXGI_RGB e421; - public DXGI_RGB e422; - public DXGI_RGB e423; - public DXGI_RGB e424; - public DXGI_RGB e425; - public DXGI_RGB e426; - public DXGI_RGB e427; - public DXGI_RGB e428; - public DXGI_RGB e429; - public DXGI_RGB e430; - public DXGI_RGB e431; - public DXGI_RGB e432; - public DXGI_RGB e433; - public DXGI_RGB e434; - public DXGI_RGB e435; - public DXGI_RGB e436; - public DXGI_RGB e437; - public DXGI_RGB e438; - public DXGI_RGB e439; - public DXGI_RGB e440; - public DXGI_RGB e441; - public DXGI_RGB e442; - public DXGI_RGB e443; - public DXGI_RGB e444; - public DXGI_RGB e445; - public DXGI_RGB e446; - public DXGI_RGB e447; - public DXGI_RGB e448; - public DXGI_RGB e449; - public DXGI_RGB e450; - public DXGI_RGB e451; - public DXGI_RGB e452; - public DXGI_RGB e453; - public DXGI_RGB e454; - public DXGI_RGB e455; - public DXGI_RGB e456; - public DXGI_RGB e457; - public DXGI_RGB e458; - public DXGI_RGB e459; - public DXGI_RGB e460; - public DXGI_RGB e461; - public DXGI_RGB e462; - public DXGI_RGB e463; - public DXGI_RGB e464; - public DXGI_RGB e465; - public DXGI_RGB e466; - public DXGI_RGB e467; - public DXGI_RGB e468; - public DXGI_RGB e469; - public DXGI_RGB e470; - public DXGI_RGB e471; - public DXGI_RGB e472; - public DXGI_RGB e473; - public DXGI_RGB e474; - public DXGI_RGB e475; - public DXGI_RGB e476; - public DXGI_RGB e477; - public DXGI_RGB e478; - public DXGI_RGB e479; - public DXGI_RGB e480; - public DXGI_RGB e481; - public DXGI_RGB e482; - public DXGI_RGB e483; - public DXGI_RGB e484; - public DXGI_RGB e485; - public DXGI_RGB e486; - public DXGI_RGB e487; - public DXGI_RGB e488; - public DXGI_RGB e489; - public DXGI_RGB e490; - public DXGI_RGB e491; - public DXGI_RGB e492; - public DXGI_RGB e493; - public DXGI_RGB e494; - public DXGI_RGB e495; - public DXGI_RGB e496; - public DXGI_RGB e497; - public DXGI_RGB e498; - public DXGI_RGB e499; - public DXGI_RGB e500; - public DXGI_RGB e501; - public DXGI_RGB e502; - public DXGI_RGB e503; - public DXGI_RGB e504; - public DXGI_RGB e505; - public DXGI_RGB e506; - public DXGI_RGB e507; - public DXGI_RGB e508; - public DXGI_RGB e509; - public DXGI_RGB e510; - public DXGI_RGB e511; - public DXGI_RGB e512; - public DXGI_RGB e513; - public DXGI_RGB e514; - public DXGI_RGB e515; - public DXGI_RGB e516; - public DXGI_RGB e517; - public DXGI_RGB e518; - public DXGI_RGB e519; - public DXGI_RGB e520; - public DXGI_RGB e521; - public DXGI_RGB e522; - public DXGI_RGB e523; - public DXGI_RGB e524; - public DXGI_RGB e525; - public DXGI_RGB e526; - public DXGI_RGB e527; - public DXGI_RGB e528; - public DXGI_RGB e529; - public DXGI_RGB e530; - public DXGI_RGB e531; - public DXGI_RGB e532; - public DXGI_RGB e533; - public DXGI_RGB e534; - public DXGI_RGB e535; - public DXGI_RGB e536; - public DXGI_RGB e537; - public DXGI_RGB e538; - public DXGI_RGB e539; - public DXGI_RGB e540; - public DXGI_RGB e541; - public DXGI_RGB e542; - public DXGI_RGB e543; - public DXGI_RGB e544; - public DXGI_RGB e545; - public DXGI_RGB e546; - public DXGI_RGB e547; - public DXGI_RGB e548; - public DXGI_RGB e549; - public DXGI_RGB e550; - public DXGI_RGB e551; - public DXGI_RGB e552; - public DXGI_RGB e553; - public DXGI_RGB e554; - public DXGI_RGB e555; - public DXGI_RGB e556; - public DXGI_RGB e557; - public DXGI_RGB e558; - public DXGI_RGB e559; - public DXGI_RGB e560; - public DXGI_RGB e561; - public DXGI_RGB e562; - public DXGI_RGB e563; - public DXGI_RGB e564; - public DXGI_RGB e565; - public DXGI_RGB e566; - public DXGI_RGB e567; - public DXGI_RGB e568; - public DXGI_RGB e569; - public DXGI_RGB e570; - public DXGI_RGB e571; - public DXGI_RGB e572; - public DXGI_RGB e573; - public DXGI_RGB e574; - public DXGI_RGB e575; - public DXGI_RGB e576; - public DXGI_RGB e577; - public DXGI_RGB e578; - public DXGI_RGB e579; - public DXGI_RGB e580; - public DXGI_RGB e581; - public DXGI_RGB e582; - public DXGI_RGB e583; - public DXGI_RGB e584; - public DXGI_RGB e585; - public DXGI_RGB e586; - public DXGI_RGB e587; - public DXGI_RGB e588; - public DXGI_RGB e589; - public DXGI_RGB e590; - public DXGI_RGB e591; - public DXGI_RGB e592; - public DXGI_RGB e593; - public DXGI_RGB e594; - public DXGI_RGB e595; - public DXGI_RGB e596; - public DXGI_RGB e597; - public DXGI_RGB e598; - public DXGI_RGB e599; - public DXGI_RGB e600; - public DXGI_RGB e601; - public DXGI_RGB e602; - public DXGI_RGB e603; - public DXGI_RGB e604; - public DXGI_RGB e605; - public DXGI_RGB e606; - public DXGI_RGB e607; - public DXGI_RGB e608; - public DXGI_RGB e609; - public DXGI_RGB e610; - public DXGI_RGB e611; - public DXGI_RGB e612; - public DXGI_RGB e613; - public DXGI_RGB e614; - public DXGI_RGB e615; - public DXGI_RGB e616; - public DXGI_RGB e617; - public DXGI_RGB e618; - public DXGI_RGB e619; - public DXGI_RGB e620; - public DXGI_RGB e621; - public DXGI_RGB e622; - public DXGI_RGB e623; - public DXGI_RGB e624; - public DXGI_RGB e625; - public DXGI_RGB e626; - public DXGI_RGB e627; - public DXGI_RGB e628; - public DXGI_RGB e629; - public DXGI_RGB e630; - public DXGI_RGB e631; - public DXGI_RGB e632; - public DXGI_RGB e633; - public DXGI_RGB e634; - public DXGI_RGB e635; - public DXGI_RGB e636; - public DXGI_RGB e637; - public DXGI_RGB e638; - public DXGI_RGB e639; - public DXGI_RGB e640; - public DXGI_RGB e641; - public DXGI_RGB e642; - public DXGI_RGB e643; - public DXGI_RGB e644; - public DXGI_RGB e645; - public DXGI_RGB e646; - public DXGI_RGB e647; - public DXGI_RGB e648; - public DXGI_RGB e649; - public DXGI_RGB e650; - public DXGI_RGB e651; - public DXGI_RGB e652; - public DXGI_RGB e653; - public DXGI_RGB e654; - public DXGI_RGB e655; - public DXGI_RGB e656; - public DXGI_RGB e657; - public DXGI_RGB e658; - public DXGI_RGB e659; - public DXGI_RGB e660; - public DXGI_RGB e661; - public DXGI_RGB e662; - public DXGI_RGB e663; - public DXGI_RGB e664; - public DXGI_RGB e665; - public DXGI_RGB e666; - public DXGI_RGB e667; - public DXGI_RGB e668; - public DXGI_RGB e669; - public DXGI_RGB e670; - public DXGI_RGB e671; - public DXGI_RGB e672; - public DXGI_RGB e673; - public DXGI_RGB e674; - public DXGI_RGB e675; - public DXGI_RGB e676; - public DXGI_RGB e677; - public DXGI_RGB e678; - public DXGI_RGB e679; - public DXGI_RGB e680; - public DXGI_RGB e681; - public DXGI_RGB e682; - public DXGI_RGB e683; - public DXGI_RGB e684; - public DXGI_RGB e685; - public DXGI_RGB e686; - public DXGI_RGB e687; - public DXGI_RGB e688; - public DXGI_RGB e689; - public DXGI_RGB e690; - public DXGI_RGB e691; - public DXGI_RGB e692; - public DXGI_RGB e693; - public DXGI_RGB e694; - public DXGI_RGB e695; - public DXGI_RGB e696; - public DXGI_RGB e697; - public DXGI_RGB e698; - public DXGI_RGB e699; - public DXGI_RGB e700; - public DXGI_RGB e701; - public DXGI_RGB e702; - public DXGI_RGB e703; - public DXGI_RGB e704; - public DXGI_RGB e705; - public DXGI_RGB e706; - public DXGI_RGB e707; - public DXGI_RGB e708; - public DXGI_RGB e709; - public DXGI_RGB e710; - public DXGI_RGB e711; - public DXGI_RGB e712; - public DXGI_RGB e713; - public DXGI_RGB e714; - public DXGI_RGB e715; - public DXGI_RGB e716; - public DXGI_RGB e717; - public DXGI_RGB e718; - public DXGI_RGB e719; - public DXGI_RGB e720; - public DXGI_RGB e721; - public DXGI_RGB e722; - public DXGI_RGB e723; - public DXGI_RGB e724; - public DXGI_RGB e725; - public DXGI_RGB e726; - public DXGI_RGB e727; - public DXGI_RGB e728; - public DXGI_RGB e729; - public DXGI_RGB e730; - public DXGI_RGB e731; - public DXGI_RGB e732; - public DXGI_RGB e733; - public DXGI_RGB e734; - public DXGI_RGB e735; - public DXGI_RGB e736; - public DXGI_RGB e737; - public DXGI_RGB e738; - public DXGI_RGB e739; - public DXGI_RGB e740; - public DXGI_RGB e741; - public DXGI_RGB e742; - public DXGI_RGB e743; - public DXGI_RGB e744; - public DXGI_RGB e745; - public DXGI_RGB e746; - public DXGI_RGB e747; - public DXGI_RGB e748; - public DXGI_RGB e749; - public DXGI_RGB e750; - public DXGI_RGB e751; - public DXGI_RGB e752; - public DXGI_RGB e753; - public DXGI_RGB e754; - public DXGI_RGB e755; - public DXGI_RGB e756; - public DXGI_RGB e757; - public DXGI_RGB e758; - public DXGI_RGB e759; - public DXGI_RGB e760; - public DXGI_RGB e761; - public DXGI_RGB e762; - public DXGI_RGB e763; - public DXGI_RGB e764; - public DXGI_RGB e765; - public DXGI_RGB e766; - public DXGI_RGB e767; - public DXGI_RGB e768; - public DXGI_RGB e769; - public DXGI_RGB e770; - public DXGI_RGB e771; - public DXGI_RGB e772; - public DXGI_RGB e773; - public DXGI_RGB e774; - public DXGI_RGB e775; - public DXGI_RGB e776; - public DXGI_RGB e777; - public DXGI_RGB e778; - public DXGI_RGB e779; - public DXGI_RGB e780; - public DXGI_RGB e781; - public DXGI_RGB e782; - public DXGI_RGB e783; - public DXGI_RGB e784; - public DXGI_RGB e785; - public DXGI_RGB e786; - public DXGI_RGB e787; - public DXGI_RGB e788; - public DXGI_RGB e789; - public DXGI_RGB e790; - public DXGI_RGB e791; - public DXGI_RGB e792; - public DXGI_RGB e793; - public DXGI_RGB e794; - public DXGI_RGB e795; - public DXGI_RGB e796; - public DXGI_RGB e797; - public DXGI_RGB e798; - public DXGI_RGB e799; - public DXGI_RGB e800; - public DXGI_RGB e801; - public DXGI_RGB e802; - public DXGI_RGB e803; - public DXGI_RGB e804; - public DXGI_RGB e805; - public DXGI_RGB e806; - public DXGI_RGB e807; - public DXGI_RGB e808; - public DXGI_RGB e809; - public DXGI_RGB e810; - public DXGI_RGB e811; - public DXGI_RGB e812; - public DXGI_RGB e813; - public DXGI_RGB e814; - public DXGI_RGB e815; - public DXGI_RGB e816; - public DXGI_RGB e817; - public DXGI_RGB e818; - public DXGI_RGB e819; - public DXGI_RGB e820; - public DXGI_RGB e821; - public DXGI_RGB e822; - public DXGI_RGB e823; - public DXGI_RGB e824; - public DXGI_RGB e825; - public DXGI_RGB e826; - public DXGI_RGB e827; - public DXGI_RGB e828; - public DXGI_RGB e829; - public DXGI_RGB e830; - public DXGI_RGB e831; - public DXGI_RGB e832; - public DXGI_RGB e833; - public DXGI_RGB e834; - public DXGI_RGB e835; - public DXGI_RGB e836; - public DXGI_RGB e837; - public DXGI_RGB e838; - public DXGI_RGB e839; - public DXGI_RGB e840; - public DXGI_RGB e841; - public DXGI_RGB e842; - public DXGI_RGB e843; - public DXGI_RGB e844; - public DXGI_RGB e845; - public DXGI_RGB e846; - public DXGI_RGB e847; - public DXGI_RGB e848; - public DXGI_RGB e849; - public DXGI_RGB e850; - public DXGI_RGB e851; - public DXGI_RGB e852; - public DXGI_RGB e853; - public DXGI_RGB e854; - public DXGI_RGB e855; - public DXGI_RGB e856; - public DXGI_RGB e857; - public DXGI_RGB e858; - public DXGI_RGB e859; - public DXGI_RGB e860; - public DXGI_RGB e861; - public DXGI_RGB e862; - public DXGI_RGB e863; - public DXGI_RGB e864; - public DXGI_RGB e865; - public DXGI_RGB e866; - public DXGI_RGB e867; - public DXGI_RGB e868; - public DXGI_RGB e869; - public DXGI_RGB e870; - public DXGI_RGB e871; - public DXGI_RGB e872; - public DXGI_RGB e873; - public DXGI_RGB e874; - public DXGI_RGB e875; - public DXGI_RGB e876; - public DXGI_RGB e877; - public DXGI_RGB e878; - public DXGI_RGB e879; - public DXGI_RGB e880; - public DXGI_RGB e881; - public DXGI_RGB e882; - public DXGI_RGB e883; - public DXGI_RGB e884; - public DXGI_RGB e885; - public DXGI_RGB e886; - public DXGI_RGB e887; - public DXGI_RGB e888; - public DXGI_RGB e889; - public DXGI_RGB e890; - public DXGI_RGB e891; - public DXGI_RGB e892; - public DXGI_RGB e893; - public DXGI_RGB e894; - public DXGI_RGB e895; - public DXGI_RGB e896; - public DXGI_RGB e897; - public DXGI_RGB e898; - public DXGI_RGB e899; - public DXGI_RGB e900; - public DXGI_RGB e901; - public DXGI_RGB e902; - public DXGI_RGB e903; - public DXGI_RGB e904; - public DXGI_RGB e905; - public DXGI_RGB e906; - public DXGI_RGB e907; - public DXGI_RGB e908; - public DXGI_RGB e909; - public DXGI_RGB e910; - public DXGI_RGB e911; - public DXGI_RGB e912; - public DXGI_RGB e913; - public DXGI_RGB e914; - public DXGI_RGB e915; - public DXGI_RGB e916; - public DXGI_RGB e917; - public DXGI_RGB e918; - public DXGI_RGB e919; - public DXGI_RGB e920; - public DXGI_RGB e921; - public DXGI_RGB e922; - public DXGI_RGB e923; - public DXGI_RGB e924; - public DXGI_RGB e925; - public DXGI_RGB e926; - public DXGI_RGB e927; - public DXGI_RGB e928; - public DXGI_RGB e929; - public DXGI_RGB e930; - public DXGI_RGB e931; - public DXGI_RGB e932; - public DXGI_RGB e933; - public DXGI_RGB e934; - public DXGI_RGB e935; - public DXGI_RGB e936; - public DXGI_RGB e937; - public DXGI_RGB e938; - public DXGI_RGB e939; - public DXGI_RGB e940; - public DXGI_RGB e941; - public DXGI_RGB e942; - public DXGI_RGB e943; - public DXGI_RGB e944; - public DXGI_RGB e945; - public DXGI_RGB e946; - public DXGI_RGB e947; - public DXGI_RGB e948; - public DXGI_RGB e949; - public DXGI_RGB e950; - public DXGI_RGB e951; - public DXGI_RGB e952; - public DXGI_RGB e953; - public DXGI_RGB e954; - public DXGI_RGB e955; - public DXGI_RGB e956; - public DXGI_RGB e957; - public DXGI_RGB e958; - public DXGI_RGB e959; - public DXGI_RGB e960; - public DXGI_RGB e961; - public DXGI_RGB e962; - public DXGI_RGB e963; - public DXGI_RGB e964; - public DXGI_RGB e965; - public DXGI_RGB e966; - public DXGI_RGB e967; - public DXGI_RGB e968; - public DXGI_RGB e969; - public DXGI_RGB e970; - public DXGI_RGB e971; - public DXGI_RGB e972; - public DXGI_RGB e973; - public DXGI_RGB e974; - public DXGI_RGB e975; - public DXGI_RGB e976; - public DXGI_RGB e977; - public DXGI_RGB e978; - public DXGI_RGB e979; - public DXGI_RGB e980; - public DXGI_RGB e981; - public DXGI_RGB e982; - public DXGI_RGB e983; - public DXGI_RGB e984; - public DXGI_RGB e985; - public DXGI_RGB e986; - public DXGI_RGB e987; - public DXGI_RGB e988; - public DXGI_RGB e989; - public DXGI_RGB e990; - public DXGI_RGB e991; - public DXGI_RGB e992; - public DXGI_RGB e993; - public DXGI_RGB e994; - public DXGI_RGB e995; - public DXGI_RGB e996; - public DXGI_RGB e997; - public DXGI_RGB e998; - public DXGI_RGB e999; - public DXGI_RGB e1000; - public DXGI_RGB e1001; - public DXGI_RGB e1002; - public DXGI_RGB e1003; - public DXGI_RGB e1004; - public DXGI_RGB e1005; - public DXGI_RGB e1006; - public DXGI_RGB e1007; - public DXGI_RGB e1008; - public DXGI_RGB e1009; - public DXGI_RGB e1010; - public DXGI_RGB e1011; - public DXGI_RGB e1012; - public DXGI_RGB e1013; - public DXGI_RGB e1014; - public DXGI_RGB e1015; - public DXGI_RGB e1016; - public DXGI_RGB e1017; - public DXGI_RGB e1018; - public DXGI_RGB e1019; - public DXGI_RGB e1020; - public DXGI_RGB e1021; - public DXGI_RGB e1022; - public DXGI_RGB e1023; - public DXGI_RGB e1024; -#if NET6_0_OR_GREATER - public ref DXGI_RGB this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return ref AsSpan()[index]; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AsSpan() => MemoryMarshal.CreateSpan(ref e0, 1025); -#else - // there is no way to do this outside of terrible unsafe code. Don't do this in .Net Standard 2.0 - - public DXGI_RGB this[int index] - { - get - { - if ((uint)index > 1025) - throw new ArgumentOutOfRangeException("index"); - - fixed (DXGI_RGB* basePtr = &e0) - { - DXGI_RGB* newPtr = basePtr + index; - return *newPtr; - } - } - set - { - if ((uint)index > 1025) - throw new ArgumentOutOfRangeException("index"); - - fixed (DXGI_RGB* basePtr = &e0) - { - DXGI_RGB* newPtr = basePtr + index; - *newPtr = value; - } - } - } -#endif - } - } - + internal unsafe struct DEVMODEW { public fixed ushort dmDeviceName[32]; @@ -1196,7 +119,7 @@ namespace Avalonia.Win32.DxgiSwapchain public uint Flags; } - internal unsafe struct DXGI_FRAME_STATISTICS + internal struct DXGI_FRAME_STATISTICS { public uint PresentCount; @@ -1225,11 +148,10 @@ namespace Avalonia.Win32.DxgiSwapchain internal unsafe struct DXGI_MAPPED_RECT { public int Pitch; - public byte* pBits; } - internal unsafe partial struct DXGI_MODE_DESC + internal struct DXGI_MODE_DESC { public ushort Width; public ushort Height; @@ -1239,7 +161,7 @@ namespace Avalonia.Win32.DxgiSwapchain public DXGI_MODE_SCALING Scaling; } - internal unsafe partial struct DXGI_OUTPUT_DESC + internal unsafe struct DXGI_OUTPUT_DESC { internal fixed ushort DeviceName[32]; @@ -1263,13 +185,13 @@ namespace Avalonia.Win32.DxgiSwapchain public POINT* pScrollOffset; } - internal unsafe partial struct DXGI_RATIONAL + internal struct DXGI_RATIONAL { public ushort Numerator; public ushort Denominator; } - internal partial struct DXGI_RGB + internal struct DXGI_RGB { public float Red; @@ -1278,7 +200,7 @@ namespace Avalonia.Win32.DxgiSwapchain public float Blue; } - internal partial struct DXGI_RGBA + internal struct DXGI_RGBA { public float r; @@ -1295,7 +217,7 @@ namespace Avalonia.Win32.DxgiSwapchain public uint Quality; } - internal unsafe struct DXGI_SURFACE_DESC + internal struct DXGI_SURFACE_DESC { public uint Width; @@ -1306,7 +228,7 @@ namespace Avalonia.Win32.DxgiSwapchain public DXGI_SAMPLE_DESC SampleDesc; } - internal unsafe partial struct DXGI_SWAP_CHAIN_DESC + internal struct DXGI_SWAP_CHAIN_DESC { public DXGI_MODE_DESC BufferDesc; public DXGI_SAMPLE_DESC SampleDesc; @@ -1333,7 +255,7 @@ namespace Avalonia.Win32.DxgiSwapchain public uint Flags; } - internal unsafe struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC + internal struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC { public DXGI_RATIONAL RefreshRate; @@ -1344,7 +266,7 @@ namespace Avalonia.Win32.DxgiSwapchain public int Windowed; } - internal partial struct D3D11_TEXTURE2D_DESC + internal struct D3D11_TEXTURE2D_DESC { public uint Width; diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs index 1a739ce3d1..14a9ff1277 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -namespace Avalonia.Win32.DxgiSwapchain +namespace Avalonia.Win32.DirectX { internal unsafe class DirectXUnmanagedMethods { @@ -23,7 +23,16 @@ namespace Avalonia.Win32.DxgiSwapchain [DllImport("user32", ExactSpelling = true)] internal static extern bool EnumDisplaySettingsW(ushort* lpszDeviceName, uint iModeNum, DEVMODEW* lpDevMode); - [DllImport("user32", ExactSpelling = true, SetLastError = true)] - internal static extern bool GetClientRect(IntPtr hWnd, Interop.UnmanagedMethods.RECT* lpRect); + [DllImport("d3d11", ExactSpelling = true, PreserveSig = false)] + public static extern void D3D11CreateDevice( + IntPtr adapter, D3D_DRIVER_TYPE DriverType, + IntPtr Software, + uint Flags, + D3D_FEATURE_LEVEL[] pFeatureLevels, + uint FeatureLevels, + uint SDKVersion, + out IntPtr ppDevice, + out D3D_FEATURE_LEVEL pFeatureLevel, + IntPtr* ppImmediateContext); } } diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs index 8c13ecdcc1..e82f7633be 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs @@ -10,11 +10,12 @@ using Avalonia.Logging; using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; using Avalonia.Rendering; +using Avalonia.Win32.OpenGl.Angle; using static Avalonia.Win32.Interop.UnmanagedMethods; -using static Avalonia.Win32.DxgiSwapchain.DirectXUnmanagedMethods; +using static Avalonia.Win32.DirectX.DirectXUnmanagedMethods; using MicroCom.Runtime; -namespace Avalonia.Win32.DxgiSwapchain +namespace Avalonia.Win32.DirectX { #pragma warning disable CA1416 // This should only be reachable on Windows #nullable enable @@ -25,34 +26,27 @@ namespace Avalonia.Win32.DxgiSwapchain public bool RunsInBackground => true; public event Action? Tick; - - private AngleWin32EglDisplay _angle; - private EglPlatformOpenGlInterface _gl; private object _syncLock; private IDXGIOutput? _output = null; private Stopwatch? _stopwatch = null; + private const string LogArea = "DXGI"; - public DxgiConnection(EglPlatformOpenGlInterface gl, object syncLock) + public DxgiConnection(object syncLock) { - _syncLock = syncLock; - _angle = (AngleWin32EglDisplay)gl.Display; - _gl = gl; } - - public EglPlatformOpenGlInterface Egl => _gl; - - public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle) + + public static void TryCreateAndRegister() { try { - TryCreateAndRegisterCore(angle); + TryCreateAndRegisterCore(); } catch (Exception ex) { - Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain)) + Logger.TryGet(LogEventLevel.Error, LogArea) ?.Log(null, "Unable to establish Dxgi: {0}", ex); } } @@ -66,7 +60,7 @@ namespace Avalonia.Win32.DxgiSwapchain } catch (Exception ex) { - Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain)) + Logger.TryGet(LogEventLevel.Error, LogArea) ?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}"); } @@ -84,7 +78,7 @@ namespace Avalonia.Win32.DxgiSwapchain } catch (Exception ex) { - Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain)) + Logger.TryGet(LogEventLevel.Error, LogArea) ?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}"); _output.Dispose(); _output = null; @@ -103,7 +97,7 @@ namespace Avalonia.Win32.DxgiSwapchain } catch (Exception ex) { - Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain)) + Logger.TryGet(LogEventLevel.Error, LogArea) ?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}"); } } @@ -168,7 +162,7 @@ namespace Avalonia.Win32.DxgiSwapchain } // Used the windows composition as a blueprint for this startup/creation - static private bool TryCreateAndRegisterCore(EglPlatformOpenGlInterface gl) + static private bool TryCreateAndRegisterCore() { var tcs = new TaskCompletionSource(); var pumpLock = new object(); @@ -178,7 +172,7 @@ namespace Avalonia.Win32.DxgiSwapchain { DxgiConnection connection; - connection = new DxgiConnection(gl, pumpLock); + connection = new DxgiConnection(pumpLock); AvaloniaLocator.CurrentMutable.BindToSelf(connection); AvaloniaLocator.CurrentMutable.Bind().ToConstant(connection); diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs index 2ab9b30d43..cb7826e185 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs @@ -8,11 +8,12 @@ using System.Threading.Tasks; using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; +using Avalonia.Win32.OpenGl.Angle; using MicroCom.Runtime; using static Avalonia.OpenGL.Egl.EglGlPlatformSurfaceBase; using static Avalonia.Win32.Interop.UnmanagedMethods; -namespace Avalonia.Win32.DxgiSwapchain +namespace Avalonia.Win32.DirectX { #pragma warning disable CA1416 // Validate platform compatibility, if you enter this not on windows you have messed up badly #nullable enable @@ -22,8 +23,7 @@ namespace Avalonia.Win32.DxgiSwapchain public const uint DXGI_USAGE_RENDER_TARGET_OUTPUT = 0x00000020U; - private IEglWindowGlPlatformSurfaceInfo _window; - private EglPlatformOpenGlInterface _egl; + private EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _window; private DxgiConnection _connection; private IDXGIDevice? _dxgiDevice = null; private IDXGIFactory2? _dxgiFactory = null; @@ -36,15 +36,14 @@ namespace Avalonia.Win32.DxgiSwapchain private Guid ID3D11Texture2DGuid = Guid.Parse("6F15AAF2-D208-4E89-9AB4-489535D34F9C"); - public DxgiRenderTarget(IEglWindowGlPlatformSurfaceInfo window, EglPlatformOpenGlInterface egl, DxgiConnection connection) : base(egl) + public DxgiRenderTarget(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo window, EglContext context, DxgiConnection connection) : base(context) { _window = window; - _egl = egl; _connection = connection; // the D3D device is expected to at least be an ID3D11Device // but how do I wrap an IntPtr as a managed IUnknown now? Like this. - IUnknown pdevice = MicroComRuntime.CreateProxyFor(((AngleWin32EglDisplay)_egl.Display).GetDirect3DDevice(), false); + IUnknown pdevice = MicroComRuntime.CreateProxyFor(((AngleWin32EglDisplay)context.Display).GetDirect3DDevice(), false); _dxgiDevice = pdevice.QueryInterface(); @@ -86,14 +85,14 @@ namespace Avalonia.Win32.DxgiSwapchain _clientRect = pClientRect; } - public override IGlPlatformSurfaceRenderingSession BeginDraw() + public override IGlPlatformSurfaceRenderingSession BeginDrawCore() { if (_swapChain is null) { throw new InvalidOperationException("No chain to draw on"); } - var contextLock = _egl.PrimaryContext.EnsureCurrent(); + var contextLock = Context.EnsureCurrent(); EglSurface? surface = null; IDisposable? transaction = null; var success = false; @@ -132,10 +131,10 @@ namespace Avalonia.Win32.DxgiSwapchain _renderTexture = texture; // I also have to get the pointer to this texture directly - surface = ((AngleWin32EglDisplay)_egl.Display).WrapDirect3D11Texture(_egl, MicroComRuntime.GetNativeIntPtr(_renderTexture), + surface = ((AngleWin32EglDisplay)Context.Display).WrapDirect3D11Texture(MicroComRuntime.GetNativeIntPtr(_renderTexture), 0, 0, size.Width, size.Height); - var res = base.BeginDraw(surface, _window, () => + var res = base.BeginDraw(surface, _window.Size, _window.Scaling, () => { _swapChain.Present((ushort)0U, (ushort)0U); surface?.Dispose(); diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs index a40d8abf80..88226c5c89 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs @@ -3,29 +3,29 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.OpenGL; using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; -namespace Avalonia.Win32.DxgiSwapchain +namespace Avalonia.Win32.DirectX { public class DxgiSwapchainWindow : EglGlPlatformSurfaceBase { private DxgiConnection _connection; - private EglPlatformOpenGlInterface _egl; - private IEglWindowGlPlatformSurfaceInfo _window; + private EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _window; - public DxgiSwapchainWindow(DxgiConnection connection, IEglWindowGlPlatformSurfaceInfo window) + public DxgiSwapchainWindow(DxgiConnection connection, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo window) { _connection = connection; _window = window; - _egl = connection.Egl; } - public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { - using (_egl.PrimaryContext.EnsureCurrent()) + var eglContext = (EglContext)context; + using (eglContext.EnsureCurrent()) { - return new DxgiRenderTarget(_window, _egl, _connection); + return new DxgiRenderTarget(_window, eglContext, _connection); } } } diff --git a/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs b/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs new file mode 100644 index 0000000000..d2a2b2f513 --- /dev/null +++ b/src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia.OpenGL; +using Avalonia.Platform; + +namespace Avalonia.Win32.DirectX; + +public interface IDirect3D11TexturePlatformSurface +{ + public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext graphicsContext, IntPtr d3dDevice); +} + + + +public interface IDirect3D11TextureRenderTarget : IDisposable +{ + bool IsCorrupted { get; } + IDirect3D11TextureRenderTargetRenderSession BeginDraw(); +} + +public interface IDirect3D11TextureRenderTargetRenderSession : IDisposable +{ + public IntPtr D3D11Texture2D { get; } + public PixelSize Size { get; } + public PixelPoint Offset { get; } + public double Scaling { get; } +} diff --git a/src/Windows/Avalonia.Win32/DirectX/directx.idl b/src/Windows/Avalonia.Win32/DirectX/directx.idl index 09d1adf574..a4552eedea 100644 --- a/src/Windows/Avalonia.Win32/DirectX/directx.idl +++ b/src/Windows/Avalonia.Win32/DirectX/directx.idl @@ -1,4 +1,4 @@ -@clr-namespace Avalonia.Win32.DxgiSwapchain +@clr-namespace Avalonia.Win32.DirectX @clr-access internal @clr-map FLOAT float @clr-map HSTRING IntPtr @@ -12,6 +12,7 @@ @clr-map HWND IntPtr @clr-map BOOL int @clr-map DWORD int +@clr-map SIZE_T IntPtr @clr-map boolean int @clr-map BYTE byte @clr-map INT16 short @@ -242,9 +243,9 @@ interface IDXGIOutput : IDXGIObject HRESULT WaitForVBlank(); HRESULT TakeOwnership([in, annotation("_In_")] IUnknown* pDevice, BOOL Exclusive); void ReleaseOwnership(); - HRESULT GetGammaControlCapabilities([out, annotation("_Out_")] DXGI_GAMMA_CONTROL_CAPABILITIES* pGammaCaps); - HRESULT SetGammaControl([in, annotation("_In_")] DXGI_GAMMA_CONTROL* pArray); - HRESULT GetGammaControl([out, annotation("_Out_")] DXGI_GAMMA_CONTROL* pArray); + HRESULT GetGammaControlCapabilities(IntPtr pGammaCaps); + HRESULT SetGammaControl([in, annotation("_In_")] void* pArray); + HRESULT GetGammaControl(IntPtr pArray); HRESULT SetDisplaySurface([in, annotation("_In_")] IDXGISurface* pScanoutSurface); HRESULT GetDisplaySurfaceData([in, annotation("_In_")] IDXGISurface* pDestination); HRESULT GetFrameStatistics([out, annotation("_Out_")] DXGI_FRAME_STATISTICS* pStats); @@ -303,3 +304,180 @@ interface IDXGISwapChain1 : IDXGISwapChain HRESULT GetRotation([out, annotation("_Out_")] DXGI_MODE_ROTATION* pRotation); } +enum D3D11_FEATURE +{ + D3D11_FEATURE_THREADING, + D3D11_FEATURE_DOUBLES, + D3D11_FEATURE_FORMAT_SUPPORT, + D3D11_FEATURE_FORMAT_SUPPORT2, + D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, + D3D11_FEATURE_D3D11_OPTIONS, + D3D11_FEATURE_ARCHITECTURE_INFO, + D3D11_FEATURE_D3D9_OPTIONS, + D3D11_FEATURE_SHADER_MIN_PRECISION_SUPPORT, + D3D11_FEATURE_D3D9_SHADOW_SUPPORT, + D3D11_FEATURE_D3D11_OPTIONS1, + D3D11_FEATURE_D3D9_SIMPLE_INSTANCING_SUPPORT, + D3D11_FEATURE_MARKER_SUPPORT, + D3D11_FEATURE_D3D9_OPTIONS1, + D3D11_FEATURE_D3D11_OPTIONS2, + D3D11_FEATURE_D3D11_OPTIONS3, + D3D11_FEATURE_GPU_VIRTUAL_ADDRESS_SUPPORT, + D3D11_FEATURE_D3D11_OPTIONSS, + D3D11_FEATURE_SHADER_CACHE +} + +[uuid(db6f6ddb-ac77-4e88-8253-819df9bbf140)] +interface ID3D11Device : IUnknown +{ + HRESULT CreateBuffer( + IntPtr pDesc, + IntPtr pInitialData, + [out, retval] IUnknown** ppBuffer ); + HRESULT CreateTexture1D( + IntPtr pDesc, + IntPtr pInitialData, + [out, retval] IUnknown** ppTexture1D ); + HRESULT CreateTexture2D( + IntPtr pDesc, + IntPtr pInitialData, + [out, retval] IUnknown** ppTexture2D ); + HRESULT CreateTexture3D( + IntPtr pDesc, + IntPtr pInitialData, + [out, retval] IUnknown** ppTexture3D ); + HRESULT CreateShaderResourceView( + IntPtr pResource, + IntPtr pDesc, + [out, retval] IUnknown** ppSRView ); + HRESULT CreateUnorderedAccessView( + IntPtr pResource, + IntPtr pDesc, + [out, retval] IUnknown** ppUAView ); + HRESULT CreateRenderTargetView( + IntPtr pResource, + IntPtr pDesc, + [out, retval] IUnknown** ppRTView); + HRESULT CreateDepthStencilView( + IntPtr pResource, + IntPtr pDesc, + [out, retval] IUnknown** ppDepthStencilView ); + HRESULT CreateInputLayout( + IntPtr pInputElementDescs, + UINT NumElements, + void* pShaderBytecodeWithInputSignature, + IntPtr BytecodeLength, + [out, retval] IUnknown** ppInputLayout ); + HRESULT CreateVertexShader( + IntPtr pShaderBytecode, + [annotation("_In_")] SIZE_T BytecodeLength, + IntPtr pClassLinkage, + [out, retval] IUnknown** ppVertexShader ); + HRESULT CreateGeometryShader( + IntPtr pShaderBytecode, + [annotation("_In_")] SIZE_T BytecodeLength, + IntPtr pClassLinkage, + [out, retval] IUnknown** ppGeometryShader ); + HRESULT CreateGeometryShaderWithStreamOutput( + IntPtr pShaderBytecode, + [annotation("_In_")] SIZE_T BytecodeLength, + IntPtr pSODeclaration, + UINT NumEntries, + UINT* pBufferStrides, + UINT NumStrides, + UINT RasterizedStream, + IntPtr pClassLinkage, + [out, retval] IUnknown** ppGeometryShader ); + HRESULT CreatePixelShader( + IntPtr pShaderBytecode, + [annotation("_In_")] SIZE_T BytecodeLength, + IntPtr pClassLinkage, + [out, retval] IUnknown** ppPixelShader ); + HRESULT CreateHullShader( + IntPtr pShaderBytecode, + [annotation("_In_")] SIZE_T BytecodeLength, + IntPtr pClassLinkage, + [out, retval] IUnknown** ppHullShader ); + HRESULT CreateDomainShader( + IntPtr pShaderBytecode, + [annotation("_In_")] SIZE_T BytecodeLength, + IntPtr pClassLinkage, + [out, retval] IUnknown** ppDomainShader ); + HRESULT CreateComputeShader( + IntPtr pShaderBytecode, + [annotation("_In_")] SIZE_T BytecodeLength, + IntPtr pClassLinkage, + [out, retval] IUnknown** ppComputeShader ); + HRESULT CreateClassLinkage([out, retval]IUnknown** ppLinkage); + HRESULT CreateBlendState( + IntPtr pBlendStateDesc, + [out, retval] IUnknown** ppBlendState ); + HRESULT CreateDepthStencilState( + IntPtr pDepthStencilDesc, + [out, retval] IUnknown** ppDepthStencilState ); + HRESULT CreateRasterizerState( + IntPtr pRasterizerDesc, + [out, retval] IUnknown** ppRasterizerState ); + HRESULT CreateSamplerState( + IntPtr pSamplerDesc, + [out, retval] IUnknown** ppSamplerState ); + HRESULT CreateQuery( + IntPtr pQueryDesc, + [out, retval] IUnknown** ppQuery ); + HRESULT CreatePredicate( + IntPtr pPredicateDesc, + [out, retval] IUnknown** ppPredicate ); + HRESULT CreateCounter( + IntPtr pCounterDesc, + [out, retval] IUnknown** ppCounter ); + HRESULT CreateDeferredContext( + UINT ContextFlags, // Reserved parameter; must be 0 + [out, retval] IUnknown** ppDeferredContext ); + HRESULT OpenSharedResource( + IntPtr hResource, + [out] Guid* ReturnedInterface, + [out, retval] IUnknown** ppResource); + + // Check* + HRESULT CheckFormatSupport( + [annotation("_In_")] DXGI_FORMAT Format, + [annotation("_Out_")] UINT* pFormatSupport ); + HRESULT CheckMultisampleQualityLevels( + [annotation("_In_")] DXGI_FORMAT Format, + [annotation("_In_")] UINT SampleCount, + [annotation("_Out_")] UINT* pNumQualityLevels ); + void CheckCounterInfo(IntPtr pCounterInfo ); + HRESULT CheckCounter( + IntPtr pDesc, + IntPtr pType, + IntPtr pActiveCounters, + IntPtr szName, + UINT* pNameLength, + IntPtr szUnits, + UINT* pUnitsLength, + IntPtr szDescription, + UINT* pDescriptionLength ); + HRESULT CheckFeatureSupport( + D3D11_FEATURE Feature, + void* pFeatureSupportData, + UINT FeatureSupportDataSize ); + + HRESULT GetPrivateData( + Guid* guid, + UINT* pDataSize, + void* pData ); + HRESULT SetPrivateData( + Guid* guid, + UINT DataSize, + IntPtr* pData ); + HRESULT SetPrivateDataInterface( + Guid* guid, + IUnknown* pData ); + + D3D_FEATURE_LEVEL GetFeatureLevel(); + UINT GetCreationFlags(); + int GetDeviceRemovedReason(); + void GetImmediateContext(IntPtr* ppImmediateContext ); + HRESULT SetExceptionMode( UINT RaiseFlags ); + UINT GetExceptionMode(); +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs index 966f996a91..08b3ee32fa 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs @@ -6,8 +6,6 @@ namespace Avalonia.Win32.Interop.Automation { internal static class UiaCoreTypesApi { - private const string StartListeningExportName = "SynchronizedInputPattern_StartListening"; - internal enum AutomationIdType { Property, diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9b86154043..672f3a781a 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -820,6 +820,14 @@ namespace Avalonia.Win32.Interop DWMWA_LAST }; + public enum DwmWindowCornerPreference : uint + { + DWMWCP_DEFAULT = 0, + DWMWCP_DONOTROUND, + DWMWCP_ROUND, + DWMWCP_ROUNDSMALL + } + public enum MapVirtualKeyMapTypes : uint { MAPVK_VK_TO_VSC = 0x00, @@ -1409,7 +1417,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool TranslateMessage(ref MSG lpMsg); - [DllImport("user32.dll")] + [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] @@ -1627,7 +1635,7 @@ namespace Avalonia.Win32.Interop public static extern bool wglDeleteContext(IntPtr context); - [DllImport("opengl32.dll")] + [DllImport("opengl32.dll", SetLastError = true)] public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr context); [DllImport("opengl32.dll")] diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs new file mode 100644 index 0000000000..6c951010c8 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleD3DTextureFeature.cs @@ -0,0 +1,103 @@ +using System.Runtime.InteropServices; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; +using Avalonia.Win32.DirectX; + +namespace Avalonia.Win32.OpenGl.Angle; + +internal class AngleD3DTextureFeature : IGlPlatformSurfaceRenderTargetFactory +{ + public bool CanRenderToSurface(IGlContext context, object surface) => + context is EglContext + { + Display: AngleWin32EglDisplay { PlatformApi: AngleOptions.PlatformApi.DirectX11 } + } && surface is IDirect3D11TexturePlatformSurface; + + class RenderTargetWrapper : EglPlatformSurfaceRenderTargetBase + { + private readonly AngleWin32EglDisplay _angle; + private readonly IDirect3D11TextureRenderTarget _target; + + public RenderTargetWrapper(EglContext context, + AngleWin32EglDisplay angle, + IDirect3D11TextureRenderTarget target) : base(context) + { + _angle = angle; + _target = target; + } + + public override IGlPlatformSurfaceRenderingSession BeginDrawCore() + { + var success = false; + var contextLock = Context.EnsureCurrent(); + IDirect3D11TextureRenderTargetRenderSession session = null; + EglSurface surface = null; + try + { + try + { + session = _target.BeginDraw(); + } + catch (RenderTargetCorruptedException e) + { + if (e.InnerException is COMException com + && ((DXGI_ERROR)com.HResult).IsDeviceLostError()) + Context.NotifyContextLost(); + + throw; + } + + surface = _angle.WrapDirect3D11Texture(session.D3D11Texture2D, session.Offset.X, session.Offset.Y, + session.Size.Width, session.Size.Height); + var rv = BeginDraw(surface, session.Size, session.Scaling, () => + { + using(contextLock) + using (session) + using (surface) + { + } + }, true); + success = true; + return rv; + } + finally + { + if (!success) + { + using(contextLock) + using (session) + using (surface) + { + } + } + } + } + + public override void Dispose() + { + _target.Dispose(); + base.Dispose(); + } + + public override bool IsCorrupted => _target.IsCorrupted || base.IsCorrupted; + } + + public IGlPlatformSurfaceRenderTarget CreateRenderTarget(IGlContext context, object surface) + { + var ctx = (EglContext)context; + var angle = (AngleWin32EglDisplay)ctx.Display; + var textureSurface = (IDirect3D11TexturePlatformSurface)surface; + try + { + var target = textureSurface.CreateRenderTarget(context, angle.GetDirect3DDevice()); + return new RenderTargetWrapper(ctx, angle, target); + } + catch (COMException com) + { + if (((DXGI_ERROR)com.HResult).IsDeviceLostError()) + ctx.NotifyContextLost(); + throw; + } + } +} diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs similarity index 56% rename from src/Avalonia.OpenGL/Angle/AngleEglInterface.cs rename to src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs index 3a332f37ad..4e00bc2c72 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleEglInterface.cs @@ -1,18 +1,31 @@ using System; using System.Runtime.InteropServices; using Avalonia.OpenGL.Egl; +using Avalonia.SourceGenerator; namespace Avalonia.OpenGL.Angle { - public class AngleEglInterface : EglInterface + internal partial class Win32AngleEglInterface : EglInterface { [DllImport("av_libGLESv2.dll", CharSet = CharSet.Ansi)] static extern IntPtr EGL_GetProcAddress(string proc); - public AngleEglInterface() : base(LoadAngle()) - { + public Win32AngleEglInterface() : this(LoadAngle()) + { + } + + private Win32AngleEglInterface(Func getProcAddress) : base(getProcAddress) + { + Initialize(getProcAddress); + } + + [GetProcAddress("eglCreateDeviceANGLE", true)] + public partial IntPtr CreateDeviceANGLE(int deviceType, IntPtr nativeDevice, int[] attribs); + + [GetProcAddress("eglReleaseDeviceANGLE", true)] + public partial void ReleaseDeviceANGLE(IntPtr device); static Func LoadAngle() { diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs new file mode 100644 index 0000000000..b2d7b2014b --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs @@ -0,0 +1,140 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Angle; +using Avalonia.OpenGL.Egl; +using Avalonia.Win32.DirectX; +using MicroCom.Runtime; +using static Avalonia.OpenGL.Egl.EglConsts; + +namespace Avalonia.Win32.OpenGl.Angle +{ + internal class AngleWin32EglDisplay : EglDisplay + { + protected override bool DisplayLockIsSharedWithContexts => true; + + public static AngleWin32EglDisplay CreateD3D9Display(EglInterface egl) + { + var display = egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, + new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, EGL_NONE }); + + return new AngleWin32EglDisplay(display, new EglDisplayOptions() + { + Egl = egl, + ContextLossIsDisplayLoss = true, + GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles + }, AngleOptions.PlatformApi.DirectX9); + } + + public static AngleWin32EglDisplay CreateSharedD3D11Display(EglInterface egl) + { + var display = egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, + new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, EGL_NONE }); + + return new AngleWin32EglDisplay(display, new EglDisplayOptions() + { + Egl = egl, + ContextLossIsDisplayLoss = true, + GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles + }, AngleOptions.PlatformApi.DirectX11); + } + + public static AngleWin32EglDisplay CreateD3D11Display(Win32AngleEglInterface egl) + { + unsafe + { + var featureLevels = new[] + { + D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_1 + }; + + DirectXUnmanagedMethods.D3D11CreateDevice(IntPtr.Zero, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, + IntPtr.Zero, 0, featureLevels, (uint)featureLevels.Length, + 7, out var pD3dDevice, out var featureLevel, null); + if (pD3dDevice == IntPtr.Zero) + throw new Win32Exception("Unable to create D3D11 Device"); + + var d3dDevice = MicroComRuntime.CreateProxyFor(pD3dDevice, true); + var angleDevice = IntPtr.Zero; + var display = IntPtr.Zero; + + void Cleanup() + { + if (angleDevice != IntPtr.Zero) + egl.ReleaseDeviceANGLE(angleDevice); + d3dDevice.Dispose(); + } + + bool success = false; + try + { + angleDevice = egl.CreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, pD3dDevice, null); + if (angleDevice == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreateDeviceANGLE", egl); + + display = egl.GetPlatformDisplayExt(EGL_PLATFORM_DEVICE_EXT, angleDevice, null); + if (display == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglGetPlatformDisplayEXT", egl); + + + var rv = new AngleWin32EglDisplay(display, new EglDisplayOptions + { + DisposeCallback = Cleanup, + Egl = egl, + ContextLossIsDisplayLoss = true, + DeviceLostCheckCallback = () => d3dDevice.DeviceRemovedReason != 0, + GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles + }, AngleOptions.PlatformApi.DirectX11); + success = true; + return rv; + } + finally + { + if (!success) + { + if (display != IntPtr.Zero) + egl.Terminate(display); + Cleanup(); + } + } + } + } + + private AngleWin32EglDisplay(IntPtr display, EglDisplayOptions options, AngleOptions.PlatformApi platformApi) : base(display, options) + { + PlatformApi = platformApi; + } + + public AngleOptions.PlatformApi PlatformApi { get; } + + public IntPtr GetDirect3DDevice() + { + if (!EglInterface.QueryDisplayAttribExt(Handle, EglConsts.EGL_DEVICE_EXT, out var eglDevice)) + throw new OpenGlException("Unable to get EGL_DEVICE_EXT"); + if (!EglInterface.QueryDeviceAttribExt(eglDevice, PlatformApi == AngleOptions.PlatformApi.DirectX9 ? EGL_D3D9_DEVICE_ANGLE : EGL_D3D11_DEVICE_ANGLE, out var d3dDeviceHandle)) + throw new OpenGlException("Unable to get EGL_D3D9_DEVICE_ANGLE"); + return d3dDeviceHandle; + } + + public EglSurface WrapDirect3D11Texture( IntPtr handle) + { + if (PlatformApi != AngleOptions.PlatformApi.DirectX11) + throw new InvalidOperationException("Current platform API is " + PlatformApi); + return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE }); + } + + public EglSurface WrapDirect3D11Texture(IntPtr handle, int offsetX, int offsetY, int width, int height) + { + if (PlatformApi != AngleOptions.PlatformApi.DirectX11) + throw new InvalidOperationException("Current platform API is " + PlatformApi); + return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE }); + } + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs new file mode 100644 index 0000000000..9a829aff92 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Logging; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Angle; +using Avalonia.OpenGL.Egl; +using Avalonia.Platform; + +namespace Avalonia.Win32.OpenGl.Angle; + +internal class AngleWin32PlatformGraphics : IPlatformGraphics +{ + private readonly Win32AngleEglInterface _egl; + private AngleWin32EglDisplay _sharedDisplay; + private EglContext _sharedContext; + public bool UsesSharedContext => PlatformApi == AngleOptions.PlatformApi.DirectX9; + + public AngleOptions.PlatformApi PlatformApi { get; } = AngleOptions.PlatformApi.DirectX11; + public IPlatformGraphicsContext CreateContext() + { + if (UsesSharedContext) + throw new InvalidOperationException(); + + var display = AngleWin32EglDisplay.CreateD3D11Display(_egl); + var success = false; + try + { + var rv = display.CreateContext(new EglContextOptions + { + DisposeCallback = display.Dispose, + ExtraFeatures = new Dictionary + { + [typeof(IGlPlatformSurfaceRenderTargetFactory)] = new AngleD3DTextureFeature() + } + }); + success = true; + return rv; + } + finally + { + if (!success) + display.Dispose(); + } + } + + public IPlatformGraphicsContext GetSharedContext() + { + if (!UsesSharedContext) + throw new InvalidOperationException(); + if (_sharedContext == null || _sharedContext.IsLost) + { + _sharedContext?.Dispose(); + _sharedContext = null; + _sharedContext = _sharedDisplay.CreateContext(new EglContextOptions()); + } + + return _sharedContext; + } + + public AngleWin32PlatformGraphics(Win32AngleEglInterface egl, AngleWin32EglDisplay display) + : this(egl, display.PlatformApi) + { + _sharedDisplay = display; + } + + public AngleWin32PlatformGraphics(Win32AngleEglInterface egl, AngleOptions.PlatformApi api) + { + _egl = egl; + PlatformApi = api; + } + + + public static AngleWin32PlatformGraphics TryCreate(AngleOptions options) + { + + + Win32AngleEglInterface egl; + try + { + egl = new(); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL") + ?.Log(null, "Unable to load ANGLE: {0}", e); + return null; + } + + foreach (var api in (options?.AllowedPlatformApis ?? new [] + { + AngleOptions.PlatformApi.DirectX11 + }).Distinct()) + if (api == AngleOptions.PlatformApi.DirectX11) + { + try + { + using var display = AngleWin32EglDisplay.CreateD3D11Display(egl); + using var ctx = display.CreateContext(new EglContextOptions()); + ctx.MakeCurrent().Dispose(); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL") + ?.Log(null, "Unable to initialize ANGLE-based rendering with DirectX11 : {0}", e); + continue; + } + + return new AngleWin32PlatformGraphics(egl, AngleOptions.PlatformApi.DirectX11); + } + else + { + AngleWin32EglDisplay sharedDisplay = null; + try + { + sharedDisplay = AngleWin32EglDisplay.CreateD3D9Display(egl); + using (var ctx = sharedDisplay.CreateContext(new EglContextOptions())) + ctx.MakeCurrent().Dispose(); + + return new AngleWin32PlatformGraphics(egl, sharedDisplay); + } + catch (Exception e) + { + sharedDisplay?.Dispose(); + Logger.TryGet(LogEventLevel.Error, "OpenGL") + ?.Log(null, "Unable to initialize ANGLE-based rendering with DirectX9 : {0}", e); + } + } + return null; + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs index d6633ddb61..d535efc5b7 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reactive.Disposables; +using System.Threading; using Avalonia.OpenGL; +using Avalonia.Platform; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.OpenGl.WglConsts; @@ -43,8 +47,9 @@ namespace Avalonia.Win32.OpenGl public void Dispose() { wglDeleteContext(_context); - ReleaseDC(_hWnd, _dc); - DestroyWindow(_hWnd); + WglGdiResourceManager.ReleaseDC(_hWnd, _dc); + WglGdiResourceManager.DestroyWindow(_hWnd); + IsLost = true; } public GlVersion Version { get; } @@ -55,17 +60,26 @@ namespace Avalonia.Win32.OpenGl private bool IsCurrent => wglGetCurrentContext() == _context && wglGetCurrentDC() == _dc; public IDisposable MakeCurrent() { + if (IsLost) + throw new PlatformGraphicsContextLostException(); if(IsCurrent) return Disposable.Empty; return new WglRestoreContext(_dc, _context, _lock); } + public bool IsLost { get; private set; } public IDisposable EnsureCurrent() => MakeCurrent(); + internal IDisposable Lock() + { + Monitor.Enter(_lock); + return Disposable.Create(_lock, Monitor.Exit); + } + public IntPtr CreateConfiguredDeviceContext(IntPtr hWnd) { - var dc = GetDC(hWnd); + var dc = WglGdiResourceManager.GetDC(hWnd); var fmt = _formatDescriptor; SetPixelFormat(dc, _pixelFormat, ref fmt); return dc; @@ -81,5 +95,14 @@ namespace Avalonia.Win32.OpenGl || _sharedWith == context || _sharedWith != null && _sharedWith == c._sharedWith; } + + public bool CanCreateSharedContext => true; + public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) + { + var versions = preferredVersions?.Append(Version).ToArray() ?? new[] { Version }; + return WglDisplay.CreateContext(versions, _sharedWith ?? this); + } + + public object TryGetFeature(Type featureType) => null; } } diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs index bc27589689..9a1963a97a 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.InteropServices; using Avalonia.OpenGL; +using Avalonia.Threading; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.OpenGl.WglConsts; @@ -9,8 +10,6 @@ namespace Avalonia.Win32.OpenGl internal class WglDisplay { private static bool? _initialized; - private static ushort _windowClass; - private static readonly WndProc _wndProcDelegate = WndProc; private static readonly DebugCallbackDelegate _debugCallback = DebugCallback; private static IntPtr _bootstrapContext; @@ -44,18 +43,9 @@ namespace Avalonia.Win32.OpenGl } static bool InitializeCore() { - var wndClassEx = new WNDCLASSEX - { - cbSize = Marshal.SizeOf(), - hInstance = GetModuleHandle(null), - lpfnWndProc = _wndProcDelegate, - lpszClassName = "AvaloniaGlWindow-" + Guid.NewGuid(), - style = (int)ClassStyles.CS_OWNDC - }; - - _windowClass = RegisterClassEx(ref wndClassEx); - _bootstrapWindow = CreateOffscreenWindow(); - _bootstrapDc = GetDC(_bootstrapWindow); + Dispatcher.UIThread.VerifyAccess(); + _bootstrapWindow = WglGdiResourceManager.CreateOffscreenWindow(); + _bootstrapDc = WglGdiResourceManager.GetDC(_bootstrapWindow); _defaultPfd = new PixelFormatDescriptor { Size = (ushort)Marshal.SizeOf(), @@ -105,17 +95,11 @@ namespace Avalonia.Win32.OpenGl DescribePixelFormat(_bootstrapDc, formats[0], Marshal.SizeOf(), ref _defaultPfd); _defaultPixelFormat = formats[0]; } - - + wglMakeCurrent(IntPtr.Zero, IntPtr.Zero); return true; } - - static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) - { - return DefWindowProc(hWnd, msg, wParam, lParam); - } - + private static void DebugCallback(int source, int type, int id, int severity, int len, IntPtr message, IntPtr userparam) { var err = Marshal.PtrToStringAnsi(message, len); @@ -131,27 +115,32 @@ namespace Avalonia.Win32.OpenGl using (new WglRestoreContext(_bootstrapDc, _bootstrapContext, null)) { - var window = CreateOffscreenWindow(); - var dc = GetDC(window); + var window = WglGdiResourceManager.CreateOffscreenWindow(); + var dc = WglGdiResourceManager.GetDC(window); SetPixelFormat(dc, _defaultPixelFormat, ref _defaultPfd); foreach (var version in versions) { if(version.Type != GlProfileType.OpenGL) continue; - var context = WglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero, - new[] - { - // major - WGL_CONTEXT_MAJOR_VERSION_ARB, version.Major, - // minor - WGL_CONTEXT_MINOR_VERSION_ARB, version.Minor, - // core profile - WGL_CONTEXT_PROFILE_MASK_ARB, 1, - // debug - // WGL_CONTEXT_FLAGS_ARB, 1, - // end - 0, 0 - }); + IntPtr context; + using (shareContext?.Lock()) + { + context = WglCreateContextAttribsArb(dc, shareContext?.Handle ?? IntPtr.Zero, + new[] + { + // major + WGL_CONTEXT_MAJOR_VERSION_ARB, version.Major, + // minor + WGL_CONTEXT_MINOR_VERSION_ARB, version.Minor, + // core profile + WGL_CONTEXT_PROFILE_MASK_ARB, 1, + // debug + // WGL_CONTEXT_FLAGS_ARB, 1, + // end + 0, 0 + }); + } + using(new WglRestoreContext(dc, context, null)) GlDebugMessageCallback(Marshal.GetFunctionPointerForDelegate(_debugCallback), IntPtr.Zero); if (context != IntPtr.Zero) @@ -159,26 +148,13 @@ namespace Avalonia.Win32.OpenGl _defaultPixelFormat, _defaultPfd); } - ReleaseDC(window, dc); - DestroyWindow(window); + WglGdiResourceManager.ReleaseDC(window, dc); + WglGdiResourceManager.DestroyWindow(window); return null; } } - static IntPtr CreateOffscreenWindow() => - CreateWindowEx( - 0, - _windowClass, - null, - (int)WindowStyles.WS_OVERLAPPEDWINDOW, - 0, - 0, - 640, - 480, - IntPtr.Zero, - IntPtr.Zero, - IntPtr.Zero, - IntPtr.Zero); + } } diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs b/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs new file mode 100644 index 0000000000..ed40488c70 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OpenGl/WglGdiResourceManager.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32.OpenGl; + +/// +/// - ReleaseDC can only happen from the same thread that has called GetDC +/// - When thread exits all of its windows and HDCs are getting destroyed +/// - We need to create OpenGL context (require a window and an HDC) and render targets (require an HDC) from thread pool threads +/// +/// So this class hosts a dedicated thread for managing offscreen windows and HDCs for OpenGL +/// + +internal class WglGdiResourceManager +{ + class GetDCOp + { + public IntPtr Window; + public TaskCompletionSource Result; + } + + class ReleaseDCOp + { + public IntPtr Window; + public IntPtr DC; + public TaskCompletionSource Result; + } + + class CreateWindowOp + { + public TaskCompletionSource Result; + } + + class DestroyWindowOp + { + public IntPtr Window; + public TaskCompletionSource Result; + } + + private static readonly Queue s_Queue = new(); + private static readonly AutoResetEvent s_Event = new(false); + private static readonly ushort s_WindowClass; + private static readonly UnmanagedMethods.WndProc s_wndProcDelegate = WndProc; + + static void Worker() + { + while (true) + { + s_Event.WaitOne(); + lock (s_Queue) + { + if(s_Queue.Count == 0) + continue; + var job = s_Queue.Dequeue(); + if (job is GetDCOp getDc) + getDc.Result.TrySetResult(UnmanagedMethods.GetDC(getDc.Window)); + else if (job is ReleaseDCOp releaseDc) + { + UnmanagedMethods.ReleaseDC(releaseDc.Window, releaseDc.DC); + releaseDc.Result.SetResult(null); + } + else if (job is CreateWindowOp createWindow) + createWindow.Result.TrySetResult(UnmanagedMethods.CreateWindowEx( + 0, + s_WindowClass, + null, + (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW, + 0, + 0, + 640, + 480, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero)); + else if (job is DestroyWindowOp destroyWindow) + { + UnmanagedMethods.DestroyWindow(destroyWindow.Window); + destroyWindow.Result.TrySetResult(null); + } + } + } + } + + static WglGdiResourceManager() + { + var wndClassEx = new UnmanagedMethods.WNDCLASSEX + { + cbSize = Marshal.SizeOf(), + hInstance = UnmanagedMethods.GetModuleHandle(null), + lpfnWndProc = s_wndProcDelegate, + lpszClassName = "AvaloniaGlWindow-" + Guid.NewGuid(), + style = (int)UnmanagedMethods.ClassStyles.CS_OWNDC + }; + + s_WindowClass = UnmanagedMethods.RegisterClassEx(ref wndClassEx); + var th = new Thread(Worker) { IsBackground = true, Name = "Win32 OpenGL HDC manager" }; + // This makes CLR to automatically pump the event queue from WaitOne + th.SetApartmentState(ApartmentState.STA); + th.Start(); + } + + + + static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); + } + + public static IntPtr CreateOffscreenWindow() + { + var tcs = new TaskCompletionSource(); + lock(s_Queue) + s_Queue.Enqueue(new CreateWindowOp() + { + Result = tcs + }); + s_Event.Set(); + return tcs.Task.Result; + } + + public static IntPtr GetDC(IntPtr hWnd) + { + var tcs = new TaskCompletionSource(); + lock(s_Queue) + s_Queue.Enqueue(new GetDCOp + { + Window = hWnd, + Result = tcs + }); + s_Event.Set(); + return tcs.Task.Result; + } + + public static void ReleaseDC(IntPtr hWnd, IntPtr hDC) + { + var tcs = new TaskCompletionSource(); + lock(s_Queue) + s_Queue.Enqueue(new ReleaseDCOp() + { + Window = hWnd, + DC = hDC, + Result = tcs + }); + s_Event.Set(); + tcs.Task.Wait(); + } + + public static void DestroyWindow(IntPtr hWnd) + { + var tcs = new TaskCompletionSource(); + lock(s_Queue) + s_Queue.Enqueue(new DestroyWindowOp() + { + Window = hWnd, + Result = tcs + }); + s_Event.Set(); + tcs.Task.Wait(); + + } +} diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs index 72bcffd447..6d3d2d28c4 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglGlPlatformSurface.cs @@ -11,18 +11,16 @@ namespace Avalonia.Win32.OpenGl class WglGlPlatformSurface: IGlPlatformSurface { - private readonly WglContext _context; private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; - public WglGlPlatformSurface(WglContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) + public WglGlPlatformSurface( EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) { - _context = context; _info = info; } - public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { - return new RenderTarget(_context, _info); + return new RenderTarget((WglContext)context, _info); } class RenderTarget : IGlPlatformSurfaceRenderTarget @@ -39,7 +37,7 @@ namespace Avalonia.Win32.OpenGl public void Dispose() { - UnmanagedMethods.ReleaseDC(_hdc, _info.Handle); + WglGdiResourceManager.ReleaseDC(_info.Handle, _hdc); } public IGlPlatformSurfaceRenderingSession BeginDraw() diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs index 1d0880a468..39dd330d52 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglPlatformOpenGlInterface.cs @@ -6,15 +6,13 @@ using Avalonia.Platform; namespace Avalonia.Win32.OpenGl { - class WglPlatformOpenGlInterface : IPlatformOpenGlInterface + class WglPlatformOpenGlInterface : IPlatformGraphics { public WglContext PrimaryContext { get; } - IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext; - IGlContext IPlatformOpenGlInterface.PrimaryContext => PrimaryContext; - public IGlContext CreateSharedContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, PrimaryContext); - - public bool CanShareContexts => true; - public bool CanCreateContexts => true; + public bool UsesSharedContext => false; + IPlatformGraphicsContext IPlatformGraphics.CreateContext() => CreateContext(); + public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException(); + public IGlContext CreateContext() => WglDisplay.CreateContext(new[] { PrimaryContext.Version }, null); private WglPlatformOpenGlInterface(WglContext primary) diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs index 265f078a5c..b145ffbb37 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglRestoreContext.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using System.Threading; using Avalonia.OpenGL; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -22,9 +23,11 @@ namespace Avalonia.Win32.OpenGl if (!wglMakeCurrent(gc, context)) { + var lastError = Marshal.GetLastWin32Error(); + var caps = GetDeviceCaps(gc, (DEVICECAP)12); if(monitor != null && takeMonitor) Monitor.Exit(monitor); - throw new OpenGlException("Unable to make the context current"); + throw new OpenGlException($"Unable to make the context current: {lastError}, DC valid: {caps != 0}"); } } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index 204acc82c9..25ea060576 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -2,8 +2,9 @@ using Avalonia.OpenGL; using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; using Avalonia.Platform; -using Avalonia.Win32.DxgiSwapchain; +using Avalonia.Win32.DirectX; using Avalonia.Win32.OpenGl; +using Avalonia.Win32.OpenGl.Angle; using Avalonia.Win32.WinRT.Composition; namespace Avalonia.Win32 @@ -11,15 +12,15 @@ namespace Avalonia.Win32 static class Win32GlManager { - public static IPlatformOpenGlInterface Initialize() + public static IPlatformGraphics Initialize() { var gl = InitializeCore(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl); return gl; } - static IPlatformOpenGlInterface InitializeCore() + static IPlatformGraphics InitializeCore() { var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions(); @@ -31,28 +32,18 @@ namespace Avalonia.Win32 if (opts.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7) { - var egl = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); + var egl = AngleWin32PlatformGraphics.TryCreate(AvaloniaLocator.Current.GetService() ?? + new()); - if (egl != null) + if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11) { - if (opts.EglRendererBlacklist != null) - { - foreach (var item in opts.EglRendererBlacklist) - { - if (egl.PrimaryEglContext.GlInterface.Renderer.Contains(item)) - { - return null; - } - } - } - if (opts.UseWindowsUIComposition) { - WinUICompositorConnection.TryCreateAndRegister(egl, opts.CompositionBackdropCornerRadius); + WinUiCompositorConnection.TryCreateAndRegister(); } else if (opts.UseLowLatencyDxgiSwapChain) { - DxgiConnection.TryCreateAndRegister(egl); + DxgiConnection.TryCreateAndRegister(); } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 63cad9679a..3f16b49772 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -23,6 +23,7 @@ using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia { +#nullable enable public static class Win32ApplicationExtensions { public static T UseWin32( @@ -59,11 +60,6 @@ namespace Avalonia /// GPU rendering will not be enabled if this is set to false. /// public bool? AllowEglInitialization { get; set; } - - public IList EglRendererBlacklist { get; set; } = new List - { - "Microsoft Basic Render" - }; /// /// Embeds popups to the window when set to true. The default value is false. @@ -106,8 +102,14 @@ namespace Avalonia /// which if active will override this setting. /// public bool UseLowLatencyDxgiSwapChain { get; set; } = false; + + /// + /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu + /// + public IPlatformGraphics? CustomPlatformGraphics { get; set; } } } +#nullable restore namespace Avalonia.Win32 { @@ -139,6 +141,7 @@ namespace Avalonia.Win32 public static Win32PlatformOptions Options { get; private set; } internal static Compositor Compositor { get; private set; } + internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; } public static void Initialize() { @@ -169,16 +172,19 @@ namespace Avalonia.Win32 .Bind().ToConstant(new NonPumpingSyncContext.HelperImpl()) .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()) .Bind().ToConstant(s_instance); - - var gl = Win32GlManager.Initialize(); - + _uiThread = Thread.CurrentThread; + var platformGraphics = options?.CustomPlatformGraphics + ?? Win32GlManager.Initialize(); + if (OleContext.Current != null) AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); if (Options.UseCompositor) - Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl); + Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), platformGraphics); + else + RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics); } public bool HasMessages() diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs deleted file mode 100644 index ef3de9fbe1..0000000000 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Numerics; -using System.Reactive.Disposables; -using System.Threading; -using Avalonia.MicroCom; -using Avalonia.OpenGL; -using Avalonia.OpenGL.Egl; -using Avalonia.Win32.Interop; -using MicroCom.Runtime; - -namespace Avalonia.Win32.WinRT.Composition -{ - public class WinUICompositedWindow : IDisposable - { - private EglContext _syncContext; - private readonly object _pumpLock; - private readonly IVisual _micaVisual; - private readonly ICompositionRoundedRectangleGeometry _roundedRectangleGeometry; - private readonly IVisual _blurVisual; - private ICompositionTarget _compositionTarget; - private IVisual _contentVisual; - private ICompositionDrawingSurfaceInterop _surfaceInterop; - private PixelSize _size; - - private static Guid IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); - private ICompositor _compositor; - - - internal WinUICompositedWindow(EglContext syncContext, - ICompositor compositor, - object pumpLock, - ICompositionTarget compositionTarget, - ICompositionDrawingSurfaceInterop surfaceInterop, - IVisual contentVisual, IVisual blurVisual, IVisual micaVisual, - ICompositionRoundedRectangleGeometry roundedRectangleGeometry) - { - _compositor = compositor.CloneReference(); - _syncContext = syncContext; - _pumpLock = pumpLock; - _micaVisual = micaVisual; - _roundedRectangleGeometry = roundedRectangleGeometry; - _blurVisual = blurVisual.CloneReference(); - _compositionTarget = compositionTarget.CloneReference(); - _contentVisual = contentVisual.CloneReference(); - _surfaceInterop = surfaceInterop.CloneReference(); - } - - - public void ResizeIfNeeded(PixelSize size) - { - using (_syncContext.EnsureLocked()) - { - if (_size != size) - { - _surfaceInterop.Resize(new UnmanagedMethods.POINT { X = size.Width, Y = size.Height }); - _contentVisual.SetSize(new Vector2(size.Width, size.Height)); - _roundedRectangleGeometry?.SetSize(new Vector2(size.Width, size.Height)); - _size = size; - } - } - } - - public unsafe IUnknown BeginDrawToTexture(out PixelPoint offset) - { - if (!_syncContext.IsCurrent) - throw new InvalidOperationException(); - - var iid = IID_ID3D11Texture2D; - void* pTexture; - var off = _surfaceInterop.BeginDraw(null, &iid, &pTexture); - offset = new PixelPoint(off.X, off.Y); - return MicroComRuntime.CreateProxyFor(pTexture, true); - } - - public void EndDraw() - { - if (!_syncContext.IsCurrent) - throw new InvalidOperationException(); - _surfaceInterop.EndDraw(); - } - - public void SetBlur(BlurEffect blurEffect) - { - using (_syncContext.EnsureLocked()) - { - _blurVisual.SetIsVisible(blurEffect == BlurEffect.Acrylic ? 1 : 0); - _micaVisual?.SetIsVisible(blurEffect == BlurEffect.Mica ? 1 : 0); - } - } - - public IDisposable BeginTransaction() - { - Monitor.Enter(_pumpLock); - return Disposable.Create(() => Monitor.Exit(_pumpLock)); - } - - public void Dispose() - { - if (_syncContext == null) - { - _compositor.Dispose(); - _blurVisual.Dispose(); - _contentVisual.Dispose(); - _surfaceInterop.Dispose(); - _compositionTarget.Dispose(); - } - } - } -} diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs deleted file mode 100644 index 6da17b8ea5..0000000000 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Numerics; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Avalonia.Logging; -using Avalonia.MicroCom; -using Avalonia.OpenGL; -using Avalonia.OpenGL.Angle; -using Avalonia.OpenGL.Egl; -using Avalonia.Rendering; -using Avalonia.Win32.Interop; -using MicroCom.Runtime; - -namespace Avalonia.Win32.WinRT.Composition -{ - class WinUICompositorConnection : IRenderTimer - { - public static readonly Version MinHostBackdropVersion = new Version(10, 0, 22000); - private readonly float? _backdropCornerRadius; - private readonly EglContext _syncContext; - private readonly ICompositionBrush _micaBrush; - private ICompositor _compositor; - private ICompositor5 _compositor5; - private ICompositorInterop _compositorInterop; - private AngleWin32EglDisplay _angle; - private ICompositionGraphicsDevice _device; - private EglPlatformOpenGlInterface _gl; - private ICompositorDesktopInterop _compositorDesktopInterop; - private ICompositionBrush _blurBrush; - private object _pumpLock = new object(); - - public WinUICompositorConnection(EglPlatformOpenGlInterface gl, object pumpLock, float? backdropCornerRadius) - { - _gl = gl; - _pumpLock = pumpLock; - _backdropCornerRadius = backdropCornerRadius; - _syncContext = _gl.PrimaryEglContext; - _angle = (AngleWin32EglDisplay)_gl.Display; - _compositor = NativeWinRTMethods.CreateInstance("Windows.UI.Composition.Compositor"); - _compositor5 = _compositor.QueryInterface(); - _compositorInterop = _compositor.QueryInterface(); - _compositorDesktopInterop = _compositor.QueryInterface(); - using var device = MicroComRuntime.CreateProxyFor(_angle.GetDirect3DDevice(), true); - - _device = _compositorInterop.CreateGraphicsDevice(device); - _blurBrush = CreateAcrylicBlurBackdropBrush(); - _micaBrush = CreateMicaBackdropBrush(); - } - - public EglPlatformOpenGlInterface Egl => _gl; - - static bool TryCreateAndRegisterCore(EglPlatformOpenGlInterface angle, float? backdropCornerRadius) - { - var tcs = new TaskCompletionSource(); - var pumpLock = new object(); - var th = new Thread(() => - { - WinUICompositorConnection connect; - try - { - NativeWinRTMethods.CreateDispatcherQueueController(new NativeWinRTMethods.DispatcherQueueOptions - { - apartmentType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_NONE, - dwSize = Marshal.SizeOf(), - threadType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT - }); - connect = new WinUICompositorConnection(angle, pumpLock, backdropCornerRadius); - AvaloniaLocator.CurrentMutable.BindToSelf(connect); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(connect); - tcs.SetResult(true); - - } - catch (Exception e) - { - tcs.SetException(e); - return; - } - connect.RunLoop(); - }) - { - IsBackground = true - }; - th.SetApartmentState(ApartmentState.STA); - th.Start(); - return tcs.Task.Result; - } - - class RunLoopHandler : IAsyncActionCompletedHandler, IMicroComShadowContainer - { - private readonly WinUICompositorConnection _parent; - private Stopwatch _st = Stopwatch.StartNew(); - - public RunLoopHandler(WinUICompositorConnection parent) - { - _parent = parent; - } - public void Dispose() - { - - } - - public void Invoke(IAsyncAction asyncInfo, AsyncStatus asyncStatus) - { - _parent.Tick?.Invoke(_st.Elapsed); - using var act = _parent._compositor5.RequestCommitAsync(); - act.SetCompleted(this); - } - - public MicroComShadow Shadow { get; set; } - public void OnReferencedFromNative() - { - } - - public void OnUnreferencedFromNative() - { - } - } - - private void RunLoop() - { - var cts = new CancellationTokenSource(); - AppDomain.CurrentDomain.ProcessExit += (sender, args) => - cts.Cancel(); - - using (var act = _compositor5.RequestCommitAsync()) - act.SetCompleted(new RunLoopHandler(this)); - - while (!cts.IsCancellationRequested) - { - UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0); - lock (_pumpLock) - UnmanagedMethods.DispatchMessage(ref msg); - } - } - - public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle, - float? backdropCornerRadius) - { - const int majorRequired = 10; - const int buildRequired = 17134; - - var majorInstalled = Win32Platform.WindowsVersion.Major; - var buildInstalled = Win32Platform.WindowsVersion.Build; - - if (majorInstalled >= majorRequired && - buildInstalled >= buildRequired) - { - try - { - TryCreateAndRegisterCore(angle, backdropCornerRadius); - return; - } - catch (Exception e) - { - Logger.TryGet(LogEventLevel.Error, "WinUIComposition") - ?.Log(null, "Unable to initialize WinUI compositor: {0}", e); - - } - } - - 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}"); - } - - - public WinUICompositedWindow CreateWindow(IntPtr hWnd) - { - using var sc = _syncContext.EnsureLocked(); - using var desktopTarget = _compositorDesktopInterop.CreateDesktopWindowTarget(hWnd, 0); - using var target = desktopTarget.QueryInterface(); - using var device2 = _device.QueryInterface(); - - using var drawingSurface = device2.CreateDrawingSurface2(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized, - DirectXAlphaMode.Premultiplied); - using var surface = drawingSurface.QueryInterface(); - using var surfaceInterop = drawingSurface.QueryInterface(); - - using var surfaceBrush = _compositor.CreateSurfaceBrushWithSurface(surface); - using var brush = surfaceBrush.QueryInterface(); - - using var spriteVisual = _compositor.CreateSpriteVisual(); - spriteVisual.SetBrush(brush); - using var visual = spriteVisual.QueryInterface(); - using var visual2 = spriteVisual.QueryInterface(); - using var container = _compositor.CreateContainerVisual(); - using var containerVisual = container.QueryInterface(); - using var containerVisual2 = container.QueryInterface(); - containerVisual2.SetRelativeSizeAdjustment(new Vector2(1, 1)); - using var containerChildren = container.Children; - - target.SetRoot(containerVisual); - - using var blur = CreateBlurVisual(_blurBrush); - IVisual mica = null; - if (_micaBrush != null) - { - mica = CreateBlurVisual(_micaBrush); - containerChildren.InsertAtTop(mica); - } - - var compositionRoundedRectangleGeometry = ClipVisual(blur, mica); - - containerChildren.InsertAtTop(blur); - containerChildren.InsertAtTop(visual); - - return new WinUICompositedWindow(_syncContext, _compositor, _pumpLock, target, surfaceInterop, visual, - blur, mica, compositionRoundedRectangleGeometry); - } - - private ICompositionBrush CreateMicaBackdropBrush() - { - if (Win32Platform.WindowsVersion.Build < 22000) - return null; - - using var compositorWithBlurredWallpaperBackdropBrush = - _compositor.QueryInterface(); - using var blurredWallpaperBackdropBrush = - compositorWithBlurredWallpaperBackdropBrush?.TryCreateBlurredWallpaperBackdropBrush(); - using var micaBackdropBrush = blurredWallpaperBackdropBrush?.QueryInterface(); - return micaBackdropBrush.CloneReference(); - } - - private unsafe ICompositionBrush CreateAcrylicBlurBackdropBrush() - { - using var backDropParameterFactory = NativeWinRTMethods.CreateActivationFactory( - "Windows.UI.Composition.CompositionEffectSourceParameter"); - using var backdropString = new HStringInterop("backdrop"); - using var backDropParameter = - backDropParameterFactory.Create(backdropString.Handle); - using var backDropParameterAsSource = backDropParameter.QueryInterface(); - var blurEffect = new WinUIGaussianBlurEffect(backDropParameterAsSource); - using var blurEffectFactory = _compositor.CreateEffectFactory(blurEffect); - using var compositionEffectBrush = blurEffectFactory.CreateBrush(); - using var backdropBrush = CreateBackdropBrush(); - - var saturateEffect = new SaturationEffect(blurEffect); - using var satEffectFactory = _compositor.CreateEffectFactory(saturateEffect); - using var sat = satEffectFactory.CreateBrush(); - compositionEffectBrush.SetSourceParameter(backdropString.Handle, backdropBrush); - return compositionEffectBrush.QueryInterface(); - } - - private ICompositionRoundedRectangleGeometry ClipVisual(params IVisual[] containerVisuals) - { - if (!_backdropCornerRadius.HasValue) - return null; - using var roundedRectangleGeometry = _compositor5.CreateRoundedRectangleGeometry(); - roundedRectangleGeometry.SetCornerRadius(new Vector2(_backdropCornerRadius.Value, _backdropCornerRadius.Value)); - - using var compositor6 = _compositor.QueryInterface(); - using var compositionGeometry = roundedRectangleGeometry - .QueryInterface(); - - using var geometricClipWithGeometry = - compositor6.CreateGeometricClipWithGeometry(compositionGeometry); - foreach (var visual in containerVisuals) - { - visual?.SetClip(geometricClipWithGeometry.QueryInterface()); - } - - return roundedRectangleGeometry.CloneReference(); - } - - private unsafe IVisual CreateBlurVisual(ICompositionBrush compositionBrush) - { - using var spriteVisual = _compositor.CreateSpriteVisual(); - using var visual = spriteVisual.QueryInterface(); - using var visual2 = spriteVisual.QueryInterface(); - - - spriteVisual.SetBrush(compositionBrush); - visual.SetIsVisible(0); - visual2.SetRelativeSizeAdjustment(new Vector2(1.0f, 1.0f)); - - return visual.CloneReference(); - } - - private ICompositionBrush CreateBackdropBrush() - { - ICompositionBackdropBrush brush = null; - try - { - if (Win32Platform.WindowsVersion >= MinHostBackdropVersion) - { - using var compositor3 = _compositor.QueryInterface(); - brush = compositor3.CreateHostBackdropBrush(); - } - else - { - using var compositor2 = _compositor.QueryInterface(); - brush = compositor2.CreateBackdropBrush(); - } - - return brush.QueryInterface(); - } - finally - { - brush?.Dispose(); - } - } - - public event Action Tick; - public bool RunsInBackground => true; - } -} diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs new file mode 100644 index 0000000000..32019f4c15 --- /dev/null +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs @@ -0,0 +1,110 @@ +using System; +using System.Numerics; +using System.Reactive.Disposables; +using System.Threading; +using Avalonia.OpenGL.Egl; +using Avalonia.Win32.Interop; +using MicroCom.Runtime; + +namespace Avalonia.Win32.WinRT.Composition; + +internal class WinUiCompositedWindow : IDisposable +{ + public EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; } + private readonly WinUiCompositionShared _shared; + private readonly ICompositionRoundedRectangleGeometry _compositionRoundedRectangleGeometry; + private readonly IVisual _mica; + private readonly IVisual _blur; + private readonly IVisual _visual; + private PixelSize _size; + private readonly ICompositionSurfaceBrush _surfaceBrush; + private readonly ICompositionTarget _target; + + public void Dispose() + { + lock (_shared.SyncRoot) + { + _compositionRoundedRectangleGeometry?.Dispose(); + _blur?.Dispose(); + _mica?.Dispose(); + _visual.Dispose(); + _surfaceBrush.Dispose(); + _target.Dispose(); + } + } + + public WinUiCompositedWindow(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info, + WinUiCompositionShared shared, float? backdropCornerRadius) + { + WindowInfo = info; + _shared = shared; + using var desktopTarget = shared.DesktopInterop.CreateDesktopWindowTarget(WindowInfo.Handle, 0); + _target = desktopTarget.QueryInterface(); + + + using var container = shared.Compositor.CreateContainerVisual(); + using var containerVisual = container.QueryInterface(); + using var containerVisual2 = container.QueryInterface(); + containerVisual2.SetRelativeSizeAdjustment(new Vector2(1, 1)); + using var containerChildren = container.Children; + + _target.SetRoot(containerVisual); + + _blur = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.BlurBrush); + if (shared.MicaBrush != null) + { + _mica = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.MicaBrush); + containerChildren.InsertAtTop(_mica); + } + + _compositionRoundedRectangleGeometry = + WinUiCompositionUtils.ClipVisual(shared.Compositor, backdropCornerRadius, _blur, _mica); + + containerChildren.InsertAtTop(_blur); + using var spriteVisual = shared.Compositor.CreateSpriteVisual(); + _visual = spriteVisual.QueryInterface(); + containerChildren.InsertAtTop(_visual); + + _surfaceBrush = shared.Compositor.CreateSurfaceBrush(); + using var compositionBrush = _surfaceBrush.QueryInterface(); + spriteVisual.SetBrush(compositionBrush); + _target.SetRoot(containerVisual); + + + + } + + public void SetSurface(ICompositionSurface surface) => _surfaceBrush.SetSurface(surface); + + public void SetBlur(BlurEffect blurEffect) + { + lock (_shared.SyncRoot) + { + + _blur.SetIsVisible(blurEffect == BlurEffect.Acrylic + || blurEffect == BlurEffect.Mica && _mica == null ? + 1 : + 0); + _mica?.SetIsVisible(blurEffect == BlurEffect.Mica ? 1 : 0); + } + } + + public IDisposable BeginTransaction() + { + Monitor.Enter(_shared.SyncRoot); + return Disposable.Create(() => Monitor.Exit(_shared.SyncRoot)); + } + + public void ResizeIfNeeded(PixelSize size) + { + lock (_shared.SyncRoot) + { + if (_size != size) + { + _visual.SetSize(new Vector2(size.Width, size.Height)); + _compositionRoundedRectangleGeometry?.SetSize(new Vector2(size.Width, size.Height)); + _size = size; + } + } + } +} diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index fc04dcda26..9ee024f9ad 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs @@ -1,119 +1,203 @@ using System; using System.Runtime.InteropServices; using Avalonia.MicroCom; +using Avalonia.OpenGL; using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; +using Avalonia.Platform; using Avalonia.Utilities; +using Avalonia.Win32.DirectX; using Avalonia.Win32.Interop; +using Avalonia.Win32.OpenGl.Angle; using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { - internal class WinUiCompositedWindowSurface : EglGlPlatformSurfaceBase, IBlurHost, IDisposable + internal class WinUiCompositedWindowSurface : IDirect3D11TexturePlatformSurface, IDisposable, IBlurHost { - private readonly WinUICompositorConnection _connection; - private EglPlatformOpenGlInterface _egl; - private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; - private IRef _window; + private readonly WinUiCompositionShared _shared; + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; + private WinUiCompositedWindow _window; private BlurEffect _blurEffect; - public WinUiCompositedWindowSurface(WinUICompositorConnection connection, IEglWindowGlPlatformSurfaceInfo info) : base() + public WinUiCompositedWindowSurface(WinUiCompositionShared shared, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) { - _connection = connection; - _egl = connection.Egl; + _shared = shared; _info = info; } - public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice) { - using (_egl.PrimaryContext.EnsureCurrent()) - { - if (_window?.Item == null) - { - _window = RefCountable.Create(_connection.CreateWindow(_info.Handle)); - _window.Item.SetBlur(_blurEffect); - } + var cornerRadius = AvaloniaLocator.Current.GetService() + ?.CompositionBackdropCornerRadius; + _window ??= new WinUiCompositedWindow(_info, _shared, cornerRadius); + _window.SetBlur(_blurEffect); - return new CompositionRenderTarget(_egl, _window, _info); - } + return new WinUiCompositedWindowRenderTarget(context, _window, d3dDevice, _shared.Compositor); + } + + public void Dispose() + { + _window?.Dispose(); + _window = null; + } + + public void SetBlur(BlurEffect enable) + { + _blurEffect = enable; + _window?.SetBlur(enable); } + } - class CompositionRenderTarget : EglPlatformSurfaceRenderTargetBase + internal class WinUiCompositedWindowRenderTarget : IDirect3D11TextureRenderTarget + { + private readonly IPlatformGraphicsContext _context; + private readonly WinUiCompositedWindow _window; + private readonly IUnknown _d3dDevice; + private readonly ICompositor _compositor; + private readonly ICompositorInterop _interop; + private readonly ICompositionGraphicsDevice _compositionDevice; + private readonly ICompositionGraphicsDevice2 _compositionDevice2; + private readonly ICompositionSurface _surface; + private PixelSize _size; + private bool _lost; + private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; + + public WinUiCompositedWindowRenderTarget(IPlatformGraphicsContext context, + WinUiCompositedWindow window, IntPtr device, + ICompositor compositor) { - private readonly EglPlatformOpenGlInterface _egl; - private readonly IRef _window; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - - public CompositionRenderTarget(EglPlatformOpenGlInterface egl, - IRef window, - IEglWindowGlPlatformSurfaceInfo info) - : base(egl) + _context = context; + _window = window; + + try { - _egl = egl; - _window = window.Clone(); - _info = info; - _window.Item.ResizeIfNeeded(_info.Size); + _d3dDevice = MicroComRuntime.CreateProxyFor(device, false).CloneReference(); + _compositor = compositor.CloneReference(); + _interop = compositor.QueryInterface(); + _compositionDevice = _interop.CreateGraphicsDevice(_d3dDevice); + _compositionDevice2 = _compositionDevice.QueryInterface(); + _drawingSurface = _compositionDevice2.CreateDrawingSurface2(new UnmanagedMethods.SIZE(), + DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied); + _surface = _drawingSurface.QueryInterface(); + _surfaceInterop = _drawingSurface.QueryInterface(); } + finally + { + if (_surfaceInterop == null) + Dispose(); + } + } - public override IGlPlatformSurfaceRenderingSession BeginDraw() + public void Dispose() + { + _surface?.Dispose(); + _surfaceInterop?.Dispose(); + _drawingSurface?.Dispose(); + _compositionDevice2?.Dispose(); + _compositionDevice?.Dispose(); + _interop?.Dispose(); + _compositor?.Dispose(); + _d3dDevice?.Dispose(); + } + + public bool IsCorrupted => _context.IsLost || _lost; + private static Guid IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); + private readonly ICompositionDrawingSurface _drawingSurface; + + public unsafe IDirect3D11TextureRenderTargetRenderSession BeginDraw() + { + if (IsCorrupted) + throw new RenderTargetCorruptedException(); + var transaction = _window.BeginTransaction(); + bool needsEndDraw = false; + try { - var contextLock = _egl.PrimaryEglContext.EnsureCurrent(); - IUnknown texture = null; - EglSurface surface = null; - IDisposable transaction = null; - var success = false; + var size = _window.WindowInfo.Size; + var scale = _window.WindowInfo.Scaling; + _window.ResizeIfNeeded(size); + _window.SetSurface(_surface); + + void* pTexture; + UnmanagedMethods.POINT off; try { - if (_window?.Item == null) - throw new ObjectDisposedException(GetType().FullName); - - var size = _info.Size; - transaction = _window.Item.BeginTransaction(); - _window.Item.ResizeIfNeeded(size); - texture = _window.Item.BeginDrawToTexture(out var offset); - - surface = ((AngleWin32EglDisplay) _egl.Display).WrapDirect3D11Texture(_egl, - texture.GetNativeIntPtr(), - offset.X, offset.Y, size.Width, size.Height); - - var res = base.BeginDraw(surface, _info, () => + if (_size != size) { - surface?.Dispose(); - texture?.Dispose(); - _window.Item.EndDraw(); - transaction?.Dispose(); - contextLock?.Dispose(); - }, true); - success = true; - return res; + _surfaceInterop.Resize(new UnmanagedMethods.POINT + { + X = size.Width, + Y = size.Height + }); + _size = size; + } + var iid = IID_ID3D11Texture2D; + off = _surfaceInterop.BeginDraw(null, &iid, &pTexture); } - finally + catch (Exception e) { - if (!success) - { - surface?.Dispose(); - texture?.Dispose(); - transaction?.Dispose(); - contextLock.Dispose(); - } + _lost = true; + throw new RenderTargetCorruptedException(e); + } + + needsEndDraw = true; + var offset = new PixelPoint(off.X, off.Y); + using var texture = MicroComRuntime.CreateProxyFor(pTexture, true); + + var session = new Session(_surfaceInterop, texture, transaction, _size, offset, scale); + transaction = null; + return session; + } + finally + { + if (transaction != null) + { + if (needsEndDraw) + _surfaceInterop.EndDraw(); + transaction?.Dispose(); } } } - public void SetBlur(BlurEffect blurEffect) + class Session : IDirect3D11TextureRenderTargetRenderSession { - _blurEffect = blurEffect; - _window?.Item?.SetBlur(blurEffect); - } + private readonly IDisposable _transaction; + private readonly PixelSize _size; + private readonly PixelPoint _offset; + private readonly double _scaling; + private readonly ICompositionDrawingSurfaceInterop _surfaceInterop; + private readonly IUnknown _texture; - public void Dispose() - { - using (_egl.PrimaryEglContext.EnsureLocked()) + public Session(ICompositionDrawingSurfaceInterop surfaceInterop, IUnknown texture, IDisposable transaction, + PixelSize size, PixelPoint offset, double scaling) { - _window?.Dispose(); - _window = null; + _transaction = transaction; + _size = size; + _offset = offset; + _scaling = scaling; + _surfaceInterop = surfaceInterop.CloneReference(); + _texture = texture.CloneReference(); } + + public void Dispose() + { + try + { + _texture.Dispose(); + _surfaceInterop.EndDraw(); + _surfaceInterop.Dispose(); + } + finally + { + _transaction.Dispose(); + } + } + + public IntPtr D3D11Texture2D => _texture.GetNativeIntPtr(); + public PixelSize Size => _size; + public PixelPoint Offset => _offset; + public double Scaling => _scaling; } } -} +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs new file mode 100644 index 0000000000..301e605e7a --- /dev/null +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs @@ -0,0 +1,34 @@ +using System; +using MicroCom.Runtime; + +namespace Avalonia.Win32.WinRT.Composition; + +internal class WinUiCompositionShared : IDisposable +{ + public ICompositor Compositor { get; } + public ICompositor5 Compositor5 { get; } + public ICompositorDesktopInterop DesktopInterop { get; } + public ICompositionBrush BlurBrush; + public ICompositionBrush MicaBrush; + public object SyncRoot { get; } = new(); + + public static readonly Version MinHostBackdropVersion = new Version(10, 0, 22000); + + public WinUiCompositionShared(ICompositor compositor) + { + Compositor = compositor.CloneReference(); + Compositor5 = compositor.QueryInterface(); + BlurBrush = WinUiCompositionUtils.CreateAcrylicBlurBackdropBrush(compositor); + MicaBrush = WinUiCompositionUtils.CreateMicaBackdropBrush(compositor); + DesktopInterop = compositor.QueryInterface(); + } + + public void Dispose() + { + BlurBrush.Dispose(); + MicaBrush.Dispose(); + DesktopInterop.Dispose(); + Compositor.Dispose(); + Compositor5.Dispose(); + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs new file mode 100644 index 0000000000..e7af4046d6 --- /dev/null +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs @@ -0,0 +1,101 @@ +using System; +using System.Numerics; +using MicroCom.Runtime; + +namespace Avalonia.Win32.WinRT.Composition; + +internal static class WinUiCompositionUtils +{ + public static ICompositionBrush CreateMicaBackdropBrush(ICompositor compositor) + { + if (Win32Platform.WindowsVersion.Build < 22000) + return null; + + using var compositorWithBlurredWallpaperBackdropBrush = + compositor.QueryInterface(); + using var blurredWallpaperBackdropBrush = + compositorWithBlurredWallpaperBackdropBrush?.TryCreateBlurredWallpaperBackdropBrush(); + return blurredWallpaperBackdropBrush?.QueryInterface(); + } + + public static unsafe ICompositionBrush CreateAcrylicBlurBackdropBrush(ICompositor compositor) + { + using var backDropParameterFactory = + NativeWinRTMethods.CreateActivationFactory( + "Windows.UI.Composition.CompositionEffectSourceParameter"); + using var backdropString = new HStringInterop("backdrop"); + using var backDropParameter = + backDropParameterFactory.Create(backdropString.Handle); + using var backDropParameterAsSource = backDropParameter.QueryInterface(); + var blurEffect = new WinUIGaussianBlurEffect(backDropParameterAsSource); + using var blurEffectFactory = compositor.CreateEffectFactory(blurEffect); + using var compositionEffectBrush = blurEffectFactory.CreateBrush(); + using var backdropBrush = CreateBackdropBrush(compositor); + + var saturateEffect = new SaturationEffect(blurEffect); + using var satEffectFactory = compositor.CreateEffectFactory(saturateEffect); + using var sat = satEffectFactory.CreateBrush(); + compositionEffectBrush.SetSourceParameter(backdropString.Handle, backdropBrush); + return compositionEffectBrush.QueryInterface(); + } + + public static ICompositionRoundedRectangleGeometry ClipVisual(ICompositor compositor, float? _backdropCornerRadius, params IVisual[] containerVisuals) + { + if (!_backdropCornerRadius.HasValue) + return null; + using var compositor5 = compositor.QueryInterface(); + using var roundedRectangleGeometry = compositor5.CreateRoundedRectangleGeometry(); + roundedRectangleGeometry.SetCornerRadius(new Vector2(_backdropCornerRadius.Value, _backdropCornerRadius.Value)); + + using var compositor6 = compositor.QueryInterface(); + using var compositionGeometry = roundedRectangleGeometry + .QueryInterface(); + + using var geometricClipWithGeometry = + compositor6.CreateGeometricClipWithGeometry(compositionGeometry); + foreach (var visual in containerVisuals) + { + visual?.SetClip(geometricClipWithGeometry.QueryInterface()); + } + + return roundedRectangleGeometry.CloneReference(); + } + + public static IVisual CreateBlurVisual(ICompositor compositor, ICompositionBrush compositionBrush) + { + using var spriteVisual = compositor.CreateSpriteVisual(); + using var visual = spriteVisual.QueryInterface(); + using var visual2 = spriteVisual.QueryInterface(); + + + spriteVisual.SetBrush(compositionBrush); + visual.SetIsVisible(0); + visual2.SetRelativeSizeAdjustment(new Vector2(1.0f, 1.0f)); + + return visual.CloneReference(); + } + + public static ICompositionBrush CreateBackdropBrush(ICompositor compositor) + { + ICompositionBackdropBrush brush = null; + try + { + if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion) + { + using var compositor3 = compositor.QueryInterface(); + brush = compositor3.CreateHostBackdropBrush(); + } + else + { + using var compositor2 = compositor.QueryInterface(); + brush = compositor2.CreateBackdropBrush(); + } + + return brush.QueryInterface(); + } + finally + { + brush?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs new file mode 100644 index 0000000000..a1408baae0 --- /dev/null +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs @@ -0,0 +1,151 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Logging; +using Avalonia.MicroCom; +using Avalonia.OpenGL.Egl; +using Avalonia.Rendering; +using Avalonia.Win32.DirectX; +using Avalonia.Win32.Interop; +using Avalonia.Win32.OpenGl.Angle; +using MicroCom.Runtime; + +namespace Avalonia.Win32.WinRT.Composition; + +internal class WinUiCompositorConnection : IRenderTimer +{ + private readonly WinUiCompositionShared _shared; + public event Action Tick; + public bool RunsInBackground => true; + + public unsafe WinUiCompositorConnection() + { + using var compositor = NativeWinRTMethods.CreateInstance("Windows.UI.Composition.Compositor"); + /* + var levels = new[] { D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1 }; + DirectXUnmanagedMethods.D3D11CreateDevice(IntPtr.Zero, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, + IntPtr.Zero, 0, levels, (uint)levels.Length, 7, out var pD3dDevice, out var level, null); + + var d3dDevice = MicroComRuntime.CreateProxyFor(pD3dDevice, true); + + var compositionDevice = compositor.QueryInterface().CreateGraphicsDevice(d3dDevice); + var surf = compositionDevice.CreateDrawingSurface(new UnmanagedMethods.SIZE_F { X = 100, Y = 100 }, + DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied); + var surfInterop = surf.QueryInterface(); + var IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); + void* texture = null; + surfInterop.BeginDraw(null, &IID_ID3D11Texture2D, &texture); + */ + + _shared = new WinUiCompositionShared(compositor); + } + + static bool TryCreateAndRegisterCore() + { + var tcs = new TaskCompletionSource(); + var pumpLock = new object(); + var th = new Thread(() => + { + WinUiCompositorConnection connect; + try + { + NativeWinRTMethods.CreateDispatcherQueueController(new NativeWinRTMethods.DispatcherQueueOptions + { + apartmentType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_NONE, + dwSize = Marshal.SizeOf(), + threadType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT + }); + connect = new WinUiCompositorConnection(); + AvaloniaLocator.CurrentMutable.BindToSelf(connect); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(connect); + tcs.SetResult(true); + + } + catch (Exception e) + { + tcs.SetException(e); + return; + } + + connect.RunLoop(); + }) + { + IsBackground = true, + Name = "DwmRenderTimerLoop" + }; + th.SetApartmentState(ApartmentState.STA); + th.Start(); + return tcs.Task.Result; + } + + class RunLoopHandler : CallbackBase, IAsyncActionCompletedHandler + { + private readonly WinUiCompositorConnection _parent; + private Stopwatch _st = Stopwatch.StartNew(); + + public RunLoopHandler(WinUiCompositorConnection parent) + { + _parent = parent; + } + + public void Invoke(IAsyncAction asyncInfo, AsyncStatus asyncStatus) + { + _parent.Tick?.Invoke(_st.Elapsed); + using var act = _parent._shared.Compositor5.RequestCommitAsync(); + act.SetCompleted(this); + } + } + + private void RunLoop() + { + var cts = new CancellationTokenSource(); + AppDomain.CurrentDomain.ProcessExit += (sender, args) => + cts.Cancel(); + + lock (_shared.SyncRoot) + using (var act = _shared.Compositor5.RequestCommitAsync()) + act.SetCompleted(new RunLoopHandler(this)); + + while (!cts.IsCancellationRequested) + { + UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0); + lock (_shared.SyncRoot) + UnmanagedMethods.DispatchMessage(ref msg); + } + } + + public static void TryCreateAndRegister() + { + const int majorRequired = 10; + const int buildRequired = 17134; + + var majorInstalled = Win32Platform.WindowsVersion.Major; + var buildInstalled = Win32Platform.WindowsVersion.Build; + + if (majorInstalled >= majorRequired && + buildInstalled >= buildRequired) + { + try + { + TryCreateAndRegisterCore(); + return; + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, "WinUIComposition") + ?.Log(null, "Unable to initialize WinUI compositor: {0}", e); + + } + } + + 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}"); + } + + public WinUiCompositedWindowSurface CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new(_shared, info); +} diff --git a/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs b/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs index 786d698daa..35f0737d5f 100644 --- a/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs +++ b/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs @@ -3,7 +3,7 @@ namespace Avalonia.Win32.WinRT { [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct WinRTColor + public record struct WinRTColor { public byte A; public byte R; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 5537a0b704..8c362b0c29 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -10,6 +10,7 @@ using Avalonia.Controls.Remote; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.Win32.Automation; using Avalonia.Win32.Input; using Avalonia.Win32.Interop.Automation; @@ -69,7 +70,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_CLOSE: { - bool? preventClosing = Closing?.Invoke(); + bool? preventClosing = Closing?.Invoke(WindowCloseReason.WindowClosing); if (preventClosing == true) { return IntPtr.Zero; @@ -106,6 +107,9 @@ namespace Avalonia.Win32 _touchDevice?.Dispose(); //Free other resources Dispose(); + + // Schedule cleanup of anything that requires window to be destroyed + Dispatcher.UIThread.Post(AfterCloseCleanup); return IntPtr.Zero; } @@ -134,13 +138,18 @@ namespace Avalonia.Win32 case WindowsMessage.WM_KEYDOWN: case WindowsMessage.WM_SYSKEYDOWN: { - e = new RawKeyEventArgs( - WindowsKeyboardDevice.Instance, - timestamp, - _owner, - RawKeyEventType.KeyDown, - KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), - WindowsKeyboardDevice.Instance.Modifiers); + var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)); + + if (key != Key.None) + { + e = new RawKeyEventArgs( + WindowsKeyboardDevice.Instance, + timestamp, + _owner, + RawKeyEventType.KeyDown, + key, + WindowsKeyboardDevice.Instance.Modifiers); + } break; } @@ -159,13 +168,18 @@ namespace Avalonia.Win32 case WindowsMessage.WM_KEYUP: case WindowsMessage.WM_SYSKEYUP: { - e = new RawKeyEventArgs( + var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)); + + if (key != Key.None) + { + e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, timestamp, _owner, RawKeyEventType.KeyUp, - KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), + key, WindowsKeyboardDevice.Instance.Modifiers); + } break; } case WindowsMessage.WM_CHAR: diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5f813eec1d..dbcc31c5f5 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -26,7 +26,8 @@ using static Avalonia.Win32.Interop.UnmanagedMethods; using Avalonia.Collections.Pooled; using Avalonia.Metadata; using Avalonia.Platform.Storage; -using Avalonia.Win32.DxgiSwapchain; +using Avalonia.Win32.DirectX; +using Avalonia.Win32.OpenGl.Angle; namespace Avalonia.Win32 { @@ -64,7 +65,6 @@ namespace Avalonia.Win32 private Thickness _offScreenMargin; private double _extendTitleBarHint = -1; private bool _isUsingComposition; - private bool _isUsingDxgiSwapchain; private IBlurHost _blurHost; private PlatformResizeReason _resizeReason; private MOUSEMOVEPOINT _lastWmMousePoint; @@ -79,7 +79,7 @@ namespace Avalonia.Win32 private readonly PenDevice _penDevice; private readonly ManagedDeferredRendererLock _rendererLock; private readonly FramebufferManager _framebuffer; - private readonly IGlPlatformSurface _gl; + private readonly object _gl; private readonly bool _wmPointerEnabled; private Win32NativeControlHost _nativeControlHost; @@ -136,23 +136,20 @@ namespace Avalonia.Win32 }; _rendererLock = new ManagedDeferredRendererLock(); - var glPlatform = AvaloniaLocator.Current.GetService(); + var glPlatform = AvaloniaLocator.Current.GetService(); - var compositionConnector = AvaloniaLocator.Current.GetService(); + var compositionConnector = AvaloniaLocator.Current.GetService(); - _isUsingComposition = compositionConnector is { } && - glPlatform is EglPlatformOpenGlInterface egl && - egl.Display is AngleWin32EglDisplay angleDisplay && - angleDisplay.PlatformApi == AngleOptions.PlatformApi.DirectX11; + var isUsingAngleDX11 = glPlatform is AngleWin32PlatformGraphics angle && + angle.PlatformApi == AngleOptions.PlatformApi.DirectX11; + _isUsingComposition = compositionConnector is { } && isUsingAngleDX11; DxgiConnection dxgiConnection = null; + var isUsingDxgiSwapchain = false; if (!_isUsingComposition) { dxgiConnection = AvaloniaLocator.Current.GetService(); - _isUsingDxgiSwapchain = dxgiConnection is { } && - glPlatform is EglPlatformOpenGlInterface eglDxgi && - eglDxgi.Display is AngleWin32EglDisplay angleDisplayDxgi && - angleDisplayDxgi.PlatformApi == AngleOptions.PlatformApi.DirectX11; + isUsingDxgiSwapchain = dxgiConnection is { } && isUsingAngleDX11; } _wmPointerEnabled = Win32Platform.WindowsVersion >= PlatformConstants.Windows8; @@ -164,24 +161,24 @@ namespace Avalonia.Win32 { if (_isUsingComposition) { - var cgl = new WinUiCompositedWindowSurface(compositionConnector, this); + var cgl = compositionConnector.CreateSurface(this); _blurHost = cgl; _gl = cgl; _isUsingComposition = true; } - else if (_isUsingDxgiSwapchain) + else if (isUsingDxgiSwapchain) { var dxgigl = new DxgiSwapchainWindow(dxgiConnection, this); _gl = dxgigl; } else { - if (glPlatform is EglPlatformOpenGlInterface egl2) - _gl = new EglGlPlatformSurface(egl2, this); + if (glPlatform is AngleWin32PlatformGraphics egl2) + _gl = new EglGlPlatformSurface(this); else if (glPlatform is WglPlatformOpenGlInterface wgl) - _gl = new WglGlPlatformSurface(wgl.PrimaryContext, this); + _gl = new WglGlPlatformSurface(this); } } @@ -194,7 +191,7 @@ namespace Avalonia.Win32 public Action Activated { get; set; } - public Func Closing { get; set; } + public Func Closing { get; set; } public Action Closed { get; set; } @@ -434,7 +431,7 @@ namespace Avalonia.Win32 _ => BlurEffect.None }; - if (Win32Platform.WindowsVersion >= WinUICompositorConnection.MinHostBackdropVersion) + if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion) { unsafe { @@ -443,7 +440,7 @@ namespace Avalonia.Win32 } } - if (Win32Platform.WindowsVersion < WinUICompositorConnection.MinHostBackdropVersion && effect == BlurEffect.Mica) + if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion && effect == BlurEffect.Mica) { effect = BlurEffect.Acrylic; } @@ -570,16 +567,22 @@ namespace Avalonia.Win32 return customRendererFactory.Create(root, loop); if (Win32Platform.Compositor != null) - return new CompositingRenderer(root, Win32Platform.Compositor); - + return new CompositingRenderer(root, Win32Platform.Compositor, () => Surfaces); + return Win32Platform.UseDeferredRendering ? _isUsingComposition - ? new DeferredRenderer(root, loop) + ? new DeferredRenderer(root, loop, + () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces), + Win32Platform.RenderInterface) { RenderOnlyOnRenderThread = true } - : (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock) - : new ImmediateRenderer((Visual)root); + : (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock, + renderTargetFactory: () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces), + renderInterface: Win32Platform.RenderInterface) + : new ImmediateRenderer((Visual)root, + () => Win32Platform.RenderInterface.CreateRenderTarget(Surfaces), + Win32Platform.RenderInterface); } public void Resize(Size value, PlatformResizeReason reason) @@ -640,12 +643,6 @@ namespace Avalonia.Win32 _hwnd = IntPtr.Zero; } - if (_className != null) - { - UnregisterClass(_className, GetModuleHandle(null)); - _className = null; - } - _framebuffer.Dispose(); } @@ -1032,6 +1029,12 @@ namespace Avalonia.Win32 { var margins = UpdateExtendMargins(); DwmExtendFrameIntoClientArea(_hwnd, ref margins); + + unsafe + { + int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_ROUND; + DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int)); + } } else { @@ -1042,6 +1045,12 @@ namespace Avalonia.Win32 _extendedMargins = new Thickness(); Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout); + + unsafe + { + int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT; + DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int)); + } } if (!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && @@ -1141,6 +1150,15 @@ namespace Avalonia.Win32 } } + private void AfterCloseCleanup() + { + if (_className != null) + { + UnregisterClass(_className, GetModuleHandle(null)); + _className = null; + } + } + private void MaximizeWithoutCoveringTaskbar() { IntPtr monitor = MonitorFromWindow(_hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); @@ -1340,8 +1358,6 @@ namespace Avalonia.Win32 } private const int MF_BYCOMMAND = 0x0; - private const int MF_BYPOSITION = 0x400; - private const int MF_REMOVE = 0x1000; private const int MF_ENABLED = 0x0; private const int MF_GRAYED = 0x1; private const int MF_DISABLED = 0x2; diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs index e1b5f5a3a0..4f4e0b9293 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs @@ -8,7 +8,6 @@ namespace Avalonia.Win32 { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); return new WindowsMountedVolumeInfoListener(mountedDrives); } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs index fb0857e472..a1836f3ce4 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs @@ -1,6 +1,5 @@ #nullable enable using Avalonia.Input.TextInput; -using JetBrains.Annotations; using UIKit; namespace Avalonia.iOS; diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 2080352020..48358c745f 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -70,7 +70,8 @@ namespace Avalonia.iOS // No-op } - public IRenderer CreateRenderer(IRenderRoot root) => new CompositingRenderer(root, Platform.Compositor); + public IRenderer CreateRenderer(IRenderRoot root) => + new CompositingRenderer(root, Platform.Compositor, () => Surfaces); public void Invalidate(Rect rect) diff --git a/src/iOS/Avalonia.iOS/EaglDisplay.cs b/src/iOS/Avalonia.iOS/EaglDisplay.cs index 906bbc29e7..7a5e1a496d 100644 --- a/src/iOS/Avalonia.iOS/EaglDisplay.cs +++ b/src/iOS/Avalonia.iOS/EaglDisplay.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reactive.Disposables; using Avalonia.OpenGL; using Avalonia.Platform; @@ -6,29 +7,37 @@ using OpenGLES; namespace Avalonia.iOS { - class EaglFeature : IPlatformOpenGlInterface + class EaglPlatformGraphics : IPlatformGraphics { - public IGlContext PrimaryContext => Context; - public IGlContext CreateSharedContext() => throw new NotSupportedException(); - IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext; - public bool CanShareContexts => false; - public bool CanCreateContexts => false; - public IGlContext CreateContext() => throw new System.NotSupportedException(); - public GlContext Context { get; } = new GlContext(); + public IPlatformGraphicsContext GetSharedContext() => Context; + + public bool UsesSharedContext => true; + public IPlatformGraphicsContext CreateContext() => throw new System.NotSupportedException(); + public GlContext Context { get; } + public static GlVersion GlVersion { get; } = new(GlProfileType.OpenGLES, 3, 0); + + public EaglPlatformGraphics() + { + + const string path = "/System/Library/Frameworks/OpenGLES.framework/OpenGLES"; + var libGl = ObjCRuntime.Dlfcn.dlopen(path, 1); + if (libGl == IntPtr.Zero) + throw new OpenGlException("Unable to load " + path); + var iface = new GlInterface(GlVersion, proc => ObjCRuntime.Dlfcn.dlsym(libGl, proc)); + Context = new(iface, null); + } } class GlContext : IGlContext { public EAGLContext Context { get; private set; } - public GlContext() + public GlContext(GlInterface glInterface, EAGLSharegroup sharegroup) { - const string path = "/System/Library/Frameworks/OpenGLES.framework/OpenGLES"; - var libGl = ObjCRuntime.Dlfcn.dlopen(path, 1); - if (libGl == IntPtr.Zero) - throw new OpenGlException("Unable to load " + path); - GlInterface = new GlInterface(Version, proc => ObjCRuntime.Dlfcn.dlsym(libGl, proc)); - Context = new EAGLContext(EAGLRenderingAPI.OpenGLES3); + GlInterface = glInterface; + Context = sharegroup == null ? + new EAGLContext(EAGLRenderingAPI.OpenGLES3) : + new(EAGLRenderingAPI.OpenGLES3, sharegroup); } public void Dispose() @@ -59,22 +68,34 @@ namespace Avalonia.iOS public IDisposable MakeCurrent() { + if (Context == null) + throw new PlatformGraphicsContextLostException(); var old = EAGLContext.CurrentContext; if (!EAGLContext.SetCurrentContext(Context)) throw new OpenGlException("Unable to make context current"); return new ResetContext(old); } + public bool IsLost => Context == null; + public IDisposable EnsureCurrent() { + if (Context == null) + throw new PlatformGraphicsContextLostException(); if(EAGLContext.CurrentContext == Context) return Disposable.Empty; return MakeCurrent(); } - public bool IsSharedWith(IGlContext context) => false; + public bool IsSharedWith(IGlContext context) => context is GlContext other + && ReferenceEquals(other.Context?.ShareGroup, Context?.ShareGroup); + public bool CanCreateSharedContext => true; + public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) + { + return new GlContext(GlInterface, Context.ShareGroup); + } - public GlVersion Version { get; } = new GlVersion(GlProfileType.OpenGLES, 3, 0); + public GlVersion Version => EaglPlatformGraphics.GlVersion; public GlInterface GlInterface { get; } public int SampleCount { @@ -92,5 +113,7 @@ namespace Avalonia.iOS return stencil; } } + + public object TryGetFeature(Type featureType) => null; } } diff --git a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs index 0e8945d921..88812653d7 100644 --- a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs +++ b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs @@ -78,10 +78,12 @@ namespace Avalonia.iOS throw new InvalidOperationException("Invalid thread, go away"); } - public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { CheckThread(); var ctx = Platform.GlFeature.Context; + if (ctx != context) + throw new InvalidOperationException("Platform surface is only usable with tha main context"); using (ctx.MakeCurrent()) { var fbo = new SizeSynchronizedLayerFbo(ctx.Context, ctx.GlInterface, _layer); diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index c2cf8c6f6c..eb0a55734a 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -25,18 +25,18 @@ namespace Avalonia.iOS { static class Platform { - public static EaglFeature GlFeature; + public static EaglPlatformGraphics GlFeature; public static DisplayLinkTimer Timer; internal static Compositor Compositor { get; private set; } public static void Register() { - GlFeature ??= new EaglFeature(); + GlFeature ??= new EaglPlatformGraphics(); Timer ??= new DisplayLinkTimer(); var keyboard = new KeyboardDevice(); AvaloniaLocator.CurrentMutable - .Bind().ToConstant(GlFeature) + .Bind().ToConstant(GlFeature) .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl()) @@ -50,7 +50,7 @@ namespace Avalonia.iOS Compositor = new Compositor( AvaloniaLocator.Current.GetRequiredService(), - AvaloniaLocator.Current.GetService()); + AvaloniaLocator.Current.GetService()); } diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 602e5e4496..0162f53f5e 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -17,6 +17,9 @@ + + + diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index c42cb0241b..547b2cb176 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -12,6 +12,7 @@ using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; using Moq; +using Nito.AsyncEx; using Xunit; #nullable enable @@ -920,6 +921,120 @@ namespace Avalonia.Base.UnitTests } } + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Typed_Bind_Executes_On_UIThread(BindingPriority priority) + { + AsyncContext.Run(async () => + { + var target = new Class1(); + var source = new Subject(); + var currentThreadId = Thread.CurrentThread.ManagedThreadId; + var raised = 0; + + var threadingInterfaceMock = new Mock(); + threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread) + .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); + + var services = new TestServices( + threadingInterface: threadingInterfaceMock.Object); + + target.PropertyChanged += (s, e) => + { + Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId); + ++raised; + }; + + using (UnitTestApplication.Start(services)) + { + target.Bind(Class1.FooProperty, source, priority); + + await Task.Run(() => source.OnNext("foobar")); + Dispatcher.UIThread.RunJobs(); + + Assert.Equal("foobar", target.GetValue(Class1.FooProperty)); + Assert.Equal(1, raised); + } + }); + } + + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Untyped_Bind_Executes_On_UIThread(BindingPriority priority) + { + AsyncContext.Run(async () => + { + var target = new Class1(); + var source = new Subject(); + var currentThreadId = Thread.CurrentThread.ManagedThreadId; + var raised = 0; + + var threadingInterfaceMock = new Mock(); + threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread) + .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); + + var services = new TestServices( + threadingInterface: threadingInterfaceMock.Object); + + target.PropertyChanged += (s, e) => + { + Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId); + ++raised; + }; + + using (UnitTestApplication.Start(services)) + { + target.Bind(Class1.FooProperty, source, priority); + + await Task.Run(() => source.OnNext("foobar")); + Dispatcher.UIThread.RunJobs(); + + Assert.Equal("foobar", target.GetValue(Class1.FooProperty)); + Assert.Equal(1, raised); + } + }); + } + + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void BindingValue_Bind_Executes_On_UIThread(BindingPriority priority) + { + AsyncContext.Run(async () => + { + var target = new Class1(); + var source = new Subject>(); + var currentThreadId = Thread.CurrentThread.ManagedThreadId; + var raised = 0; + + var threadingInterfaceMock = new Mock(); + threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread) + .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); + + var services = new TestServices( + threadingInterface: threadingInterfaceMock.Object); + + target.PropertyChanged += (s, e) => + { + Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId); + ++raised; + }; + + using (UnitTestApplication.Start(services)) + { + target.Bind(Class1.FooProperty, source, priority); + + await Task.Run(() => source.OnNext("foobar")); + Dispatcher.UIThread.RunJobs(); + + Assert.Equal("foobar", target.GetValue(Class1.FooProperty)); + Assert.Equal(1, raised); + } + }); + } + [Fact] public async Task Bind_With_Scheduler_Executes_On_Scheduler() { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 20172eea88..973090ee92 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -7,8 +7,10 @@ using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Logging; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.UnitTests; using Moq; +using Nito.AsyncEx; using Xunit; namespace Avalonia.Base.UnitTests @@ -519,25 +521,39 @@ namespace Avalonia.Base.UnitTests } [Fact] - public async Task Bind_Executes_On_UIThread() + public void Bind_Executes_On_UIThread() { - var target = new Class1(); - var source = new Subject(); - var currentThreadId = Thread.CurrentThread.ManagedThreadId; + AsyncContext.Run(async () => + { + var target = new Class1(); + var source = new Subject(); + var currentThreadId = Thread.CurrentThread.ManagedThreadId; + var raised = 0; - var threadingInterfaceMock = new Mock(); - threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread) - .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); + var threadingInterfaceMock = new Mock(); + threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread) + .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); - var services = new TestServices( - threadingInterface: threadingInterfaceMock.Object); + var services = new TestServices( + threadingInterface: threadingInterfaceMock.Object); - using (UnitTestApplication.Start(services)) - { - target.Bind(Class1.FooProperty, source); + target.PropertyChanged += (s, e) => + { + Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId); + ++raised; + }; - await Task.Run(() => source.OnNext("foobar")); - } + using (UnitTestApplication.Start(services)) + { + target.Bind(Class1.FooProperty, source); + + await Task.Run(() => source.OnNext("foobar")); + Dispatcher.UIThread.RunJobs(); + + Assert.Equal("foobar", target.Foo); + Assert.Equal(1, raised); + } + }); } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 508eff5f9d..59085a21ce 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Input.GestureRecognizers; +using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -201,5 +203,101 @@ namespace Avalonia.Base.UnitTests.Input border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt")); } + + [Fact] + public void Pinched_Should_Not_Be_Raised_For_Same_Pointer() + { + var touch = new TouchTestHelper(); + + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new PinchGestureRecognizer()); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true); + + var firstPoint = new Point(5, 5); + var secondPoint = new Point(10, 10); + + touch.Down(border, position: firstPoint); + touch.Down(border, position: secondPoint); + touch.Down(border, position: new Point(20, 20)); + + Assert.False(raised); + } + + [Fact] + public void Pinched_Should_Be_Raised_For_Two_Pointers_Moving() + { + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new PinchGestureRecognizer()); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true); + + var firstPoint = new Point(5, 5); + var secondPoint = new Point(10, 10); + + var firstTouch = new TouchTestHelper(); + var secondTouch = new TouchTestHelper(); + + firstTouch.Down(border, position: firstPoint); + secondTouch.Down(border, position: secondPoint); + secondTouch.Move(border, position: new Point(20, 20)); + + Assert.True(raised); + } + + [Fact] + public void Scrolling_Should_Start_After_Start_Distance_Is_Exceded() + { + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new ScrollGestureRecognizer() + { + CanHorizontallyScroll = true, + CanVerticallyScroll = true, + ScrollStartDistance = 50 + }); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.ScrollGestureEvent, (s, e) => raised = true); + + var firstTouch = new TouchTestHelper(); + + firstTouch.Down(border, position: new Point(5, 5)); + firstTouch.Move(border, position: new Point(20, 20)); + + Assert.False(raised); + + firstTouch.Move(border, position: new Point(70, 20)); + + Assert.True(raised); + } } } diff --git a/tests/Avalonia.Base.UnitTests/RectTests.cs b/tests/Avalonia.Base.UnitTests/RectTests.cs index 95a438b287..c44b328ed5 100644 --- a/tests/Avalonia.Base.UnitTests/RectTests.cs +++ b/tests/Avalonia.Base.UnitTests/RectTests.cs @@ -52,7 +52,7 @@ namespace Avalonia.Base.UnitTests double.PositiveInfinity, double.PositiveInfinity) .Normalize(); - Assert.Equal(Rect.Empty, result); + Assert.Equal(default, result); } } } diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs index 7b987d8e68..7f4e160000 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs @@ -18,7 +18,6 @@ using Avalonia.Rendering.Composition; using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; -using JetBrains.Annotations; using Xunit; namespace Avalonia.Base.UnitTests.Rendering; @@ -76,7 +75,7 @@ public class CompositorTestsBase public IRenderer CreateRenderer(IRenderRoot root) { - return Renderer = new CompositingRenderer(root, _compositor); + return Renderer = new CompositingRenderer(root, _compositor, () => Surfaces); } public void Invalidate(Rect rect) diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs index 08e5955ec5..f0c5a24cc4 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests.cs @@ -46,6 +46,7 @@ namespace Avalonia.Base.UnitTests.Rendering var target = new DeferredRenderer( root, loop.Object, + renderTargetFactory: root.CreateRenderTarget, sceneBuilder: sceneBuilder.Object); target.Start(); @@ -83,6 +84,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); target.Start(); @@ -133,6 +135,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); root.Renderer = target; @@ -178,6 +181,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); root.Renderer = target; @@ -223,6 +227,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); root.Renderer = target; @@ -270,6 +275,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); root.Renderer = target; @@ -317,6 +323,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); root.Renderer = target; @@ -366,6 +373,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); root.Renderer = target; @@ -415,6 +423,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); var otherSceneBuilder = new SceneBuilder(); @@ -423,6 +432,7 @@ namespace Avalonia.Base.UnitTests.Rendering otherRoot, loop.Object, sceneBuilder: otherSceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); root.Renderer = target; @@ -738,6 +748,7 @@ namespace Avalonia.Base.UnitTests.Rendering root, new RenderLoop(timer.Object, dispatcher), sceneBuilder: sceneBuilder, + renderTargetFactory: root.CreateRenderTarget, dispatcher: dispatcher); root.Renderer = target; target.Start(); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index 2cf42d9604..4f11af7327 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -35,7 +35,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); @@ -63,7 +63,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); @@ -100,7 +100,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); @@ -129,7 +129,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); @@ -173,7 +173,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); @@ -227,7 +227,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); @@ -276,7 +276,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); container.Measure(Size.Infinity); container.Arrange(new Rect(container.DesiredSize)); @@ -324,7 +324,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); @@ -399,11 +399,11 @@ namespace Avalonia.Base.UnitTests.Rendering scroll.UpdateChild(); - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(Rect.Empty); + root.Renderer.Paint(default); var result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); Assert.Equal(item1, result); @@ -419,7 +419,7 @@ namespace Avalonia.Base.UnitTests.Rendering container.InvalidateArrange(); container.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(Rect.Empty); + root.Renderer.Paint(default); result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); Assert.Equal(item2, result); @@ -447,7 +447,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); @@ -486,7 +486,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds); @@ -522,7 +522,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); @@ -560,7 +560,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs index 26a155d203..a07191f464 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Base.UnitTests.Rendering root.LayoutManager.ExecuteInitialLayoutPass(); - var target = new ImmediateRenderer(root); + var target = new ImmediateRenderer(root, root.CreateRenderTarget); target.AddDirty(child); @@ -69,7 +69,7 @@ namespace Avalonia.Base.UnitTests.Rendering child.RenderTransform = new ScaleTransform() { ScaleX = 2, ScaleY = 2 }; - var target = new ImmediateRenderer(root); + var target = new ImmediateRenderer(root, root.CreateRenderTarget); target.AddDirty(child); @@ -97,7 +97,7 @@ namespace Avalonia.Base.UnitTests.Rendering Height = 400, }; - var target = new ImmediateRenderer(root); + var target = new ImmediateRenderer(root, root.CreateRenderTarget); root.LayoutManager.ExecuteInitialLayoutPass(); target.AddDirty(child); @@ -165,7 +165,7 @@ namespace Avalonia.Base.UnitTests.Rendering stackPanel.Children.Add(control3); var root = new TestRoot(rootGrid); - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.LayoutManager.ExecuteInitialLayoutPass(); var rootSize = new Size(RootWidth, RootHeight); @@ -223,7 +223,7 @@ namespace Avalonia.Base.UnitTests.Rendering stackPanel.Children.Add(control3); var root = new TestRoot(rootGrid); - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.LayoutManager.ExecuteInitialLayoutPass(); var rootSize = new Size(RootWidth, RootHeight); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs index 70e4acba1e..9ce8c42e33 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs @@ -32,7 +32,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -70,7 +70,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -100,7 +100,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -145,7 +145,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -200,7 +200,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -259,7 +259,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); container.Measure(Size.Infinity); container.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -308,7 +308,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -384,7 +384,7 @@ namespace Avalonia.Base.UnitTests.Rendering scroll.UpdateChild(); - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); @@ -434,7 +434,7 @@ namespace Avalonia.Base.UnitTests.Rendering } }; - root.Renderer = new ImmediateRenderer(root); + root.Renderer = new ImmediateRenderer(root, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); root.Renderer.Paint(new Rect(root.ClientSize)); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs index 2fac968206..07d2d672ae 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -13,9 +13,9 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph [Fact] public void Empty_Bounds_Remain_Empty() { - var target = new TestDrawOperation(Rect.Empty, Matrix.Identity, null); + var target = new TestDrawOperation(default, Matrix.Identity, null); - Assert.Equal(Rect.Empty, target.Bounds); + Assert.Equal(default, target.Bounds); } [Theory] diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 10db08f302..42e33729ac 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -8,13 +8,15 @@ using Avalonia.Media.Imaging; namespace Avalonia.Base.UnitTests.VisualTree { - class MockRenderInterface : IPlatformRenderInterface + class MockRenderInterface : IPlatformRenderInterface, IPlatformRenderInterfaceContext { public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); } + public object TryGetFeature(Type featureType) => null; + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { throw new NotImplementedException(); @@ -77,6 +79,11 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } + public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) + { + return this; + } + public bool SupportsIndividualRoundRects { get; set; } public AlphaFormat DefaultAlphaFormat { get; } public PixelFormat DefaultPixelFormat { get; } @@ -263,6 +270,11 @@ namespace Avalonia.Base.UnitTests.VisualTree } } } + + public void Dispose() + { + + } } } diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs index b40ea40985..0c516a0481 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetVisualsAt.cs @@ -44,7 +44,7 @@ namespace Avalonia.Base.UnitTests.VisualTree } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); @@ -86,7 +86,7 @@ namespace Avalonia.Base.UnitTests.VisualTree } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); diff --git a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs index 0039f5670c..cd323ededb 100644 --- a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs +++ b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace Avalonia.Benchmarks.Data { @@ -38,7 +37,6 @@ namespace Avalonia.Benchmarks.Data { } - [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); diff --git a/tests/Avalonia.Benchmarks/NullRenderer.cs b/tests/Avalonia.Benchmarks/NullRenderer.cs index 16f8998038..feb325f630 100644 --- a/tests/Avalonia.Benchmarks/NullRenderer.cs +++ b/tests/Avalonia.Benchmarks/NullRenderer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Avalonia.Rendering; using Avalonia.VisualTree; @@ -43,5 +44,7 @@ namespace Avalonia.Benchmarks public void Stop() { } + + public ValueTask TryGetRenderInterfaceFeature(Type featureType) => new(0); } } diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 4170de71e6..a272d89b8a 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -9,7 +9,7 @@ using Microsoft.Diagnostics.Runtime; namespace Avalonia.Benchmarks { - internal class NullRenderingPlatform : IPlatformRenderInterface + internal class NullRenderingPlatform : IPlatformRenderInterface, IPlatformRenderInterfaceContext { public IGeometryImpl CreateEllipseGeometry(Rect rect) { @@ -46,6 +46,8 @@ namespace Avalonia.Benchmarks throw new NotImplementedException(); } + public object TryGetFeature(Type featureType) => null; + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { throw new NotImplementedException(); @@ -123,10 +125,19 @@ namespace Avalonia.Benchmarks return new MockGlyphRun(); } + public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) + { + return this; + } + public bool SupportsIndividualRoundRects => true; public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888; + public void Dispose() + { + + } } } diff --git a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs index e82576c7d9..70636d1fe6 100644 --- a/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Themes/ThemeBenchmark.cs @@ -16,6 +16,8 @@ namespace Avalonia.Benchmarks.Themes public class ThemeBenchmark : IDisposable { private IDisposable _app; + private readonly FluentTheme _reusableFluentTheme = new FluentTheme(); + private readonly SimpleTheme _reusableSimpleTheme = new SimpleTheme(); public ThemeBenchmark() { @@ -49,6 +51,26 @@ namespace Avalonia.Benchmarks.Themes }; return ((IResourceHost)UnitTestApplication.Current).TryGetResource("ThemeAccentColor", out _); } + + [Benchmark] + [Arguments(typeof(Button))] + [Arguments(typeof(TextBox))] + [Arguments(typeof(DatePicker))] + public object FindFluentControlTheme(Type type) + { + _reusableFluentTheme.TryGetResource(type, out var theme); + return theme; + } + + [Benchmark] + [Arguments(typeof(Button))] + [Arguments(typeof(TextBox))] + [Arguments(typeof(DatePicker))] + public object FindSimpleControlTheme(Type type) + { + _reusableSimpleTheme.TryGetResource(type, out var theme); + return theme; + } public void Dispose() { diff --git a/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs b/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs index d43d6bd48b..e160c8dfa8 100644 --- a/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs @@ -172,7 +172,7 @@ internal sealed class AvaloniaPropertyValueStoreOld internal class MockProperty : StyledProperty { - public MockProperty([JetBrains.Annotations.NotNull] string name) : base(name, typeof(object), new StyledPropertyMetadata()) + public MockProperty(string name) : base(name, typeof(object), new StyledPropertyMetadata()) { } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 08fd777ac6..2d1dad6be0 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -569,8 +569,8 @@ namespace Avalonia.Controls.UnitTests.Presenters var target = CreateTarget(itemCount: 10); var items = (IList)target.Items; target.ApplyTemplate(); - target.Measure(Size.Empty); - target.Arrange(Rect.Empty); + target.Measure(default(Size)); + target.Arrange(default); // Check for issue #591: this should not throw. target.ScrollIntoView(0); diff --git a/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs b/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs index 668af3b5d7..1b629be695 100644 --- a/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Specialized; +using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Selection; using Avalonia.Controls.Utils; @@ -1144,6 +1145,24 @@ namespace Avalonia.Controls.UnitTests.Selection Assert.Equal(new[] { "foo" }, target.SelectedItems); Assert.Equal(0, target.AnchorIndex); } + + [Fact] + public void SelectedItems_Indexer_Is_Correct() + { + // Issue #7974 + var target = CreateTarget(); + var raised = 0; + + target.SelectionChanged += (s, e) => + { + Assert.Equal("bar", e.SelectedItems.First()); + Assert.Equal("bar", e.SelectedItems[0]); + ++raised; + }; + + target.Select(1); + Assert.Equal(1, raised); + } } public class BatchUpdate diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs index 8d8ce10d4c..067d709ae1 100644 --- a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Shapes target.Measure(new Size(100, 100)); var geometry = Assert.IsType(target.RenderedGeometry); - Assert.Equal(Rect.Empty, geometry.Rect); + Assert.Equal(default, geometry.Rect); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index cd38bf556a..81936711ef 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -17,7 +17,6 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; -using JetBrains.Annotations; using Moq; using Xunit; diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 435d0d92ce..9dc0cb95bd 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -155,12 +155,16 @@ namespace Avalonia.Controls.UnitTests window.Closing += (sender, e) => { + Assert.Equal(WindowCloseReason.WindowClosing, e.CloseReason); + Assert.Equal(programaticClose, e.IsProgrammatic); count++; windowClosing = count; }; child.Closing += (sender, e) => { + Assert.Equal(WindowCloseReason.OwnerWindowClosing, e.CloseReason); + Assert.Equal(programaticClose, e.IsProgrammatic); count++; childClosing = count; }; @@ -186,7 +190,7 @@ namespace Avalonia.Controls.UnitTests } else { - var cancel = window.PlatformImpl.Closing(); + var cancel = window.PlatformImpl.Closing(WindowCloseReason.WindowClosing); Assert.Equal(false, cancel); } @@ -248,7 +252,7 @@ namespace Avalonia.Controls.UnitTests } else { - var cancel = window.PlatformImpl.Closing(); + var cancel = window.PlatformImpl.Closing(WindowCloseReason.WindowClosing); Assert.Equal(true, cancel); } diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index ae8df6168d..c713a9e61d 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Disposables; - +using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Presenters; @@ -1060,6 +1060,8 @@ namespace Avalonia.LeakTests public void Stop() { } + + public ValueTask TryGetRenderInterfaceFeature(Type featureType) => new(null); } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs index d51d6122cd..59a17a8cd1 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs @@ -166,6 +166,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data [Fact] public void Binding_Method_To_Command_Collected() { + using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); + WeakReference MakeRef() { var weakVm = new WeakReference(null); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs new file mode 100644 index 0000000000..92807b2cb9 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/MergeResourceIncludeTests.cs @@ -0,0 +1,131 @@ +using System; +using System.Runtime.CompilerServices; +using System.Xml; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Media; +using Avalonia.Styling; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml; + +public class MergeResourceIncludeTests +{ + static MergeResourceIncludeTests() + { + RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle); + } + + [Fact] + public void MergeResourceInclude_Works_With_Single_Resource() + { + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources.xaml"), @" + + Red +"), + new RuntimeXamlLoaderDocument(@" + + + + Blue + + + + + +") + }; + + var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + var contentControl = Assert.IsType(objects[1]); + + var resources = Assert.IsType(contentControl.Resources); + Assert.Empty(resources.MergedDictionaries); + + var initialResource = (ISolidColorBrush)resources["brush1"]!; + Assert.Equal(Colors.Blue, initialResource.Color); + + var mergedResource = (ISolidColorBrush)resources["brush2"]!; + Assert.Equal(Colors.Red, mergedResource.Color); + } + + [Fact] + public void Mixing_MergeResourceInclude_And_ResourceInclude_Is_Not_Allowed() + { + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @" + + Red +"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @" + + Blue +"), + new RuntimeXamlLoaderDocument(@" + + + + + +") + }; + + Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.LoadGroup(documents)); + } + + [Fact] + public void MergeResourceInclude_Works_With_Multiple_Resources() + { + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1.xaml"), @" + + Red + Blue +"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources2.xaml"), @" + + Yellow + + + +"), + new RuntimeXamlLoaderDocument(@" + + + + + + Black + White +"), + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resources1_2.xaml"), @" + + Green +"), + }; + + var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + var resources = Assert.IsType(objects[2]); + Assert.Empty(resources.MergedDictionaries); + + Assert.Equal(Colors.Red, ((ISolidColorBrush)resources["brush1"]!).Color); + Assert.Equal(Colors.Blue, ((ISolidColorBrush)resources["brush2"]!).Color); + Assert.Equal(Colors.Green, ((ISolidColorBrush)resources["brush3"]!).Color); + Assert.Equal(Colors.Yellow, ((ISolidColorBrush)resources["brush4"]!).Color); + Assert.Equal(Colors.Black, ((ISolidColorBrush)resources["brush5"]!).Color); + Assert.Equal(Colors.White, ((ISolidColorBrush)resources["brush6"]!).Color); + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs index d76e51f419..5d6d4a78e4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using Avalonia.Controls; +using Avalonia.Data; using Avalonia.Markup.Xaml.Styling; using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Media; @@ -15,6 +17,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml; public class StyleIncludeTests { + static StyleIncludeTests() + { + RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle); + AssetLoader.RegisterResUriParsers(); + } + [Fact] public void StyleInclude_Is_Built() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 4b32a8cdca..f42f787117 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -15,7 +15,6 @@ using Avalonia.Styling; using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; -using JetBrains.Annotations; using Xunit; namespace Avalonia.Markup.Xaml.UnitTests @@ -389,7 +388,6 @@ namespace Avalonia.Markup.Xaml.UnitTests public bool IsPressed { get; set; } public event PropertyChangedEventHandler PropertyChanged; - [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index d52539c371..2d83f5ce0f 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -69,8 +69,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media { var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt; var fb = new Framebuffer(fmt, new PixelSize(80, 80)); - var r = Avalonia.AvaloniaLocator.Current.GetService(); - using (var target = r.CreateRenderTarget(new object[] { fb })) + var r = Avalonia.AvaloniaLocator.Current.GetRequiredService(); + using(var cpuContext = r.CreateBackendContext(null)) + using (var target = cpuContext.CreateRenderTarget(new object[] { fb })) using (var ctx = target.CreateDrawingContext(null)) { ctx.Clear(Colors.Transparent); diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 3f918e2a73..8a127897d7 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Threading; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Media; using Avalonia.Rendering.Composition; using Avalonia.Threading; @@ -122,10 +123,13 @@ namespace Avalonia.Direct2D1.RenderTests var timer = new ManualRenderTimer(); var compositor = new Compositor(new RenderLoop(timer, Dispatcher.UIThread), null); - using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpiVector)) + using (var writableBitmap = factory.CreateWriteableBitmap(pixelSize, dpiVector, factory.DefaultPixelFormat, factory.DefaultAlphaFormat)) { - var root = new TestRenderRoot(dpiVector.X / 96, rtb); - using (var renderer = new CompositingRenderer(root, compositor) { RenderOnlyOnRenderThread = false}) + var root = new TestRenderRoot(dpiVector.X / 96, null!); + using (var renderer = new CompositingRenderer(root, compositor, () => new[] + { + new BitmapFramebufferSurface(writableBitmap) + }) { RenderOnlyOnRenderThread = false }) { root.Initialize(renderer, target); renderer.Start(); @@ -136,8 +140,20 @@ namespace Avalonia.Direct2D1.RenderTests // Free pools for (var c = 0; c < 11; c++) TestThreadingInterface.RunTimers(); - rtb.Save(compositedPath); + writableBitmap.Save(compositedPath); + } + } + + class BitmapFramebufferSurface : IFramebufferPlatformSurface + { + private readonly IWriteableBitmapImpl _bitmap; + + public BitmapFramebufferSurface(IWriteableBitmapImpl bitmap) + { + _bitmap = bitmap; } + + public ILockedFramebuffer Lock() => _bitmap.Lock(); } protected void CompareImages([CallerMemberName] string testName = "") diff --git a/tests/Avalonia.Skia.UnitTests/HitTesting.cs b/tests/Avalonia.Skia.UnitTests/HitTesting.cs index dceb0cdb9b..df267ee136 100644 --- a/tests/Avalonia.Skia.UnitTests/HitTesting.cs +++ b/tests/Avalonia.Skia.UnitTests/HitTesting.cs @@ -30,7 +30,7 @@ namespace Avalonia.Skia.UnitTests } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); @@ -64,7 +64,7 @@ namespace Avalonia.Skia.UnitTests } }; - root.Renderer = new DeferredRenderer((IRenderRoot)root, null); + root.Renderer = new DeferredRenderer((IRenderRoot)root, null, root.CreateRenderTarget); root.Measure(Size.Infinity); root.Arrange(new Rect(root.DesiredSize)); diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 0f951ed867..f9e1e45098 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -9,7 +9,7 @@ using Moq; namespace Avalonia.UnitTests { - public class MockPlatformRenderInterface : IPlatformRenderInterface + public class MockPlatformRenderInterface : IPlatformRenderInterface, IPlatformRenderInterfaceContext { public IGeometryImpl CreateEllipseGeometry(Rect rect) { @@ -48,6 +48,8 @@ namespace Avalonia.UnitTests return m.Object; } + + public bool IsCorrupted => false; } public IRenderTarget CreateRenderTarget(IEnumerable surfaces) @@ -55,6 +57,8 @@ namespace Avalonia.UnitTests return new MockRenderTarget(); } + public object TryGetFeature(Type featureType) => null; + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) { return Mock.Of(); @@ -147,6 +151,8 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this; + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { return Mock.Of(); @@ -172,5 +178,8 @@ namespace Avalonia.UnitTests public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888; + public void Dispose() + { + } } } diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs new file mode 100644 index 0000000000..db70f570a2 --- /dev/null +++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs @@ -0,0 +1,53 @@ +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace Avalonia.UnitTests +{ + public class TouchTestHelper + { + private readonly Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Touch, true); + private ulong _nextStamp = 1; + private ulong Timestamp() => _nextStamp++; + public IInputElement Captured => _pointer.Captured; + + public void Down(Interactive target, Point position = default, KeyModifiers modifiers = default) + { + Down(target, target, position, modifiers); + } + + public void Down(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) + { + _pointer.Capture((IInputElement)target); + source.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (Visual)source, position, Timestamp(), PointerPointProperties.None, + modifiers)); + } + + public void Move(Interactive target, in Point position, KeyModifiers modifiers = default) => Move(target, target, position, modifiers); + + public void Move(Interactive target, Interactive source, in Point position, KeyModifiers modifiers = default) + { + target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position, + Timestamp(), PointerPointProperties.None, modifiers)); + } + + public void Up(Interactive target, Point position = default, KeyModifiers modifiers = default) + => Up(target, target, position, modifiers); + + public void Up(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) + { + source.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None, + modifiers, MouseButton.None)); + _pointer.Capture(null); + } + + public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default) + => Tap(target, target, position, modifiers); + + public void Tap(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) + { + Down(target, source, position, modifiers); + Up(target, source, position, modifiers); + } + } +}