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