From 34d779df45c4a7c03497b015cde51a255c9017aa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 09:49:12 -0600 Subject: [PATCH 01/11] Updated gitignore to remove vs2017 specific files. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a510c4e49f..d16287cfb4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.suo *.user *.sln.docstates +.vs/ # Build results From f44468a3ab6f2abb0d1cb5250ebe835b952dcaa2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 10:47:48 -0600 Subject: [PATCH 02/11] Added swap chain backed render target. --- Avalonia.sln.DotSettings | 19 +++ .../Avalonia.Direct2D1.csproj | 20 +-- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 36 ++++- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 56 ++++++++ .../Media/DrawingContext.cs | 8 +- .../SwapChainRenderTarget.cs | 134 ++++++++++++++++++ src/Windows/Avalonia.Direct2D1/app.config | 4 +- .../Avalonia.Direct2D1/packages.config | 7 +- 8 files changed, 265 insertions(+), 19 deletions(-) create mode 100644 src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs create mode 100644 src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index bf98899847..16c9218a7e 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -2,6 +2,24 @@ ExplicitlyExcluded ExplicitlyExcluded HINT + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="set_" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="_" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="I" Suffix="" Style="AaBb" /> @@ -10,6 +28,7 @@ <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="s_" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index 9f9558ff76..95ccc98692 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -37,17 +37,17 @@ true - - ..\..\..\packages\SharpDX.3.1.0\lib\net45\SharpDX.dll - True + + ..\..\..\packages\SharpDX.3.1.1\lib\net45\SharpDX.dll - - ..\..\..\packages\SharpDX.Direct2D1.3.1.0\lib\net45\SharpDX.Direct2D1.dll - True + + ..\..\..\packages\SharpDX.Direct2D1.3.1.1\lib\net45\SharpDX.Direct2D1.dll - - ..\..\..\packages\SharpDX.DXGI.3.1.0\lib\net45\SharpDX.DXGI.dll - True + + ..\..\..\packages\SharpDX.Direct3D11.3.1.1\lib\net45\SharpDX.Direct3D11.dll + + + ..\..\..\packages\SharpDX.DXGI.3.1.1\lib\net45\SharpDX.DXGI.dll @@ -62,6 +62,7 @@ + @@ -79,6 +80,7 @@ + diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index b43eef2fa9..f86fa0b93a 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -8,6 +8,7 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Controls; using Avalonia.Rendering; +using SharpDX.Direct3D11; namespace Avalonia { @@ -29,20 +30,47 @@ namespace Avalonia.Direct2D1 private static readonly SharpDX.Direct2D1.Factory s_d2D1Factory = #if DEBUG - new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded, SharpDX.Direct2D1.DebugLevel.Error); + new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); #else - new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded, SharpDX.Direct2D1.DebugLevel.None); + new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); #endif private static readonly SharpDX.DirectWrite.Factory s_dwfactory = new SharpDX.DirectWrite.Factory(); private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory(); + private static readonly SharpDX.DXGI.Device s_device; + + static Direct2D1Platform() + { + var featureLevels = new[] + { + SharpDX.Direct3D.FeatureLevel.Level_12_1, + SharpDX.Direct3D.FeatureLevel.Level_12_0, + SharpDX.Direct3D.FeatureLevel.Level_11_1, + SharpDX.Direct3D.FeatureLevel.Level_11_0, + SharpDX.Direct3D.FeatureLevel.Level_10_1, + SharpDX.Direct3D.FeatureLevel.Level_10_0, + SharpDX.Direct3D.FeatureLevel.Level_9_3, + SharpDX.Direct3D.FeatureLevel.Level_9_2, + SharpDX.Direct3D.FeatureLevel.Level_9_1, + }; + + using (var d3dDevice = new SharpDX.Direct3D11.Device( + SharpDX.Direct3D.DriverType.Hardware, + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, + featureLevels)) + { + s_device = d3dDevice.QueryInterface(); + } + } + public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .BindToSelf(s_d2D1Factory) .BindToSelf(s_dwfactory) - .BindToSelf(s_imagingFactory); + .BindToSelf(s_imagingFactory) + .BindToSelf(s_device); public IBitmapImpl CreateBitmap(int width, int height) { @@ -70,7 +98,7 @@ namespace Avalonia.Direct2D1 { if (handle.HandleDescriptor == "HWND") { - return new RenderTarget(handle.Handle); + return new HwndRenderTarget(handle.Handle); } else { diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs new file mode 100644 index 0000000000..5c0c460dcb --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Win32.Interop; +using SharpDX; +using SharpDX.DXGI; + +namespace Avalonia.Direct2D1 +{ + class HwndRenderTarget : SwapChainRenderTarget + { + private readonly IntPtr _hwnd; + + public HwndRenderTarget(IntPtr hwnd) + { + _hwnd = hwnd; + } + + protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) + { + return new SwapChain1(dxgiFactory, Device, _hwnd, ref swapChainDesc); + } + + protected override Size2F GetWindowDpi() + { + if (UnmanagedMethods.ShCoreAvailable) + { + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow( + _hwnd, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size2F(dpix, dpiy); + } + } + + return new Size2F(96, 96); + } + + protected override Size2 GetWindowSize() + { + UnmanagedMethods.RECT rc; + UnmanagedMethods.GetClientRect(_hwnd, out rc); + return new Size2(rc.right - rc.left, rc.bottom - rc.top); + } + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs index 75a0f43d9f..decf3c6fc6 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs @@ -27,6 +27,8 @@ namespace Avalonia.Direct2D1.Media /// private SharpDX.DirectWrite.Factory _directWriteFactory; + private SharpDX.DXGI.SwapChain1 _swapChain; + /// /// Initializes a new instance of the class. /// @@ -34,10 +36,12 @@ namespace Avalonia.Direct2D1.Media /// The DirectWrite factory. public DrawingContext( SharpDX.Direct2D1.RenderTarget renderTarget, - SharpDX.DirectWrite.Factory directWriteFactory) + SharpDX.DirectWrite.Factory directWriteFactory, + SharpDX.DXGI.SwapChain1 swapChain = null) { _renderTarget = renderTarget; _directWriteFactory = directWriteFactory; + _swapChain = swapChain; _renderTarget.BeginDraw(); } @@ -60,6 +64,8 @@ namespace Avalonia.Direct2D1.Media try { _renderTarget.EndDraw(); + + _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None); } catch (SharpDXException ex) when((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET { diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs new file mode 100644 index 0000000000..a7b2f532c2 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Win32.Interop; +using SharpDX; +using SharpDX.Direct2D1; +using SharpDX.DXGI; +using AlphaMode = SharpDX.Direct2D1.AlphaMode; +using Device = SharpDX.Direct2D1.Device; +using Factory = SharpDX.Direct2D1.Factory; +using Factory2 = SharpDX.DXGI.Factory2; + +namespace Avalonia.Direct2D1 +{ + public abstract class SwapChainRenderTarget : IRenderTarget + { + private Size2 _savedSize; + private Size2F _savedDpi; + private DeviceContext _deviceContext; + private SwapChain1 _swapChain; + + protected SwapChainRenderTarget() + { + Device = AvaloniaLocator.Current.GetService(); + Direct2DFactory = AvaloniaLocator.Current.GetService(); + DirectWriteFactory = AvaloniaLocator.Current.GetService(); + } + + + /// + /// Gets the Direct2D factory. + /// + public Factory Direct2DFactory + { + get; + } + + /// + /// Gets the DirectWrite factory. + /// + public SharpDX.DirectWrite.Factory DirectWriteFactory + { + get; + } + + protected SharpDX.DXGI.Device Device { get; } + + /// + /// Creates a drawing context for a rendering session. + /// + /// An . + public DrawingContext CreateDrawingContext() + { + var size = GetWindowSize(); + var dpi = GetWindowDpi(); + + if (size != _savedSize || dpi != _savedDpi) + { + _savedSize = size; + _savedDpi = dpi; + CreateSwapChain(); + } + + return new DrawingContext(new Media.DrawingContext(_deviceContext, DirectWriteFactory, _swapChain)); + } + + public void Dispose() + { + _deviceContext.Dispose(); + _swapChain.Dispose(); + } + + private void CreateSwapChain() + { + using (var d2dDevice = new Device(Device)) + using (var dxgiAdaptor = Device.Adapter) + using (var dxgiFactory = dxgiAdaptor.GetParent()) + { + _deviceContext?.Dispose(); + _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None); + + var swapChainDesc = new SwapChainDescription1 + { + Width = _savedSize.Width, + Height = _savedSize.Height, + Format = Format.B8G8R8A8_UNorm, + Stereo = false, + SampleDescription = new SampleDescription + { + Count = 1, + Quality = 0, + }, + Usage = Usage.RenderTargetOutput, + BufferCount = 2, + Scaling = Scaling.None, + SwapEffect = SwapEffect.FlipSequential, + Flags = 0, + }; + + var dpi = Direct2DFactory.DesktopDpi; + + _swapChain?.Dispose(); + _swapChain = CreateSwapChain(dxgiFactory, swapChainDesc); + + using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) + using (var d2dBackBuffer = new Bitmap1( + _deviceContext, + dxgiBackBuffer, + new BitmapProperties1( + new PixelFormat + { + AlphaMode = AlphaMode.Ignore, + Format = Format.B8G8R8A8_UNorm + }, + _savedDpi.Width, + _savedDpi.Height, + BitmapOptions.Target | BitmapOptions.CannotDraw))) + { + _deviceContext.Target = d2dBackBuffer; + } + } + } + + protected abstract SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc); + + protected abstract Size2F GetWindowDpi(); + + protected abstract Size2 GetWindowSize(); + } +} diff --git a/src/Windows/Avalonia.Direct2D1/app.config b/src/Windows/Avalonia.Direct2D1/app.config index 743de168f3..60a1012655 100644 --- a/src/Windows/Avalonia.Direct2D1/app.config +++ b/src/Windows/Avalonia.Direct2D1/app.config @@ -8,11 +8,11 @@ - + - + diff --git a/src/Windows/Avalonia.Direct2D1/packages.config b/src/Windows/Avalonia.Direct2D1/packages.config index 57031c2b9d..780e6014e5 100644 --- a/src/Windows/Avalonia.Direct2D1/packages.config +++ b/src/Windows/Avalonia.Direct2D1/packages.config @@ -1,6 +1,7 @@  - - - + + + + \ No newline at end of file From cc4c3d02d0fcb61b74843b8391597ac2f2058d20 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 11:08:12 -0600 Subject: [PATCH 03/11] Fixed DPI issues. --- src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs | 1 + src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs index decf3c6fc6..486116c27b 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContext.cs @@ -34,6 +34,7 @@ namespace Avalonia.Direct2D1.Media /// /// The render target to draw to. /// The DirectWrite factory. + /// An optional swap chain associated with this drawing context. public DrawingContext( SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.DirectWrite.Factory directWriteFactory, diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index a7b2f532c2..2fbf65ed15 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -81,7 +81,8 @@ namespace Avalonia.Direct2D1 using (var dxgiFactory = dxgiAdaptor.GetParent()) { _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None); + _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; + var swapChainDesc = new SwapChainDescription1 { @@ -101,8 +102,6 @@ namespace Avalonia.Direct2D1 Flags = 0, }; - var dpi = Direct2DFactory.DesktopDpi; - _swapChain?.Dispose(); _swapChain = CreateSwapChain(dxgiFactory, swapChainDesc); From bde461f4005458cb69d266ddaa63cd4371fff1e6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 11:30:36 -0600 Subject: [PATCH 04/11] Use the factory associated with the Direct2D1 device. --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 21 +++++++++---------- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 2 +- .../SwapChainRenderTarget.cs | 12 ++++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index f86fa0b93a..c1ce7ce6f8 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -28,17 +28,13 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); - private static readonly SharpDX.Direct2D1.Factory s_d2D1Factory = -#if DEBUG - new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); -#else - new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); -#endif private static readonly SharpDX.DirectWrite.Factory s_dwfactory = new SharpDX.DirectWrite.Factory(); private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory(); - private static readonly SharpDX.DXGI.Device s_device; + private static readonly SharpDX.DXGI.Device s_dxgiDevice; + + private static readonly SharpDX.Direct2D1.Device s_d2d1Device; static Direct2D1Platform() { @@ -60,17 +56,20 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, featureLevels)) { - s_device = d3dDevice.QueryInterface(); + s_dxgiDevice = d3dDevice.QueryInterface(); } + + s_d2d1Device = new SharpDX.Direct2D1.Device(s_dxgiDevice); } public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .BindToSelf(s_d2D1Factory) + .BindToSelf(s_d2d1Device.Factory) .BindToSelf(s_dwfactory) .BindToSelf(s_imagingFactory) - .BindToSelf(s_device); + .BindToSelf(s_dxgiDevice) + .BindToSelf(s_d2d1Device); public IBitmapImpl CreateBitmap(int width, int height) { @@ -110,7 +109,7 @@ namespace Avalonia.Direct2D1 public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) { - return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Factory, width, height); + return new RenderTargetBitmapImpl(s_imagingFactory, s_d2d1Device.Factory, width, height); } public IStreamGeometryImpl CreateStreamGeometry() diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 5c0c460dcb..49d4c91c52 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -20,7 +20,7 @@ namespace Avalonia.Direct2D1 protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) { - return new SwapChain1(dxgiFactory, Device, _hwnd, ref swapChainDesc); + return new SwapChain1(dxgiFactory, DxgiDevice, _hwnd, ref swapChainDesc); } protected override Size2F GetWindowDpi() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 2fbf65ed15..0d3799f1b1 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -25,7 +25,8 @@ namespace Avalonia.Direct2D1 protected SwapChainRenderTarget() { - Device = AvaloniaLocator.Current.GetService(); + DxgiDevice = AvaloniaLocator.Current.GetService(); + D2DDevice = AvaloniaLocator.Current.GetService(); Direct2DFactory = AvaloniaLocator.Current.GetService(); DirectWriteFactory = AvaloniaLocator.Current.GetService(); } @@ -47,7 +48,9 @@ namespace Avalonia.Direct2D1 get; } - protected SharpDX.DXGI.Device Device { get; } + protected SharpDX.DXGI.Device DxgiDevice { get; } + + public Device D2DDevice { get; } /// /// Creates a drawing context for a rendering session. @@ -76,12 +79,11 @@ namespace Avalonia.Direct2D1 private void CreateSwapChain() { - using (var d2dDevice = new Device(Device)) - using (var dxgiAdaptor = Device.Adapter) + using (var dxgiAdaptor = DxgiDevice.Adapter) using (var dxgiFactory = dxgiAdaptor.GetParent()) { _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(d2dDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; + _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; var swapChainDesc = new SwapChainDescription1 From 4575c1abc9841f7ebf3bf5e3eb69d90f2b30b619 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 11:38:20 -0600 Subject: [PATCH 05/11] Updated Resharper naming rules to match our conventions. --- Avalonia.sln.DotSettings | 2 +- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index 16c9218a7e..ab21d6e50b 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -30,7 +30,7 @@ <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="s_" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index c1ce7ce6f8..6d6e1a1149 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -34,7 +34,7 @@ namespace Avalonia.Direct2D1 private static readonly SharpDX.DXGI.Device s_dxgiDevice; - private static readonly SharpDX.Direct2D1.Device s_d2d1Device; + private static readonly SharpDX.Direct2D1.Device s_d2D1Device; static Direct2D1Platform() { @@ -59,17 +59,17 @@ namespace Avalonia.Direct2D1 s_dxgiDevice = d3dDevice.QueryInterface(); } - s_d2d1Device = new SharpDX.Direct2D1.Device(s_dxgiDevice); + s_d2D1Device = new SharpDX.Direct2D1.Device(s_dxgiDevice); } public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .BindToSelf(s_d2d1Device.Factory) + .BindToSelf(s_d2D1Device.Factory) .BindToSelf(s_dwfactory) .BindToSelf(s_imagingFactory) .BindToSelf(s_dxgiDevice) - .BindToSelf(s_d2d1Device); + .BindToSelf(s_d2D1Device); public IBitmapImpl CreateBitmap(int width, int height) { @@ -109,7 +109,7 @@ namespace Avalonia.Direct2D1 public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) { - return new RenderTargetBitmapImpl(s_imagingFactory, s_d2d1Device.Factory, width, height); + return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Device.Factory, width, height); } public IStreamGeometryImpl CreateStreamGeometry() From d02b7cbe9285e3056c3a94852ebc832e8c76a447 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 11:56:20 -0600 Subject: [PATCH 06/11] Create D2D1 device with a factory. --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 14 +++- .../Avalonia.Direct2D1/RenderTarget.cs | 79 ------------------- .../SwapChainRenderTarget.cs | 4 +- 3 files changed, 13 insertions(+), 84 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 6d6e1a1149..f4515a3814 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -8,7 +8,6 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Controls; using Avalonia.Rendering; -using SharpDX.Direct3D11; namespace Avalonia { @@ -28,6 +27,12 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); + private static readonly SharpDX.Direct2D1.Factory s_d2D1Factory = +#if DEBUG + new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); +#else + new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); +#endif private static readonly SharpDX.DirectWrite.Factory s_dwfactory = new SharpDX.DirectWrite.Factory(); private static readonly SharpDX.WIC.ImagingFactory s_imagingFactory = new SharpDX.WIC.ImagingFactory(); @@ -59,13 +64,16 @@ namespace Avalonia.Direct2D1 s_dxgiDevice = d3dDevice.QueryInterface(); } - s_d2D1Device = new SharpDX.Direct2D1.Device(s_dxgiDevice); + using (var factory1 = s_d2D1Factory.QueryInterface()) + { + s_d2D1Device = new SharpDX.Direct2D1.Device(factory1, s_dxgiDevice); + } } public static void Initialize() => AvaloniaLocator.CurrentMutable .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .BindToSelf(s_d2D1Device.Factory) + .BindToSelf(s_d2D1Factory) .BindToSelf(s_dwfactory) .BindToSelf(s_imagingFactory) .BindToSelf(s_dxgiDevice) diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index 180e1a7472..52146d77c1 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -13,42 +13,11 @@ namespace Avalonia.Direct2D1 { public class RenderTarget : IRenderTarget { - private readonly IntPtr _hwnd; - private Size2 _savedSize; - private Size2F _savedDpi; - /// /// The render target. /// private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; - /// - /// Initializes a new instance of the class. - /// - /// The window handle. - public RenderTarget(IntPtr hwnd) - { - _hwnd = hwnd; - Direct2DFactory = AvaloniaLocator.Current.GetService(); - DirectWriteFactory = AvaloniaLocator.Current.GetService(); - - RenderTargetProperties renderTargetProperties = new RenderTargetProperties - { - }; - - HwndRenderTargetProperties hwndProperties = new HwndRenderTargetProperties - { - Hwnd = hwnd, - PixelSize = _savedSize = GetWindowSize(), - PresentOptions = PresentOptions.Immediately, - }; - - _renderTarget = new WindowRenderTarget( - Direct2DFactory, - renderTargetProperties, - hwndProperties); - } - /// /// Initializes a new instance of the class. /// @@ -82,24 +51,6 @@ namespace Avalonia.Direct2D1 /// An . public DrawingContext CreateDrawingContext() { - var window = _renderTarget as WindowRenderTarget; - - if (window != null) - { - var size = GetWindowSize(); - var dpi = GetWindowDpi(); - - if (size != _savedSize) - { - window.Resize(_savedSize = size); - } - - if (dpi != _savedDpi) - { - window.DotsPerInch = _savedDpi = dpi; - } - } - return new DrawingContext(new Media.DrawingContext(_renderTarget, DirectWriteFactory)); } @@ -107,35 +58,5 @@ namespace Avalonia.Direct2D1 { _renderTarget.Dispose(); } - - private Size2F GetWindowDpi() - { - if (UnmanagedMethods.ShCoreAvailable) - { - uint dpix, dpiy; - - var monitor = UnmanagedMethods.MonitorFromWindow( - _hwnd, - UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); - - if (UnmanagedMethods.GetDpiForMonitor( - monitor, - UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, - out dpix, - out dpiy) == 0) - { - return new Size2F(dpix, dpiy); - } - } - - return new Size2F(96, 96); - } - - private Size2 GetWindowSize() - { - UnmanagedMethods.RECT rc; - UnmanagedMethods.GetClientRect(_hwnd, out rc); - return new Size2(rc.right - rc.left, rc.bottom - rc.top); - } } } diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 0d3799f1b1..8362305b9f 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -73,8 +73,8 @@ namespace Avalonia.Direct2D1 public void Dispose() { - _deviceContext.Dispose(); - _swapChain.Dispose(); + _deviceContext?.Dispose(); + _swapChain?.Dispose(); } private void CreateSwapChain() From 1a96efa8af20cc9884667cf2514eff42deb71f0a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 12:45:47 -0600 Subject: [PATCH 07/11] Update implementation and added test case to control catalog. Fixes #807. --- samples/ControlCatalog/Pages/ImagePage.xaml | 6 ++++- .../ControlCatalog/Pages/ImagePage.xaml.cs | 23 +++++++++++++++++++ .../Avalonia.Android/PlatformIconLoader.cs | 6 ++--- .../Platform/IWindowIconImpl.cs | 2 +- src/Avalonia.Controls/WindowIcon.cs | 2 +- src/Gtk/Avalonia.Gtk/IconImpl.cs | 5 ++-- src/Windows/Avalonia.Win32/IconImpl.cs | 8 +++---- src/iOS/Avalonia.iOS/PlatformIconLoader.cs | 8 +++---- 8 files changed, 41 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml index 1aaedc4420..dc93808f27 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml +++ b/samples/ControlCatalog/Pages/ImagePage.xaml @@ -34,6 +34,10 @@ Width="100" Height="200" Stretch="UniformToFill"/> - + + + Window Icon as an Image + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs index cc35b4d237..792b25963e 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs +++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs @@ -1,10 +1,14 @@ +using System.IO; +using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; namespace ControlCatalog.Pages { public class ImagePage : UserControl { + private Image iconImage; public ImagePage() { this.InitializeComponent(); @@ -13,6 +17,25 @@ namespace ControlCatalog.Pages private void InitializeComponent() { AvaloniaXamlLoader.Load(this); + iconImage = this.Get("Icon"); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + if (iconImage.Source == null) + { + var windowRoot = e.Root as Window; + if (windowRoot != null) + { + using (var stream = new MemoryStream()) + { + windowRoot.Icon.Save(stream); + stream.Seek(0, SeekOrigin.Begin); + iconImage.Source = new Bitmap(stream); + } + } + } } } } diff --git a/src/Android/Avalonia.Android/PlatformIconLoader.cs b/src/Android/Avalonia.Android/PlatformIconLoader.cs index 11fc5583a5..289025ce1d 100644 --- a/src/Android/Avalonia.Android/PlatformIconLoader.cs +++ b/src/Android/Avalonia.Android/PlatformIconLoader.cs @@ -49,11 +49,9 @@ namespace Avalonia.Android stream.CopyTo(this.stream); } - public Stream Save() + public void Save(Stream outputStream) { - var returnStream = new MemoryStream(); - stream.CopyTo(returnStream); - return returnStream; + stream.CopyTo(outputStream); } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Platform/IWindowIconImpl.cs b/src/Avalonia.Controls/Platform/IWindowIconImpl.cs index cd339ff404..d106e2a616 100644 --- a/src/Avalonia.Controls/Platform/IWindowIconImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowIconImpl.cs @@ -7,6 +7,6 @@ namespace Avalonia.Platform { public interface IWindowIconImpl { - Stream Save(); + void Save(Stream outputStream); } } diff --git a/src/Avalonia.Controls/WindowIcon.cs b/src/Avalonia.Controls/WindowIcon.cs index debac7c981..dff84ff6ef 100644 --- a/src/Avalonia.Controls/WindowIcon.cs +++ b/src/Avalonia.Controls/WindowIcon.cs @@ -31,6 +31,6 @@ namespace Avalonia.Controls public IWindowIconImpl PlatformImpl { get; } - public Stream Save() => PlatformImpl.Save(); + public void Save(Stream stream) => PlatformImpl.Save(stream); } } diff --git a/src/Gtk/Avalonia.Gtk/IconImpl.cs b/src/Gtk/Avalonia.Gtk/IconImpl.cs index a3cf3be47a..3203e59f21 100644 --- a/src/Gtk/Avalonia.Gtk/IconImpl.cs +++ b/src/Gtk/Avalonia.Gtk/IconImpl.cs @@ -18,9 +18,10 @@ namespace Avalonia.Gtk public Pixbuf Pixbuf { get; } - public Stream Save() + public void Save(Stream stream) { - return new MemoryStream(Pixbuf.SaveToBuffer("png")); + var buffer = Pixbuf.SaveToBuffer("png"); + stream.Write(buffer, 0, buffer.Length); } } } diff --git a/src/Windows/Avalonia.Win32/IconImpl.cs b/src/Windows/Avalonia.Win32/IconImpl.cs index b9e3378b6f..b7293397d7 100644 --- a/src/Windows/Avalonia.Win32/IconImpl.cs +++ b/src/Windows/Avalonia.Win32/IconImpl.cs @@ -27,18 +27,16 @@ namespace Avalonia.Win32 public IntPtr HIcon => icon?.Handle ?? bitmap.GetHicon(); - public Stream Save() + public void Save(Stream outputStream) { - var stream = new MemoryStream(); if (icon != null) { - icon.Save(stream); + icon.Save(outputStream); } else { - bitmap.Save(stream, ImageFormat.Png); + bitmap.Save(outputStream, ImageFormat.Png); } - return stream; } } } diff --git a/src/iOS/Avalonia.iOS/PlatformIconLoader.cs b/src/iOS/Avalonia.iOS/PlatformIconLoader.cs index dc9d87660f..ca54a660b3 100644 --- a/src/iOS/Avalonia.iOS/PlatformIconLoader.cs +++ b/src/iOS/Avalonia.iOS/PlatformIconLoader.cs @@ -31,18 +31,16 @@ namespace Avalonia.iOS // Stores the icon created as a stream to support saving even though an icon is never shown public class FakeIcon : IWindowIconImpl { - private Stream stream = new MemoryStream(); + private readonly Stream stream = new MemoryStream(); public FakeIcon(Stream stream) { stream.CopyTo(this.stream); } - public Stream Save() + public void Save(Stream outputStream) { - var returnStream = new MemoryStream(); - stream.CopyTo(returnStream); - return returnStream; + stream.CopyTo(outputStream); } } } \ No newline at end of file From e2ba8fb5bb0b25e61d8774d5461e3b124969b54b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 13:15:13 -0600 Subject: [PATCH 08/11] Make video support creation flag only be set when running on Windows 8 or newer (Direct3D11.1 or newer). --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index f4515a3814..69e0811b5e 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -55,10 +55,16 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D.FeatureLevel.Level_9_2, SharpDX.Direct3D.FeatureLevel.Level_9_1, }; + var creationFlags = SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport; + var osVersion = Environment.OSVersion.Version; + if (osVersion.Major > 6 || (osVersion.Major == 6 && osVersion.Minor >= 2)) // If Windows 8 or newer + { + creationFlags |= SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport; + } using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, + creationFlags, featureLevels)) { s_dxgiDevice = d3dDevice.QueryInterface(); From 9d73868da75d1ad53844814066ec15dab4cbca7c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 13:29:54 -0600 Subject: [PATCH 09/11] Remove the VideoSupport flag because AppVeyor machines do not support it. --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 69e0811b5e..49a5130282 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -55,16 +55,10 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D.FeatureLevel.Level_9_2, SharpDX.Direct3D.FeatureLevel.Level_9_1, }; - var creationFlags = SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport; - var osVersion = Environment.OSVersion.Version; - if (osVersion.Major > 6 || (osVersion.Major == 6 && osVersion.Minor >= 2)) // If Windows 8 or newer - { - creationFlags |= SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport; - } using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - creationFlags, + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport, featureLevels)) { s_dxgiDevice = d3dDevice.QueryInterface(); From 619e64ef1d7c4f4a3c08c43ddda910861addd44b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 15:28:17 -0600 Subject: [PATCH 10/11] Remove DirectX 12 feature level choices. Device will be created with feature levels up to DirectX 11.1 (Windows 8), which is all we use. --- src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 49a5130282..a073407a6c 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -45,8 +45,6 @@ namespace Avalonia.Direct2D1 { var featureLevels = new[] { - SharpDX.Direct3D.FeatureLevel.Level_12_1, - SharpDX.Direct3D.FeatureLevel.Level_12_0, SharpDX.Direct3D.FeatureLevel.Level_11_1, SharpDX.Direct3D.FeatureLevel.Level_11_0, SharpDX.Direct3D.FeatureLevel.Level_10_1, @@ -58,7 +56,7 @@ namespace Avalonia.Direct2D1 using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport, + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, featureLevels)) { s_dxgiDevice = d3dDevice.QueryInterface(); From d73350e0fd1f307635abc5651c91f3df799ccdf1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 11 Jan 2017 16:07:22 -0600 Subject: [PATCH 11/11] Fixes intermittent test failures on AppVeyor when running Leak Tests. --- tests/Avalonia.LeakTests/AvaloniaObjectTests.cs | 1 + tests/Avalonia.LeakTests/MemberSelectorTests.cs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs index 8410c2aa3e..54f9a87f94 100644 --- a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs +++ b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs @@ -6,6 +6,7 @@ using Xunit.Abstractions; namespace Avalonia.LeakTests { + [DotMemoryUnit(FailIfRunWithoutSupport = false)] public class AvaloniaObjectTests { public AvaloniaObjectTests(ITestOutputHelper atr) diff --git a/tests/Avalonia.LeakTests/MemberSelectorTests.cs b/tests/Avalonia.LeakTests/MemberSelectorTests.cs index d794e788fd..ffee18ae0a 100644 --- a/tests/Avalonia.LeakTests/MemberSelectorTests.cs +++ b/tests/Avalonia.LeakTests/MemberSelectorTests.cs @@ -4,12 +4,20 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using JetBrains.dotMemoryUnit; using Xunit; +using Xunit.Abstractions; namespace Avalonia.LeakTests { + [DotMemoryUnit(FailIfRunWithoutSupport = false)] public class MemberSelectorTests { + public MemberSelectorTests(ITestOutputHelper atr) + { + DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine); + } + [Fact] public void Should_Not_Hold_Reference_To_Object() {