diff --git a/src/Avalonia.Base/Platform/IRenderTarget.cs b/src/Avalonia.Base/Platform/IRenderTarget.cs index 0bc84c7a145..b6c1734891e 100644 --- a/src/Avalonia.Base/Platform/IRenderTarget.cs +++ b/src/Avalonia.Base/Platform/IRenderTarget.cs @@ -33,7 +33,10 @@ public interface IRenderTarget : IDisposable /// PlatformRenderTargetState PlatformRenderTargetState => PlatformRenderTargetState.Ready; - public record struct RenderTargetSceneInfo(PixelSize Size, double Scaling, Size LogicalSize, CompositionTransparencyLevel TransparencyLevel) + // TopLevelSpecificSceneInfo is an opaque immutable object provided by the platform's + // ITopLevelImpl.TopLevelSpecificSceneInfo. + public record struct RenderTargetSceneInfo(PixelSize Size, double Scaling, Size LogicalSize, + CompositionTransparencyLevel TransparencyLevel, object? TopLevelSpecificSceneInfo = null) { public RenderTargetSceneInfo(PixelSize size, double scaling, CompositionTransparencyLevel transparencyLevel) : this(size, scaling, size.ToSize(scaling), transparencyLevel) { diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 72d1eac2785..f16a6e164e6 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -205,7 +205,9 @@ public void Render() try { renderTargetContext = - _renderTarget.CreateDrawingContext(new(PixelSize, Scaling, Size, TransparencyLevel), out properties); + _renderTarget.CreateDrawingContext( + new(PixelSize, Scaling, Size, TransparencyLevel, TopLevelSpecificSceneInfo), + out properties); } catch (RenderTargetNotReadyException) { diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml index 1256355fa49..98f860fd0bf 100644 --- a/src/Avalonia.Base/composition-schema.xml +++ b/src/Avalonia.Base/composition-schema.xml @@ -11,6 +11,7 @@ + @@ -60,6 +61,7 @@ + diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 548d4989b67..eaed786f17b 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -76,6 +76,18 @@ public interface ITopLevelImpl : IOptionalFeatureProvider, IDisposable /// Action? TransparencyLevelChanged { get; set; } + /// + /// Gets an opaque platform-specific object with extra information about the scene, + /// to be passed to the platform render target with each frame. + /// The object must be immutable; implementations must publish a new instance on change. + /// + object? TopLevelSpecificSceneInfo => null; + + /// + /// Gets or sets a method called when changes. + /// + Action? TopLevelSpecificSceneInfoChanged { get => null; set { } } + /// /// Gets the compositor that's compatible with the toplevel /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 721b20ccaba..78aaa83b7b8 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -223,6 +223,7 @@ internal TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyRes _source.Renderer.CompositionTarget.TransparencyLevel = ToCompositionTransparencyLevel(_actualTransparencyLevel); + _source.Renderer.CompositionTarget.TopLevelSpecificSceneInfo = impl.TopLevelSpecificSceneInfo; _accessKeyHandler = TryGetService(dependencyResolver); @@ -240,6 +241,7 @@ internal TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyRes impl.Resized = HandleResized; impl.ScalingChanged += HandleScalingChanged; impl.TransparencyLevelChanged = HandleTransparencyLevelChanged; + impl.TopLevelSpecificSceneInfoChanged = HandleTopLevelSpecificSceneInfoChanged; CreatePlatformImplBinding(TransparencyLevelHintProperty, hint => PlatformImpl.SetTransparencyLevelHint(hint ?? Array.Empty())); @@ -771,6 +773,11 @@ private void HandleTransparencyLevelChanged(WindowTransparencyLevel transparency ToCompositionTransparencyLevel(transparencyLevel); } + private void HandleTopLevelSpecificSceneInfoChanged(object? sceneInfo) + { + Renderer.CompositionTarget.TopLevelSpecificSceneInfo = sceneInfo; + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); diff --git a/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindow.cs b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindow.cs index 4689ad9bc49..56768bf3635 100644 --- a/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindow.cs @@ -1,12 +1,15 @@ using System; using System.Threading; using Avalonia.OpenGL.Egl; +using Avalonia.Platform; using Avalonia.Reactive; +using Avalonia.Rendering.Composition; +using Avalonia.Win32.DirectX; using MicroCom.Runtime; namespace Avalonia.Win32.DComposition; -internal class DirectCompositedWindow : IDisposable +internal class DirectCompositedWindow : ISwapchainVisualHost, IDisposable { private readonly DirectCompositionShared _shared; public EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; } @@ -41,6 +44,18 @@ public DirectCompositedWindow(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceIn public void SetSurface(IDCompositionSurface surface) => _container.SetContent(surface); + public void SetSwapchainContent(IUnknown swapchain) => _container.SetContent(swapchain); + + // A DComp visual displays its content at native size, nothing to resize + public void ResizeIfNeeded(PixelSize size) + { + } + + // No composition effects on the DComp path + public void ApplyEffects(CompositionTransparencyLevel transparencyLevel, PlatformThemeVariant themeVariant) + { + } + public IDisposable BeginTransaction() { Monitor.Enter(_shared.SyncRoot); diff --git a/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs index e91ca786f78..6926b0721a4 100644 --- a/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/DComposition/DirectCompositedWindowSurface.cs @@ -16,7 +16,6 @@ internal class DirectCompositedWindowSurface : IDirect3D11TexturePlatformSurface private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; private readonly DirectCompositionShared _shared; private DirectCompositedWindow? _window; - private BlurEffect _blurEffect; public DirectCompositedWindowSurface(DirectCompositionShared shared, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) { @@ -32,7 +31,9 @@ IDirect3D11TextureRenderTarget IDirect3D11TexturePlatformSurface.CreateRenderTar public IDirect3D11TextureRenderTarget2 CreateRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice) { _window ??= new DirectCompositedWindow(_info, _shared); - SetBlur(_blurEffect); + + if (AvaloniaLocator.Current.GetService()?.UseCompositionSwapchain ?? false) + return new CompositionSwapchainRenderTarget(context, d3dDevice, _window); return new DirectCompositedWindowRenderTarget(context, d3dDevice, _shared, _window); } @@ -43,14 +44,8 @@ public void Dispose() _window = null; } - // TODO: we can implement BlurEffect.GaussianBlur in with IDCompositionDevice3.CreateGaussianBlurEffect. + // 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, IDirect3D11TextureRenderTarget2 diff --git a/src/Windows/Avalonia.Win32/DirectX/CompositionSwapchainRenderTarget.cs b/src/Windows/Avalonia.Win32/DirectX/CompositionSwapchainRenderTarget.cs new file mode 100644 index 00000000000..d6b25cde3e1 --- /dev/null +++ b/src/Windows/Avalonia.Win32/DirectX/CompositionSwapchainRenderTarget.cs @@ -0,0 +1,226 @@ +using System; +using Avalonia.OpenGL.Egl; +using Avalonia.Platform; +using Avalonia.Rendering.Composition; +using MicroCom.Runtime; + +namespace Avalonia.Win32.DirectX; + +/// +/// A composed window that can display a DXGI swapchain as its content. +/// +internal interface ISwapchainVisualHost +{ + EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; } + IDisposable BeginTransaction(); + + /// + /// Attaches the swapchain as the content of the window's visual. + /// Called with the transaction held, only when the swapchain is (re)created. + /// + void SetSwapchainContent(IUnknown swapchain); + + void ResizeIfNeeded(PixelSize size); + void ApplyEffects(CompositionTransparencyLevel transparencyLevel, PlatformThemeVariant themeVariant); +} + +/// +/// Renders into a DXGI flip-model swapchain created for composition and attached +/// as visual content via . Unlike the composition +/// drawing surface targets, this allows DWM to use optimized presentation modes +/// (independent flip) for fullscreen windows. +/// +internal unsafe class CompositionSwapchainRenderTarget : IDirect3D11TextureRenderTarget, IDirect3D11TextureRenderTarget2 +{ + private static readonly Guid IID_ID3D11Texture2D = Guid.Parse("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); + + private readonly IPlatformGraphicsContext _context; + private readonly ISwapchainVisualHost _host; + private readonly IUnknown _d3dDevice; + private readonly IDXGIDevice _dxgiDevice; + private readonly IDXGIFactory2 _dxgiFactory; + private IDXGISwapChain1? _swapChain; + private PixelSize _size; + private bool _isSwapchainTransparencyCapable; + private bool _lost; + + public CompositionSwapchainRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice, + ISwapchainVisualHost host) + { + _context = context; + _host = host; + + try + { + _d3dDevice = MicroComRuntime.CreateProxyFor(d3dDevice, false).CloneReference(); + _dxgiDevice = _d3dDevice.QueryInterface(); + using var adapter = _dxgiDevice.Adapter; + var factoryGuid = MicroComRuntime.GetGuidFor(typeof(IDXGIFactory2)); + _dxgiFactory = MicroComRuntime.CreateProxyFor(adapter.GetParent(&factoryGuid), true); + } + catch + { + _dxgiFactory?.Dispose(); + _dxgiDevice?.Dispose(); + _d3dDevice?.Dispose(); + throw; + } + } + + public void Dispose() + { + _swapChain?.Dispose(); + _dxgiFactory.Dispose(); + _dxgiDevice.Dispose(); + _d3dDevice.Dispose(); + } + + public PlatformRenderTargetState State => + _context.IsLost || _lost ? PlatformRenderTargetState.Corrupted : PlatformRenderTargetState.Ready; + + private void CreateSwapchain(PixelSize size, bool isTransparency) + { + var desc = new DXGI_SWAP_CHAIN_DESC1 + { + Width = (uint)size.Width, + Height = (uint)size.Height, + Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, + SampleDesc = { Count = 1, Quality = 0 }, + BufferUsage = DxgiRenderTarget.DXGI_USAGE_RENDER_TARGET_OUTPUT, + BufferCount = 2, + Scaling = DXGI_SCALING.DXGI_SCALING_STRETCH, + // FLIP_DISCARD is not available on Windows 8.x + SwapEffect = Win32Platform.WindowsVersion >= PlatformConstants.Windows10 ? + DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_DISCARD : + DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, + // Premultiplied alpha increases the DWM composition cost and prevents optimized + // presentation modes, so it's only used when the window is actually transparent, + // see https://github.com/AvaloniaUI/Avalonia/issues/20643 + AlphaMode = isTransparency ? + DXGI_ALPHA_MODE.DXGI_ALPHA_MODE_PREMULTIPLIED : + DXGI_ALPHA_MODE.DXGI_ALPHA_MODE_IGNORE + }; + + var swapChain = _dxgiFactory.CreateSwapChainForComposition(_dxgiDevice, &desc, null); + try + { + _host.SetSwapchainContent(swapChain); + } + catch + { + swapChain.Dispose(); + throw; + } + + _swapChain = swapChain; + _isSwapchainTransparencyCapable = isTransparency; + _size = size; + } + + IDirect3D11TextureRenderTargetRenderSession IDirect3D11TextureRenderTarget.BeginDraw() + { + var fallbackSceneInfo = new IRenderTarget.RenderTargetSceneInfo(_host.WindowInfo.Size, + _host.WindowInfo.Scaling, CompositionTransparencyLevel.None); + return BeginDraw(fallbackSceneInfo); + } + + public IDirect3D11TextureRenderTargetRenderSession BeginDraw(IRenderTarget.RenderTargetSceneInfo sceneInfo) + { + if (State.IsCorrupted) + throw new RenderTargetCorruptedException(); + var transaction = _host.BeginTransaction(); + try + { + var isTransparency = sceneInfo.TransparencyLevel != CompositionTransparencyLevel.None; + // Unlike hwnd ones, composition swapchains require explicit non-zero dimensions + var size = new PixelSize(Math.Max(1, sceneInfo.Size.Width), Math.Max(1, sceneInfo.Size.Height)); + + IUnknown texture; + try + { + if (_swapChain is null || _isSwapchainTransparencyCapable != isTransparency) + { + // The alpha mode can't be changed by ResizeBuffers, so the swapchain + // is recreated when the transparency level changes + _swapChain?.Dispose(); + _swapChain = null; + CreateSwapchain(size, isTransparency); + } + else if (_size != size) + { + // All backbuffer references are released at this point: the previous + // session disposes its texture proxy before Present, and the EGL surface + // wrapping it is created and destroyed within a frame + _swapChain.ResizeBuffers(2, (uint)size.Width, (uint)size.Height, + DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, 0); + _size = size; + } + + _host.ResizeIfNeeded(size); + _host.ApplyEffects(sceneInfo.TransparencyLevel, + (sceneInfo.TopLevelSpecificSceneInfo as Win32TopLevelSceneInfo)?.ThemeVariant ?? + PlatformThemeVariant.Light); + + var iid = IID_ID3D11Texture2D; + texture = MicroComRuntime.CreateProxyFor(_swapChain!.GetBuffer(0, &iid), true); + } + catch (Exception e) + { + _lost = true; + throw new RenderTargetCorruptedException(e); + } + + using (texture) + { + var session = new Session(_swapChain, texture, transaction, size, sceneInfo.Scaling); + transaction = null; + return session; + } + } + finally + { + transaction?.Dispose(); + } + } + + private class Session : IDirect3D11TextureRenderTargetRenderSession + { + private readonly IDXGISwapChain1 _swapChain; + private readonly IUnknown _texture; + private readonly IDisposable _transaction; + private readonly PixelSize _size; + private readonly double _scaling; + + public Session(IDXGISwapChain1 swapChain, IUnknown texture, IDisposable transaction, + PixelSize size, double scaling) + { + _swapChain = swapChain.CloneReference(); + _texture = texture.CloneReference(); + _transaction = transaction; + _size = size; + _scaling = scaling; + } + + public void Dispose() + { + try + { + // The backbuffer reference must be released before the next ResizeBuffers; + // Present goes before the transaction is committed, so a freshly attached + // swapchain always has a frame by the time it becomes visible + _texture.Dispose(); + _swapChain.Present(0, 0); + _swapChain.Dispose(); + } + finally + { + _transaction.Dispose(); + } + } + + public IntPtr D3D11Texture2D => _texture.GetNativeIntPtr(); + public PixelSize Size => _size; + public PixelPoint Offset => default; + public double Scaling => _scaling; + } +} diff --git a/src/Windows/Avalonia.Win32/IBlurHost.cs b/src/Windows/Avalonia.Win32/IBlurHost.cs index fcaf58eaacd..96ddd334a11 100644 --- a/src/Windows/Avalonia.Win32/IBlurHost.cs +++ b/src/Windows/Avalonia.Win32/IBlurHost.cs @@ -12,6 +12,4 @@ internal enum BlurEffect internal interface ICompositionEffectsSurface { bool IsBlurSupported(BlurEffect effect); - - void SetBlur(BlurEffect enable); } diff --git a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs index 351b374e3e4..674fb83e2dd 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformOptions.cs @@ -134,6 +134,17 @@ public class Win32PlatformOptions Win32CompositionMode.WinUIComposition, Win32CompositionMode.DirectComposition, Win32CompositionMode.RedirectionSurface }; + /// + /// When resolves to or + /// , render into a DXGI flip-model swapchain attached + /// as composition visual content instead of a composition drawing surface. + /// This allows DWM to use optimized presentation modes (independent flip) for fullscreen windows + /// and reduces latency, at the cost of every window owning two full-size buffers and, on the + /// WinUI composition path, looser visual synchronization during interactive resize. + /// The default value is null, which lets the framework decide (currently: disabled). + /// + public bool? UseCompositionSwapchain { get; set; } + /// /// When is set to , create rounded corner blur brushes /// If set to null the brushes will be created using default settings (sharp corners) diff --git a/src/Windows/Avalonia.Win32/Win32TopLevelSceneInfo.cs b/src/Windows/Avalonia.Win32/Win32TopLevelSceneInfo.cs new file mode 100644 index 00000000000..969c9fba14e --- /dev/null +++ b/src/Windows/Avalonia.Win32/Win32TopLevelSceneInfo.cs @@ -0,0 +1,10 @@ +using Avalonia.Platform; + +namespace Avalonia.Win32; + +/// +/// An immutable bag with Win32-specific per-frame scene information, published via +/// and consumed by render targets +/// on the render thread. +/// +internal sealed record Win32TopLevelSceneInfo(PlatformThemeVariant ThemeVariant); diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs index d720e525d3a..49ca94571a0 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs @@ -1,13 +1,17 @@ using System; +using System.Diagnostics; using System.Numerics; using System.Threading; using Avalonia.OpenGL.Egl; +using Avalonia.Platform; using Avalonia.Reactive; +using Avalonia.Rendering.Composition; +using Avalonia.Win32.DirectX; using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition; -internal class WinUiCompositedWindow : IDisposable +internal class WinUiCompositedWindow : ISwapchainVisualHost, IDisposable { public EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; } private readonly WinUiCompositionShared _shared; @@ -17,13 +21,16 @@ internal class WinUiCompositedWindow : IDisposable private readonly IVisual _blur; private readonly IVisual _visual; private PixelSize _size; + private BlurEffect? _appliedBlurEffect; private readonly ICompositionSurfaceBrush _surfaceBrush; private readonly ICompositionTarget _target; + private ICompositionSurface? _swapchainSurface; public void Dispose() { lock (_shared.SyncRoot) { + _swapchainSurface?.Dispose(); _compositionRoundedRectangleGeometry?.Dispose(); _blur.Dispose(); _micaLight?.Dispose(); @@ -80,18 +87,41 @@ public WinUiCompositedWindow(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInf public void SetSurface(ICompositionSurface surface) => _surfaceBrush.SetSurface(surface); - public void SetBlur(BlurEffect blurEffect) + public void SetSwapchainContent(IUnknown swapchain) { - lock (_shared.SyncRoot) + Debug.Assert(Monitor.IsEntered(_shared.SyncRoot)); + + using var interop = _shared.Compositor.QueryInterface(); + var surface = interop.CreateCompositionSurfaceForSwapChain(swapchain); + _surfaceBrush.SetSurface(surface); + _swapchainSurface?.Dispose(); + _swapchainSurface = surface; + } + + // Called from the render thread with the transaction (SyncRoot) held, so effect changes + // are applied within the same composition batch as the frame they belong to. + public void ApplyEffects(CompositionTransparencyLevel transparencyLevel, PlatformThemeVariant themeVariant) + { + Debug.Assert(Monitor.IsEntered(_shared.SyncRoot)); + + var blurEffect = transparencyLevel switch { - _blur.SetIsVisible(blurEffect == BlurEffect.Acrylic - || (blurEffect == BlurEffect.MicaLight && _micaLight == null) || - (blurEffect == BlurEffect.MicaDark && _micaDark == null) ? - 1 : - 0); - _micaLight?.SetIsVisible(blurEffect == BlurEffect.MicaLight ? 1 : 0); - _micaDark?.SetIsVisible(blurEffect == BlurEffect.MicaDark ? 1 : 0); - } + CompositionTransparencyLevel.AcrylicBlur => BlurEffect.Acrylic, + CompositionTransparencyLevel.Mica when themeVariant == PlatformThemeVariant.Dark => BlurEffect.MicaDark, + CompositionTransparencyLevel.Mica => BlurEffect.MicaLight, + _ => BlurEffect.None + }; + if (_appliedBlurEffect == blurEffect) + return; + + _blur.SetIsVisible(blurEffect == BlurEffect.Acrylic + || (blurEffect == BlurEffect.MicaLight && _micaLight == null) || + (blurEffect == BlurEffect.MicaDark && _micaDark == null) ? + 1 : + 0); + _micaLight?.SetIsVisible(blurEffect == BlurEffect.MicaLight ? 1 : 0); + _micaDark?.SetIsVisible(blurEffect == BlurEffect.MicaDark ? 1 : 0); + _appliedBlurEffect = blurEffect; } public IDisposable BeginTransaction() @@ -102,14 +132,13 @@ public IDisposable BeginTransaction() public void ResizeIfNeeded(PixelSize size) { - lock (_shared.SyncRoot) + Debug.Assert(Monitor.IsEntered(_shared.SyncRoot)); + + if (_size != size) { - if (_size != size) - { - _visual.SetSize(new Vector2(size.Width, size.Height)); - _compositionRoundedRectangleGeometry?.SetSize(new Vector2(size.Width, size.Height)); - _size = size; - } + _visual.SetSize(new Vector2(size.Width, size.Height)); + _compositionRoundedRectangleGeometry?.SetSize(new Vector2(size.Width, size.Height)); + _size = size; } } } diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index 179d0c20606..a52ca03a77a 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs @@ -14,7 +14,6 @@ internal class WinUiCompositedWindowSurface : IDirect3D11TexturePlatformSurface, private readonly WinUiCompositionShared _shared; private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; private WinUiCompositedWindow? _window; - private BlurEffect _blurEffect; public WinUiCompositedWindowSurface(WinUiCompositionShared shared, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info) { @@ -29,10 +28,11 @@ IDirect3D11TextureRenderTarget IDirect3D11TexturePlatformSurface.CreateRenderTar public IDirect3D11TextureRenderTarget2 CreateRenderTarget(IPlatformGraphicsContext context, IntPtr d3dDevice) { - var cornerRadius = AvaloniaLocator.Current.GetService() - ?.WinUICompositionBackdropCornerRadius; - _window ??= new WinUiCompositedWindow(_info, _shared, cornerRadius); - _window.SetBlur(_blurEffect); + var options = AvaloniaLocator.Current.GetService(); + _window ??= new WinUiCompositedWindow(_info, _shared, options?.WinUICompositionBackdropCornerRadius); + + if (options?.UseCompositionSwapchain ?? false) + return new CompositionSwapchainRenderTarget(context, d3dDevice, _window); return new WinUiCompositedWindowRenderTarget(context, _window, d3dDevice, _shared.Compositor); } @@ -51,12 +51,6 @@ public void Dispose() BlurEffect.MicaDark => Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion, _ => false }; - - public void SetBlur(BlurEffect enable) - { - _blurEffect = enable; - _window?.SetBlur(enable); - } } internal class WinUiCompositedWindowRenderTarget : IDirect3D11TextureRenderTarget, IDirect3D11TextureRenderTarget2 @@ -171,6 +165,9 @@ public unsafe IDirect3D11TextureRenderTargetRenderSession BeginDraw( var size = sceneInfo.Size; var scale = sceneInfo.Scaling; _window.ResizeIfNeeded(size); + _window.ApplyEffects(sceneInfo.TransparencyLevel, + (sceneInfo.TopLevelSpecificSceneInfo as Win32TopLevelSceneInfo)?.ThemeVariant ?? + PlatformThemeVariant.Light); _window.SetSurface(_surface); void* pTexture; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9fbba4e20bf..d2d5b19116f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -118,6 +118,7 @@ internal partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindow private static POINTER_INFO[]? s_historyInfos; private static MOUSEMOVEPOINT[]? s_mouseHistoryInfos; private PlatformThemeVariant _currentThemeVariant; + private Win32TopLevelSceneInfo _sceneInfo = new(PlatformThemeVariant.Light); public WindowImpl() { @@ -216,6 +217,10 @@ internal IInputRoot Owner public Action? TransparencyLevelChanged { get; set; } + public object? TopLevelSpecificSceneInfo => _sceneInfo; + + public Action? TopLevelSpecificSceneInfoChanged { get; set; } + public Thickness BorderThickness { get @@ -437,43 +442,29 @@ private bool IsSupported(WindowTransparencyLevel level) return false; } + // The composition effects themselves are applied by the render target on the render thread, + // based on the transparency level and theme variant it receives with RenderTargetSceneInfo. + // Here we only adjust the DWM window attributes that have to be set from the UI thread. + private bool SetTransparencyTransparent() { - if (CompositionEffectsSurface is { } surface) - { - surface.SetBlur(BlurEffect.None); + if (CompositionEffectsSurface is not null) return true; - } - else - { - return SetLegacyTransparency(true); - } + + return SetLegacyTransparency(true); } private bool SetTransparencyAcrylicBlur() { SetUseHostBackdropBrush(true); SetLegacyTransparency(false); - - CompositionEffectsSurface!.SetBlur(BlurEffect.Acrylic); return true; } - /// - /// Sets the transparency mica - /// - /// private bool SetTransparencyMica() { SetUseHostBackdropBrush(false); SetLegacyTransparency(false); - - CompositionEffectsSurface!.SetBlur(_currentThemeVariant switch - { - PlatformThemeVariant.Light => BlurEffect.MicaLight, - PlatformThemeVariant.Dark => BlurEffect.MicaDark, - _ => throw new ArgumentOutOfRangeException() - }); return true; } @@ -951,6 +942,11 @@ private void EnsureTopmost() public unsafe void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { _currentThemeVariant = themeVariant ?? Win32Platform.Instance.PlatformSettings.GetColorValues().ThemeVariant; + if (_sceneInfo.ThemeVariant != _currentThemeVariant) + { + _sceneInfo = new Win32TopLevelSceneInfo(_currentThemeVariant); + TopLevelSpecificSceneInfoChanged?.Invoke(_sceneInfo); + } if (Win32Platform.WindowsVersion.Build >= 22000) { var pvUseBackdropBrush = _currentThemeVariant == PlatformThemeVariant.Dark ? 1 : 0;