From da523ffcaff11d3ad50fc7faf57349d634f96999 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 25 Oct 2023 16:43:02 -0700 Subject: [PATCH] Implement DirectComposition support, as well as return Win8+ window transparency (#13382) * Init DirectComposition support * Use Win8.1 as a minimum dcomp target, avoid hacks with render timer * Refactor transparency supprot a little * Cleanups * Restore old hack as I don't know how to remove it yet --- .../Avalonia.Win32/Avalonia.Win32.csproj | 1 + .../DComposition/DirectCompositedWindow.cs | 54 ++++ .../DirectCompositedWindowSurface.cs | 173 +++++++++++++ .../DirectCompositionConnection.cs | 129 ++++++++++ .../DComposition/DirectCompositionShared.cs | 24 ++ .../DComposition/NativeMethods.cs | 14 ++ .../DComposition/NativeStructs.cs | 21 ++ .../Avalonia.Win32/DComposition/dcomp.idl | 164 ++++++++++++ .../Avalonia.Win32/DirectX/DxgiConnection.cs | 8 +- src/Windows/Avalonia.Win32/IBlurHost.cs | 17 ++ .../Avalonia.Win32/IWindowsSurfaceFactory.cs | 11 + .../Interop/UnmanagedMethods.cs | 31 +-- .../Avalonia.Win32/PlatformConstants.cs | 1 + src/Windows/Avalonia.Win32/Win32GlManager.cs | 8 + .../Avalonia.Win32/Win32PlatformOptions.cs | 20 +- .../WinUiCompositedWindowSurface.cs | 11 +- .../Composition/WinUiCompositorConnection.cs | 7 +- src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs | 15 -- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 233 ++++++++---------- 20 files changed, 752 insertions(+), 192 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindow.cs create mode 100644 src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs create mode 100644 src/Windows/Avalonia.Win32/DComposition/DirectCompositionConnection.cs create mode 100644 src/Windows/Avalonia.Win32/DComposition/DirectCompositionShared.cs create mode 100644 src/Windows/Avalonia.Win32/DComposition/NativeMethods.cs create mode 100644 src/Windows/Avalonia.Win32/DComposition/NativeStructs.cs create mode 100644 src/Windows/Avalonia.Win32/DComposition/dcomp.idl create mode 100644 src/Windows/Avalonia.Win32/IBlurHost.cs create mode 100644 src/Windows/Avalonia.Win32/IWindowsSurfaceFactory.cs delete mode 100644 src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 97f0d38545..da37ec1314 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindow.cs b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindow.cs new file mode 100644 index 0000000000..8935e8bcbf --- /dev/null +++ b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindow.cs @@ -0,0 +1,54 @@ +using System; +using System.Numerics; +using System.Threading; +using Avalonia.OpenGL.Egl; +using Avalonia.Reactive; +using MicroCom.Runtime; + +namespace Avalonia.Win32.DComposition; + +internal class DirectCompositedWindow : IDisposable +{ + private readonly DirectCompositionShared _shared; + public EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; } + private readonly IDCompositionVisual _container; + private readonly IDCompositionTarget _target; + private readonly IDCompositionDevice2 _device; + + public void Dispose() + { + lock (_shared.SyncRoot) + { + _container.Dispose(); + _target.Dispose(); + _device.Dispose(); + } + } + + public DirectCompositedWindow(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info, DirectCompositionShared shared) + { + WindowInfo = info; + _shared = shared; + _device = shared.Device.CloneReference(); + + using var desktopTarget = shared.Device.CreateTargetForHwnd(WindowInfo.Handle, false); + _target = desktopTarget.QueryInterface(); + + using var container = shared.Device.CreateVisual(); + _container = container.CloneReference(); + + _target.SetRoot(container); + } + + public void SetSurface(IDCompositionSurface surface) => _container.SetContent(surface); + + public IDisposable BeginTransaction() + { + Monitor.Enter(_shared.SyncRoot); + return Disposable.Create(() => + { + _device.Commit(); + Monitor.Exit(_shared.SyncRoot); + }); + } +} diff --git a/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs new file mode 100644 index 0000000000..d0651e08ee --- /dev/null +++ b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs @@ -0,0 +1,173 @@ +using System; +using System.ComponentModel; +using Avalonia.OpenGL.Egl; +using Avalonia.Platform; +using Avalonia.Win32.DirectX; +using Avalonia.Win32.Interop; +using MicroCom.Runtime; + +namespace Avalonia.Win32.DComposition; + +internal class DirectCompositedWindowSurface : IDirect3D11TexturePlatformSurface, IDisposable, ICompositionEffectsSurface +{ + private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; + private readonly DirectCompositionShared _shared; + private DirectCompositedWindow? _window; + private BlurEffect _blurEffect; + + public DirectCompositedWindowSurface(DirectCompositionShared shared, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) + { + _shared = shared; + _info = info; + } + + public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice) + { + _window ??= new DirectCompositedWindow(_info, _shared); + SetBlur(_blurEffect); + + return new DirectCompositedWindowRenderTarget(context, d3dDevice, _shared, _window); + } + + public void Dispose() + { + _window?.Dispose(); + _window = null; + } + + // TODO: we can implement BlurEffect.GaussianBlur in with IDCompositionDevice3.CreateGaussianBlurEffect. + public bool IsBlurSupported(BlurEffect effect) => effect == BlurEffect.None; + + public void SetBlur(BlurEffect enable) + { + _blurEffect = enable; + // _window?.SetBlur(enable); + } +} + +internal class DirectCompositedWindowRenderTarget : IDirect3D11TextureRenderTarget +{ + private static readonly Guid IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); + + private readonly IPlatformGraphicsContext _context; + private readonly DirectCompositedWindow _window; + private readonly IDCompositionVirtualSurface _surface; + private bool _lost; + private PixelSize _size; + private readonly IUnknown _d3dDevice; + + public DirectCompositedWindowRenderTarget( + IPlatformGraphicsContext context, IntPtr d3dDevice, + DirectCompositionShared shared, DirectCompositedWindow window) + { + _d3dDevice = MicroComRuntime.CreateProxyFor(d3dDevice, false).CloneReference(); + + _context = context; + _window = window; + + using (var surfaceFactory = shared.Device.CreateSurfaceFactory(_d3dDevice)) + { + _surface = surfaceFactory.CreateVirtualSurface(1, 1, DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, + DXGI_ALPHA_MODE.DXGI_ALPHA_MODE_PREMULTIPLIED); + } + } + + public void Dispose() + { + _surface.Dispose(); + _d3dDevice.Dispose(); + } + + public bool IsCorrupted => _context.IsLost || _lost; + + public unsafe IDirect3D11TextureRenderTargetRenderSession BeginDraw() + { + if (IsCorrupted) + throw new RenderTargetCorruptedException(); + var transaction = _window.BeginTransaction(); + bool needsEndDraw = false; + try + { + var size = _window.WindowInfo.Size; + var scale = _window.WindowInfo.Scaling; + if (_size != size) + { + _surface.Resize((ushort)size.Width, (ushort)size.Height); + _size = size; + } + + _window.SetSurface(_surface); + + void* pTexture; + UnmanagedMethods.POINT off; + try + { + var rect = new UnmanagedMethods.RECT { right = size.Width, bottom = size.Height }; + var iid = IID_ID3D11Texture2D; + off = _surface.BeginDraw(&rect, &iid, &pTexture); + } + catch (Exception e) + { + _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(_surface, texture, transaction, size, offset, scale); + transaction = null; + return session; + } + finally + { + if (transaction != null) + { + if (needsEndDraw) + _surface.EndDraw(); + transaction.Dispose(); + } + } + } + + private class Session : IDirect3D11TextureRenderTargetRenderSession + { + private readonly IDisposable _transaction; + private readonly PixelSize _size; + private readonly PixelPoint _offset; + private readonly double _scaling; + private readonly IDCompositionSurface _surfaceInterop; + private readonly IUnknown _texture; + + public Session(IDCompositionSurface surfaceInterop, IUnknown texture, IDisposable transaction, + PixelSize size, PixelPoint offset, double scaling) + { + _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; + } +} diff --git a/src/Windows/Avalonia.Win32/DComposition/DirectCompositionConnection.cs b/src/Windows/Avalonia.Win32/DComposition/DirectCompositionConnection.cs new file mode 100644 index 0000000000..df940a8035 --- /dev/null +++ b/src/Windows/Avalonia.Win32/DComposition/DirectCompositionConnection.cs @@ -0,0 +1,129 @@ +using System; +using System.ComponentModel; +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.Interop; +using Avalonia.Win32.WinRT; +using Avalonia.Win32.WinRT.Composition; +using MicroCom.Runtime; + +namespace Avalonia.Win32.DComposition; + +internal class DirectCompositionConnection : IRenderTimer, IWindowsSurfaceFactory +{ + private static readonly Guid IID_IDCompositionDesktopDevice = Guid.Parse("5f4633fe-1e08-4cb8-8c75-ce24333f5602"); + + public event Action? Tick; + public bool RunsInBackground => true; + + private readonly DirectCompositionShared _shared; + + public DirectCompositionConnection(DirectCompositionShared shared) + { + _shared = shared; + } + + private static bool TryCreateAndRegisterCore() + { + var tcs = new TaskCompletionSource(); + var th = new Thread(() => + { + DirectCompositionConnection connect; + try + { + var result = NativeMethods.DCompositionCreateDevice2(default, IID_IDCompositionDesktopDevice, out var cDevice); + if (result != UnmanagedMethods.HRESULT.S_OK) + { + throw new Win32Exception((int)result); + } + + using (var device = MicroComRuntime.CreateProxyFor(cDevice, false)) + { + var shared = new DirectCompositionShared(device); + connect = new DirectCompositionConnection(shared); + } + + AvaloniaLocator.CurrentMutable.Bind().ToConstant(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; + } + + private void RunLoop() + { + var cts = new CancellationTokenSource(); + AppDomain.CurrentDomain.ProcessExit += (_, _) => + cts.Cancel(); + + var _stopwatch = Stopwatch.StartNew(); + var device = _shared.Device.CloneReference(); + + while (!cts.IsCancellationRequested) + { + try + { + device.WaitForCommitCompletion(); + Tick?.Invoke(_stopwatch.Elapsed); + } + catch (Exception ex) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Win32Platform) + ?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}"); + } + } + + device?.Dispose(); + } + + public static bool IsSupported() + { + return Win32Platform.WindowsVersion >= PlatformConstants.Windows8_1; + } + + public static bool TryCreateAndRegister() + { + if (IsSupported()) + { + try + { + TryCreateAndRegisterCore(); + return true; + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Win32Platform) + ?.Log(null, "Unable to initialize WinUI compositor: {0}", e); + } + } + else + { + var osVersionNotice = + $"Windows {PlatformConstants.Windows8_1} is required. Your machine has Windows {Win32Platform.WindowsVersion} installed."; + + Logger.TryGet(LogEventLevel.Warning, LogArea.Win32Platform)?.Log(null, + $"Unable to initialize WinUI compositor: {osVersionNotice}"); + } + + return false; + } + + public bool RequiresNoRedirectionBitmap => true; + public object CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new DirectCompositedWindowSurface(_shared, info); +} diff --git a/src/Windows/Avalonia.Win32/DComposition/DirectCompositionShared.cs b/src/Windows/Avalonia.Win32/DComposition/DirectCompositionShared.cs new file mode 100644 index 0000000000..6b3a5c85fb --- /dev/null +++ b/src/Windows/Avalonia.Win32/DComposition/DirectCompositionShared.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; +using System.Threading; +using Avalonia.Win32.Interop; +using MicroCom.Runtime; + +namespace Avalonia.Win32.DComposition; + +internal class DirectCompositionShared : IDisposable +{ + public object SyncRoot { get; } = new(); + + public DirectCompositionShared(IDCompositionDesktopDevice device) + { + Device = device.CloneReference(); + } + + public IDCompositionDesktopDevice Device { get; } + + public void Dispose() + { + Device.Dispose(); + } +} diff --git a/src/Windows/Avalonia.Win32/DComposition/NativeMethods.cs b/src/Windows/Avalonia.Win32/DComposition/NativeMethods.cs new file mode 100644 index 0000000000..7a3549c042 --- /dev/null +++ b/src/Windows/Avalonia.Win32/DComposition/NativeMethods.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Win32.DirectX; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32.DComposition; + +internal class NativeMethods +{ + [DllImport("dcomp", ExactSpelling = true)] + public static extern UnmanagedMethods.HRESULT DCompositionCreateDevice2( /* _In_opt_ */ + IntPtr renderingDevice, /* _In_ */ + [MarshalAs(UnmanagedType.LPStruct)] Guid iid, /* _Outptr_ */ out IntPtr dcompositionDevice); +} diff --git a/src/Windows/Avalonia.Win32/DComposition/NativeStructs.cs b/src/Windows/Avalonia.Win32/DComposition/NativeStructs.cs new file mode 100644 index 0000000000..98ed64f0a2 --- /dev/null +++ b/src/Windows/Avalonia.Win32/DComposition/NativeStructs.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; +using Avalonia.Win32.DirectX; + +namespace Avalonia.Win32.DComposition; + +[StructLayout(LayoutKind.Sequential)] +public struct DXGI_RATIONAL +{ + public uint Numerator; + public uint Denominator; +} + +[StructLayout(LayoutKind.Sequential)] +public struct DCOMPOSITION_FRAME_STATISTICS +{ + public long lastFrameTime; + public DXGI_RATIONAL currentCompositionRate; + public long currentTime; + public long timeFrequency; + public long nextEstimatedFrameTime; +} diff --git a/src/Windows/Avalonia.Win32/DComposition/dcomp.idl b/src/Windows/Avalonia.Win32/DComposition/dcomp.idl new file mode 100644 index 0000000000..ebc0fba608 --- /dev/null +++ b/src/Windows/Avalonia.Win32/DComposition/dcomp.idl @@ -0,0 +1,164 @@ +@clr-namespace Avalonia.Win32.DComposition +@clr-access internal +@clr-map FLOAT float +@clr-map HSTRING IntPtr +@clr-map Vector2 System.Numerics.Vector2 +@clr-map Vector3 System.Numerics.Vector3 +@clr-map Quaternion System.Numerics.Quaternion +@clr-map Matrix4x4 System.Numerics.Matrix4x4 +@clr-map RECT Avalonia.Win32.Interop.UnmanagedMethods.RECT +@clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE +@clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT +@clr-map DXGI_FORMAT Avalonia.Win32.DirectX.DXGI_FORMAT +@clr-map DXGI_ALPHA_MODE Avalonia.Win32.DirectX.DXGI_ALPHA_MODE +@clr-map HWND IntPtr +@clr-map HANDLE 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 +@clr-map INT32 int +@clr-map INT64 long +@clr-map UINT ushort +@clr-map UINT16 ushort +@clr-map ULONG uint +@clr-map UINT32 uint +@clr-map UINT64 ulong +@clr-map DOUBLE double +@clr-map GUID System.Guid +@clr-map REFGUID System.Guid* +@clr-map REFIID System.Guid* +@clr-map WCHAR System.Char +@clr-map HMODULE void* +@clr-map DXGI_USAGE uint +@clr-map LARGE_INTEGER ulong +@clr-map INT int +@clr-map DXGI_SHARED_RESOURCE void* +@clr-map LUID ulong +@clr-map LPSTR ushort* +@clr-map LPWSTR ushort* +@clr-map LPCWSTR ushort* + +[uuid(c37ea93a-e7aa-450d-b16f-9746cb0407f3)] +interface IDCompositionDevice : IUnknown +{ + HRESULT Commit(); + HRESULT WaitForCommitCompletion(); + HRESULT GetFrameStatistics(DCOMPOSITION_FRAME_STATISTICS* statistics); + HRESULT CreateTargetForHwnd(IntPtr hwnd, bool topmost, [out] IDCompositionVisual** target); + HRESULT CreateVisual([out] IDCompositionVisual** visual); + HRESULT CreateSurface(UINT width, UINT height, DXGI_FORMAT pixelFormat, DXGI_ALPHA_MODE alphaMode, [out] IDCompositionSurface** surface); + HRESULT CreateVirtualSurface(UINT initialWidth, UINT initialHeight, DXGI_FORMAT pixelFormat, DXGI_ALPHA_MODE alphaMode, [out] IDCompositionVirtualSurface** virtualSurface); + HRESULT CreateSurfaceFromHandle(IntPtr handle, [out] IUnknown** surface); + HRESULT CreateSurfaceFromHwnd(IntPtr hwnd, [out] IUnknown** surface); + HRESULT CreateTranslateTransform([out] void** translateTransform); + HRESULT CreateScaleTransform([out] void** scaleTransform); + HRESULT CreateRotateTransform([out] void** rotateTransform); + HRESULT CreateSkewTransform([out] void** skewTransform); + HRESULT CreateMatrixTransform([out] void** matrixTransform); + HRESULT CreateTransformGroup(void* transforms, int elements, [out] void** transformGroup); + HRESULT CreateTranslateTransform3D([out] void** translateTransform3D); + HRESULT CreateScaleTransform3D([out] void** scaleTransform3D); + HRESULT CreateRotateTransform3D([out] void** rotateTransform3D); + HRESULT CreateMatrixTransform3D([out] void** matrixTransform3D); + HRESULT CreateTransform3DGroup(void* transforms3D, int elements, [out] void** transform3DGroup); + HRESULT CreateEffectGroup([out] void** effectGroup); + HRESULT CreateRectangleClip([out] void** clip); + HRESULT CreateAnimation([out] void** animation); + HRESULT CheckDeviceState([out] BOOL* pfValid); +} + +// IDCompositionDevice2 DOES NOT inherit IDCompositionDevice, it's a completely different interface in DComp as it doesn't have desktop only APIs. +[uuid(75F6468D-1B8E-447C-9BC6-75FEA80B5B25)] +interface IDCompositionDevice2 : IUnknown +{ + HRESULT Commit(); + HRESULT WaitForCommitCompletion(); + HRESULT GetFrameStatistics(DCOMPOSITION_FRAME_STATISTICS* statistics); + HRESULT CreateVisual([out] IDCompositionVisual** visual); + HRESULT CreateSurfaceFactory(IUnknown* renderingDevice, [out] IDCompositionSurfaceFactory** surfaceFactory); + HRESULT CreateSurface(UINT width, UINT height, DXGI_FORMAT pixelFormat, DXGI_ALPHA_MODE alphaMode, [out] IDCompositionSurface** surface); + HRESULT CreateVirtualSurface(UINT initialWidth, UINT initialHeight, DXGI_FORMAT pixelFormat, DXGI_ALPHA_MODE alphaMode, [out] IDCompositionVirtualSurface** virtualSurface); + HRESULT CreateTranslateTransform([out] void** translateTransform); + HRESULT CreateScaleTransform([out] void** scaleTransform); + HRESULT CreateRotateTransform([out] void** rotateTransform); + HRESULT CreateSkewTransform([out] void** skewTransform); + HRESULT CreateMatrixTransform([out] void** matrixTransform); + HRESULT CreateTransformGroup(void* transforms, int elements, [out] void** transformGroup); + HRESULT CreateTranslateTransform3D([out] void** translateTransform3D); + HRESULT CreateScaleTransform3D([out] void** scaleTransform3D); + HRESULT CreateRotateTransform3D([out] void** rotateTransform3D); + HRESULT CreateMatrixTransform3D([out] void** matrixTransform3D); + HRESULT CreateTransform3DGroup(void* transforms3D, int elements, [out] void** transform3DGroup); + HRESULT CreateEffectGroup([out] void** effectGroup); + HRESULT CreateRectangleClip([out] void** clip); + HRESULT CreateAnimation([out] void** animation); +} + +[uuid(0987cb06-f916-48bf-8d35-ce7641781bd9)] +interface IDCompositionDevice3 : IDCompositionDevice2 +{ + HRESULT CreateGaussianBlurEffect([out] void** gaussianBlurEffect); +} + +[uuid(5f4633fe-1e08-4cb8-8c75-ce24333f5602)] +interface IDCompositionDesktopDevice : IDCompositionDevice2 +{ + HRESULT CreateTargetForHwnd(IntPtr hwnd, bool topmost, [out] IDCompositionVisual** target); + HRESULT CreateSurfaceFromHandle(IntPtr handle, [out] IUnknown** surface); + HRESULT CreateSurfaceFromHwnd(IntPtr hwnd, [out] IUnknown** surface); +} + +[uuid(4d93059d-097b-4651-9a60-f0f25116e2f3)] +interface IDCompositionVisual : IUnknown +{ + HRESULT SetOffsetX_IDCompositionAnimation(void* animation); + HRESULT SetOffsetX(float offsetX); + HRESULT SetOffsetY_IDCompositionAnimation(void* animation); + HRESULT SetOffsetY(float offsetY); + HRESULT SetTransform_IDCompositionTransform(void* transform); + HRESULT SetTransform(/* D2D_MATRIX_3X2_F */ void* matrix); + HRESULT SetTransformParent(IDCompositionVisual* visual); + HRESULT SetEffect(void* effect); + HRESULT SetBitmapInterpolationMode(/* DCOMPOSITION_BITMAP_INTERPOLATION_MODE */ int interpolationMode); + HRESULT SetBorderMode(/* DCOMPOSITION_BORDER_MODE */ int borderMode); + HRESULT SetClip_IDCompositionClip(void* clip); + HRESULT SetClip(/* D2D_RECT_F */ void* rect); + HRESULT SetContent(IUnknown* content); + HRESULT AddVisual(IDCompositionVisual* visual, BOOL insertAbove, IDCompositionVisual* referenceVisual); + HRESULT RemoveVisual(IDCompositionVisual* visual); + HRESULT RemoveAllVisuals(); + HRESULT SetCompositeMode(/* DCOMPOSITION_COMPOSITE_MODE */ int compositeMode); +} + +[uuid(eacdd04c-117e-4e17-88f4-d1b12b0e3d89)] +interface IDCompositionTarget : IUnknown +{ + HRESULT SetRoot(IDCompositionVisual* visual); +} + +[uuid(e334bc12-3937-4e02-85eb-fcf4eb30d2c8)] +interface IDCompositionSurfaceFactory : IUnknown +{ + HRESULT CreateSurface(UINT width, UINT height, DXGI_FORMAT pixelFormat, DXGI_ALPHA_MODE alphaMode, [out] IDCompositionSurface** surface); + HRESULT CreateVirtualSurface(UINT initialWidth, UINT initialHeight, DXGI_FORMAT pixelFormat, DXGI_ALPHA_MODE alphaMode, [out] IDCompositionVirtualSurface** virtualSurface); +} + +[uuid(bb8a4953-2c99-4f5a-96f5-4819027fa3ac)] +interface IDCompositionSurface : IUnknown +{ + HRESULT BeginDraw(RECT* updateRect, GUID* iid, [out] void** updateObject, [out] POINT* updateOffset); + HRESULT EndDraw(); + HRESULT SuspendDraw(); + HRESULT ResumeDraw(); + HRESULT Scroll(RECT* scrollRect, RECT* clipRect, int offsetX, int offsetY); +} + +[uuid(ae471c51-5f53-4a24-8d3e-d0c39c30b3f0)] +interface IDCompositionVirtualSurface : IDCompositionSurface +{ + HRESULT Resize(UINT width, UINT height); + HRESULT Trim(/* RECT* */ void* rectangles, INT count); +} diff --git a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs index bac2a73dd9..f5031146cb 100644 --- a/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs +++ b/src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Threading.Tasks; using Avalonia.Logging; +using Avalonia.OpenGL.Egl; using Avalonia.Rendering; using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.DirectX.DirectXUnmanagedMethods; @@ -9,7 +10,7 @@ using MicroCom.Runtime; namespace Avalonia.Win32.DirectX { - internal unsafe class DxgiConnection : IRenderTimer + internal unsafe class DxgiConnection : IRenderTimer, IWindowsSurfaceFactory { public const uint ENUM_CURRENT_SETTINGS = unchecked((uint)(-1)); @@ -164,7 +165,7 @@ namespace Avalonia.Win32.DirectX { var connection = new DxgiConnection(pumpLock); - AvaloniaLocator.CurrentMutable.BindToSelf(connection); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(connection); AvaloniaLocator.CurrentMutable.Bind().ToConstant(connection); tcs.SetResult(true); connection.RunLoop(); @@ -180,5 +181,8 @@ namespace Avalonia.Win32.DirectX // block until return tcs.Task.Result; } + + public bool RequiresNoRedirectionBitmap => false; + public object CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new DxgiSwapchainWindow(this, info); } } diff --git a/src/Windows/Avalonia.Win32/IBlurHost.cs b/src/Windows/Avalonia.Win32/IBlurHost.cs new file mode 100644 index 0000000000..fcaf58eaac --- /dev/null +++ b/src/Windows/Avalonia.Win32/IBlurHost.cs @@ -0,0 +1,17 @@ +namespace Avalonia.Win32; + +internal enum BlurEffect +{ + None, + GaussianBlur, + Acrylic, + MicaLight, + MicaDark +} + +internal interface ICompositionEffectsSurface +{ + bool IsBlurSupported(BlurEffect effect); + + void SetBlur(BlurEffect enable); +} diff --git a/src/Windows/Avalonia.Win32/IWindowsSurfaceFactory.cs b/src/Windows/Avalonia.Win32/IWindowsSurfaceFactory.cs new file mode 100644 index 0000000000..e46c155b1d --- /dev/null +++ b/src/Windows/Avalonia.Win32/IWindowsSurfaceFactory.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; +using Avalonia.OpenGL.Egl; + +namespace Avalonia.Win32; + +internal interface IWindowsSurfaceFactory +{ + bool RequiresNoRedirectionBitmap { get; } + + object CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info); +} diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 445bec979e..e040e5bfb7 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1151,6 +1151,9 @@ namespace Avalonia.Win32.Interop uint uStartScan, uint cScanLines, IntPtr lpvBits, [In] ref BITMAPINFO lpbmi, uint fuColorUse); + [DllImport("gdi32.dll", SetLastError = false, ExactSpelling = true)] + public static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2); + [DllImport("user32.dll")] public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC); @@ -1704,8 +1707,8 @@ namespace Avalonia.Win32.Interop [DllImport("dwmapi.dll")] public static extern bool DwmDefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref IntPtr plResult); - [DllImport("dwmapi.dll")] - public static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind); + [DllImport("dwmapi.dll", SetLastError = false)] + public static extern int DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind); [Flags] public enum LayeredWindowFlags @@ -1732,14 +1735,6 @@ namespace Avalonia.Win32.Interop public bool fEnable; public IntPtr hRgnBlur; public bool fTransitionOnMaximized; - - public DWM_BLURBEHIND(bool enabled) - { - fEnable = enabled; - hRgnBlur = IntPtr.Zero; - fTransitionOnMaximized = false; - dwFlags = DWM_BB.Enable; - } } [StructLayout(LayoutKind.Sequential)] @@ -1830,22 +1825,6 @@ namespace Avalonia.Win32.Interop return result; } - internal static int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data) - { - var user32 = LoadLibrary("user32.dll"); - var pfnSetWindowCompositionAttribute = (delegate* unmanaged[Stdcall])GetProcAddress(user32, nameof(SetWindowCompositionAttribute)); - if (pfnSetWindowCompositionAttribute == null) - { - // This preserves the same behavior as using the DllImport attribute. - throw new EntryPointNotFoundException("The unsupported SetWindowCompositionAttribute-function has been removed from the operating system."); - } - - fixed (WindowCompositionAttributeData* pData = &data) - { - return pfnSetWindowCompositionAttribute(hwnd, pData); - } - } - [Flags] public enum GCS : uint { diff --git a/src/Windows/Avalonia.Win32/PlatformConstants.cs b/src/Windows/Avalonia.Win32/PlatformConstants.cs index bed78501f3..c638314c4d 100644 --- a/src/Windows/Avalonia.Win32/PlatformConstants.cs +++ b/src/Windows/Avalonia.Win32/PlatformConstants.cs @@ -9,6 +9,7 @@ namespace Avalonia.Win32 public static readonly Version Windows10 = new Version(10, 0); public static readonly Version Windows8 = new Version(6, 2); + public static readonly Version Windows8_1 = new Version(6, 3); public static readonly Version Windows7 = new Version(6, 1); } } diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index 26a55a98df..cef200b4d7 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -3,6 +3,7 @@ using System.Diagnostics.Tracing; using System.Linq; using Avalonia.OpenGL; using Avalonia.Platform; +using Avalonia.Win32.DComposition; using Avalonia.Win32.DirectX; using Avalonia.Win32.OpenGl; using Avalonia.Win32.OpenGl.Angle; @@ -87,6 +88,13 @@ static class Win32GlManager return; } + if (compositionMode == Win32CompositionMode.DirectComposition + && DirectCompositionConnection.IsSupported() + && DirectCompositionConnection.TryCreateAndRegister()) + { + return; + } + if (compositionMode == Win32CompositionMode.LowLatencyDxgiSwapChain && DxgiConnection.TryCreateAndRegister()) { diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs index eac18dc080..4506d7a3e8 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -61,14 +61,14 @@ public enum Win32CompositionMode /// WinUIComposition = 1, - // /// - // /// Render Avalonia to a texture inside the DirectComposition tree. - // /// - // /// - // /// Supported on Windows 8 and above. Ignored on other versions.
- // /// Can only be applied with =. - // ///
- // DirectComposition = 2, + /// + /// Render Avalonia to a texture inside the DirectComposition tree. + /// + /// + /// Supported on Windows 8 and above. Ignored on other versions.
+ /// Can only be applied with =. + ///
+ DirectComposition = 2, /// /// When is active, renders Avalonia through a low-latency Dxgi Swapchain. @@ -117,7 +117,7 @@ public class Win32PlatformOptions /// /// Gets or sets Avalonia composition modes with fallbacks. /// The first element in the array has the highest priority. - /// The default value is: , . + /// The default value is: , , . /// /// /// If application should work on as wide range of devices as possible, at least add as a fallback value. @@ -125,7 +125,7 @@ public class Win32PlatformOptions /// Thrown if no values were matched. public IReadOnlyList CompositionMode { get; set; } = new[] { - Win32CompositionMode.WinUIComposition, Win32CompositionMode.RedirectionSurface + Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition, Win32CompositionMode.RedirectionSurface }; /// diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index 8da154dc83..2addbc6524 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs @@ -7,7 +7,7 @@ using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { - internal class WinUiCompositedWindowSurface : IDirect3D11TexturePlatformSurface, IDisposable, IBlurHost + internal class WinUiCompositedWindowSurface : IDirect3D11TexturePlatformSurface, IDisposable, ICompositionEffectsSurface { private readonly WinUiCompositionShared _shared; private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; @@ -36,6 +36,15 @@ namespace Avalonia.Win32.WinRT.Composition _window = null; } + public bool IsBlurSupported(BlurEffect effect) => effect switch + { + BlurEffect.None => true, + BlurEffect.Acrylic => Win32Platform.WindowsVersion >= WinUiCompositionShared.MinAcrylicVersion, + BlurEffect.MicaLight => Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion, + BlurEffect.MicaDark => Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion, + _ => false + }; + public void SetBlur(BlurEffect enable) { _blurEffect = enable; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs index 596c94d30b..5255eaff98 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositorConnection.cs @@ -11,7 +11,7 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32.WinRT.Composition; -internal class WinUiCompositorConnection : IRenderTimer +internal class WinUiCompositorConnection : IRenderTimer, Win32.IWindowsSurfaceFactory { private readonly WinUiCompositionShared _shared; public event Action? Tick; @@ -54,7 +54,7 @@ internal class WinUiCompositorConnection : IRenderTimer threadType = NativeWinRTMethods.DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT }); connect = new WinUiCompositorConnection(); - AvaloniaLocator.CurrentMutable.BindToSelf(connect); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(connect); AvaloniaLocator.CurrentMutable.Bind().ToConstant(connect); tcs.SetResult(true); @@ -144,5 +144,6 @@ internal class WinUiCompositorConnection : IRenderTimer return false; } - public WinUiCompositedWindowSurface CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new(_shared, info); + public bool RequiresNoRedirectionBitmap => true; + public object CreateSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) => new WinUiCompositedWindowSurface(_shared, info); } diff --git a/src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs b/src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs deleted file mode 100644 index 5f2e752af8..0000000000 --- a/src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Avalonia.Win32.WinRT -{ - internal enum BlurEffect - { - None, - Acrylic, - MicaLight, - MicaDark - } - - internal interface IBlurHost - { - void SetBlur(BlurEffect enable); - } -} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 6dd491030d..7a3f411195 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -100,7 +100,7 @@ namespace Avalonia.Win32 } // Cleanup render targets - (_gl as IDisposable)?.Dispose(); + (_glSurface as IDisposable)?.Dispose(); if (_dropTarget != null) { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ffd9b08d4a..d83c72eb1f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -60,8 +60,6 @@ namespace Avalonia.Win32 private Thickness _extendedMargins; private Thickness _offScreenMargin; private double _extendTitleBarHint = -1; - private readonly bool _isUsingComposition; - private readonly IBlurHost? _blurHost; private WindowResizeReason _resizeReason; private MOUSEMOVEPOINT _lastWmMousePoint; @@ -74,7 +72,7 @@ namespace Avalonia.Win32 private readonly WindowsMouseDevice _mouseDevice; private readonly PenDevice _penDevice; private readonly FramebufferManager _framebuffer; - private readonly object? _gl; + private readonly object? _glSurface; private readonly bool _wmPointerEnabled; private readonly Win32NativeControlHost _nativeControlHost; @@ -101,6 +99,7 @@ namespace Avalonia.Win32 private uint _langid; internal bool _ignoreWmChar; private WindowTransparencyLevel _transparencyLevel; + private readonly WindowTransparencyLevel _defaultTransparencyLevel; private const int MaxPointerHistorySize = 512; private static readonly PooledList s_intermediatePointsPooledList = new(); @@ -133,20 +132,10 @@ namespace Avalonia.Win32 Decorations = SystemDecorations.Full }; + var surfaceFactory = AvaloniaLocator.Current.GetService(); var glPlatform = AvaloniaLocator.Current.GetService(); - - var compositionConnector = AvaloniaLocator.Current.GetService(); - - var isUsingAngleDX11 = glPlatform is D3D11AngleWin32PlatformGraphics; - _isUsingComposition = compositionConnector is { } && isUsingAngleDX11; - - DxgiConnection? dxgiConnection = null; - var isUsingDxgiSwapchain = false; - if (!_isUsingComposition) - { - dxgiConnection = AvaloniaLocator.Current.GetService(); - isUsingDxgiSwapchain = dxgiConnection is { } && isUsingAngleDX11; - } + UseRedirectionBitmap = surfaceFactory is null || glPlatform is null || + !surfaceFactory.RequiresNoRedirectionBitmap; _wmPointerEnabled = Win32Platform.WindowsVersion >= PlatformConstants.Windows8; @@ -157,34 +146,28 @@ namespace Avalonia.Win32 { UpdateInputMethod(GetKeyboardLayout(0)); } - + if (glPlatform != null) { - if (_isUsingComposition) + if (surfaceFactory is not null) { - var cgl = compositionConnector!.CreateSurface(this); - _blurHost = cgl; - _gl = cgl; - } - else if (isUsingDxgiSwapchain) - { - var dxgigl = new DxgiSwapchainWindow(dxgiConnection!, this); - _gl = dxgigl; + _glSurface = surfaceFactory.CreateSurface(this); } else { if (glPlatform is D3D11AngleWin32PlatformGraphics or D3D9AngleWin32PlatformGraphics) - _gl = new EglGlPlatformSurface(this); + _glSurface = new EglGlPlatformSurface(this); else if (glPlatform is WglPlatformOpenGlInterface) - _gl = new WglGlPlatformSurface(this); + _glSurface = new WglGlPlatformSurface(this); } } Screen = new ScreenImpl(); _storageProvider = new Win32StorageProvider(this); - _nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition); - _transparencyLevel = _isUsingComposition ? WindowTransparencyLevel.Transparent : WindowTransparencyLevel.None; + _nativeControlHost = new Win32NativeControlHost(this, !UseRedirectionBitmap); + _defaultTransparencyLevel = UseRedirectionBitmap ? WindowTransparencyLevel.None : WindowTransparencyLevel.Transparent; + _transparencyLevel = _defaultTransparencyLevel; s_instances.Add(this); } @@ -244,6 +227,9 @@ namespace Avalonia.Win32 private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scaling ?? 1; + private ICompositionEffectsSurface? CompositionEffectsSurface => _glSurface as ICompositionEffectsSurface; + private bool UseRedirectionBitmap { get; } + public double RenderScaling => _scaling; public double DesktopScaling => RenderScaling; @@ -362,164 +348,143 @@ namespace Avalonia.Win32 public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { - var windowsVersion = Win32Platform.WindowsVersion; - foreach (var level in transparencyLevels) { - if (!IsSupported(level, windowsVersion)) + if (!IsSupported(level)) continue; + if (level == TransparencyLevel) + { return; + } if (level == WindowTransparencyLevel.Transparent) - SetTransparencyTransparent(windowsVersion); - else if (level == WindowTransparencyLevel.Blur) - SetTransparencyBlur(windowsVersion); + { + if (!SetTransparencyTransparent()) + continue; + } else if (level == WindowTransparencyLevel.AcrylicBlur) - SetTransparencyAcrylicBlur(windowsVersion); + { + if (!SetTransparencyAcrylicBlur()) + continue; + } else if (level == WindowTransparencyLevel.Mica) - SetTransparencyMica(windowsVersion); + { + if (!SetTransparencyMica()) + continue; + } TransparencyLevel = level; return; } - // If we get here, we didn't find a supported level. Use the defualt of Transparent or - // None, depending on whether composition is enabled. - if (_isUsingComposition) - { - SetTransparencyTransparent(windowsVersion); - TransparencyLevel = WindowTransparencyLevel.Transparent; - } - else - { - TransparencyLevel = WindowTransparencyLevel.None; - } + // If we get here, we didn't find a supported level. Report the default. + TransparencyLevel = _defaultTransparencyLevel; } - private bool IsSupported(WindowTransparencyLevel level, Version windowsVersion) + private bool IsSupported(WindowTransparencyLevel level) { - // Only None is suppported when composition is disabled. - if (!_isUsingComposition) - return level == WindowTransparencyLevel.None; - - // When composition is enabled, None is not supported because the backing visual always - // has an alpha channel + // None is only supported with redirection bitmap. + // Note, it's still possible to have non-transparent window with a fallback background brush. if (level == WindowTransparencyLevel.None) - return false; + return UseRedirectionBitmap; - // Transparent only supported on Windows 8+. + // Transparent is supported either with DwmEnableBlurBehindWindow (win8+) or with NoRedirectionBitmap. if (level == WindowTransparencyLevel.Transparent) - return windowsVersion >= PlatformConstants.Windows8; + return !UseRedirectionBitmap || Win32Platform.WindowsVersion >= PlatformConstants.Windows8; - // Blur only supported on Windows 8 and lower. if (level == WindowTransparencyLevel.Blur) - return windowsVersion < PlatformConstants.Windows10; + return CompositionEffectsSurface?.IsBlurSupported(BlurEffect.GaussianBlur) ?? false; - // Acrylic is supported on Windows >= 10.0.15063. if (level == WindowTransparencyLevel.AcrylicBlur) - return windowsVersion >= WinUiCompositionShared.MinAcrylicVersion; + return CompositionEffectsSurface?.IsBlurSupported(BlurEffect.Acrylic) ?? false; - // Mica is supported on Windows >= 10.0.22000. if (level == WindowTransparencyLevel.Mica) - return windowsVersion >= WinUiCompositionShared.MinHostBackdropVersion; + return CompositionEffectsSurface?.IsBlurSupported(BlurEffect.MicaDark) ?? false; return false; } - private void SetTransparencyTransparent(Version windowsVersion) + private bool SetTransparencyTransparent() { - // Transparent only supported with composition on Windows 8+. - if (!_isUsingComposition || windowsVersion < PlatformConstants.Windows8) - return; - - if (windowsVersion < PlatformConstants.Windows10) + if (CompositionEffectsSurface is {} surface) { - // Some of the AccentState Enum's values have different meanings on Windows 8.x than on - // Windows 10, hence using ACCENT_ENABLE_BLURBEHIND to disable blurbehind ¯\_(ツ)_/¯. - // Hey, I'm just porting what was here before. - SetAccentState(AccentState.ACCENT_ENABLE_BLURBEHIND); - var blurInfo = new DWM_BLURBEHIND(false); - DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); + surface.SetBlur(BlurEffect.None); + return true; + } + else + { + return SetLegacyTransparency(true); } - - SetUseHostBackdropBrush(false); - _blurHost?.SetBlur(BlurEffect.None); - } - - private void SetTransparencyBlur(Version windowsVersion) - { - // Blur only supported with composition on Windows 8 and lower. - if (!_isUsingComposition || windowsVersion >= PlatformConstants.Windows10) - return; - - // Some of the AccentState Enum's values have different meanings on Windows 8.x than on - // Windows 10. - SetAccentState(AccentState.ACCENT_DISABLED); - var blurInfo = new DWM_BLURBEHIND(true); - DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); } - private void SetTransparencyAcrylicBlur(Version windowsVersion) + private bool SetTransparencyAcrylicBlur() { - // Acrylic blur only supported with composition on Windows >= 10.0.15063. - if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinAcrylicVersion) - return; - SetUseHostBackdropBrush(true); - _blurHost?.SetBlur(BlurEffect.Acrylic); + SetLegacyTransparency(false); + + CompositionEffectsSurface!.SetBlur(BlurEffect.Acrylic); + return true; } - private void SetTransparencyMica(Version windowsVersion) + /// + /// Sets the transparency mica + /// + /// + private bool SetTransparencyMica() { - // Mica only supported with composition on Windows >= 10.0.22000. - if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinHostBackdropVersion) - return; - SetUseHostBackdropBrush(false); - _blurHost?.SetBlur(_currentThemeVariant switch + SetLegacyTransparency(false); + + CompositionEffectsSurface!.SetBlur(_currentThemeVariant switch { PlatformThemeVariant.Light => BlurEffect.MicaLight, PlatformThemeVariant.Dark => BlurEffect.MicaDark, _ => throw new ArgumentOutOfRangeException() }); + return true; } - private void SetAccentState(AccentState state) + private bool SetLegacyTransparency(bool enabled) { - var accent = new AccentPolicy(); - var accentStructSize = Marshal.SizeOf(accent); - - //Some of the AccentState Enum's values have different meanings on Windows 8.x than on Windows 10 - accent.AccentState = state; + if (Win32Platform.WindowsVersion < PlatformConstants.Windows8 || !UseRedirectionBitmap) + return false; + + // On pre-Win8 this method was blurring a window, which is a different from desired behavior. + // On win8+ we use this method as a fallback, when WinUI/DComp composition with true transparency isn't available. + // Note: there is no guarantee that this behavior won't be changed back to true blur in Win12. + // See https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmenableblurbehindwindow#remarks + // Also https://github.com/qt/qtbase/blob/fd300f143fd30947bba60a03d614acd2711b635f/src/plugins/platforms/windows/qwindowswindow.cpp#L519 + var blurInfo = new DWM_BLURBEHIND(); + blurInfo.fEnable = enabled; + blurInfo.dwFlags = DWM_BB.Enable | DWM_BB.BlurRegion; + blurInfo.hRgnBlur = CreateRectRgn(0, 0, -1, -1); - var accentPtr = Marshal.AllocHGlobal(accentStructSize); - Marshal.StructureToPtr(accent, accentPtr, false); + var result = DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); - var data = new WindowCompositionAttributeData(); - data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY; - data.SizeOfData = accentStructSize; - data.Data = accentPtr; + if (blurInfo.hRgnBlur != default) + { + DeleteObject(blurInfo.hRgnBlur); + } - SetWindowCompositionAttribute(_hwnd, ref data); - Marshal.FreeHGlobal(accentPtr); + return result == 0; } - private void SetUseHostBackdropBrush(bool useHostBackdropBrush) + private unsafe bool SetUseHostBackdropBrush(bool useHostBackdropBrush) { if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion) - return; + return false; - unsafe - { - var pvUseBackdropBrush = useHostBackdropBrush ? 1 : 0; - DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int)); - } + // AcrylicBlur requires window to set DWMWA_USE_HOSTBACKDROPBRUSH flag on Win11+. + // It's not necessary on older versions and it's not necessary with Mica brush. + + var pvUseBackdropBrush = useHostBackdropBrush ? 1 : 0; + var result = DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int)); + return result == 0; } - public IEnumerable Surfaces - => _gl is null ? + => _glSurface is null ? new object[] { Handle, _framebuffer } : - new object[] { Handle, _gl, _framebuffer }; + new object[] { Handle, _glSurface, _framebuffer }; public PixelPoint Position { @@ -797,7 +762,7 @@ namespace Avalonia.Win32 sizeof(int)); if (TransparencyLevel == WindowTransparencyLevel.Mica) { - SetTransparencyMica(Win32Platform.WindowsVersion); + SetTransparencyMica(); } } } @@ -805,7 +770,7 @@ namespace Avalonia.Win32 protected virtual IntPtr CreateWindowOverride(ushort atom) { return CreateWindowEx( - _isUsingComposition ? (int)WindowStyles.WS_EX_NOREDIRECTIONBITMAP : 0, + UseRedirectionBitmap ? 0 : (int)WindowStyles.WS_EX_NOREDIRECTIONBITMAP, atom, null, (int)WindowStyles.WS_OVERLAPPEDWINDOW | (int)WindowStyles.WS_CLIPCHILDREN, @@ -992,7 +957,7 @@ namespace Avalonia.Win32 } //using a default margin of 0 when using WinUiComp removes artefacts when resizing. See issue #8316 - var defaultMargin = _isUsingComposition ? 0 : 1; + var defaultMargin = UseRedirectionBitmap ? 1 : 0; MARGINS margins = new MARGINS(); margins.cxLeftWidth = defaultMargin; @@ -1274,7 +1239,7 @@ namespace Avalonia.Win32 if (oldProperties.IsFullScreen == newProperties.IsFullScreen) { - var exStyle = WindowStyles.WS_EX_WINDOWEDGE | (_isUsingComposition ? WindowStyles.WS_EX_NOREDIRECTIONBITMAP : 0); + var exStyle = WindowStyles.WS_EX_WINDOWEDGE | (UseRedirectionBitmap ? 0 : WindowStyles.WS_EX_NOREDIRECTIONBITMAP); if ((oldProperties.ShowInTaskbar != newProperties.ShowInTaskbar) || forceChanges) {