Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Avalonia.Base/Platform/IRenderTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ public interface IRenderTarget : IDisposable
/// </summary>
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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Avalonia.Base/composition-schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<Manual Name="Avalonia.Media.IImmutableBrush" Passthrough="true"/>
<Manual Name="Avalonia.Media.IImmutableEffect" Passthrough="true"/>
<Manual Name="Avalonia.Media.Immutable.ImmutableDashStyle" Passthrough="true"/>
<Manual Name="object" Passthrough="true"/>
<Manual Name="CompositionSurface" />
<Manual Name="CompositionDrawingSurface" />
<Object Name="CompositionVisual" Abstract="true">
Expand Down Expand Up @@ -60,6 +61,7 @@
<Property Name="Scaling" Type="double"/>
<Property Name="Size" Type="Size" />
<Property Name="TransparencyLevel" Type="CompositionTransparencyLevel" />
<Property Name="TopLevelSpecificSceneInfo" Type="object?" />
</Object>
<KeyFrameAnimation Name="Scalar" Type="float"/>
<KeyFrameAnimation Name="Double" Type="double"/>
Expand Down
12 changes: 12 additions & 0 deletions src/Avalonia.Controls/Platform/ITopLevelImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ public interface ITopLevelImpl : IOptionalFeatureProvider, IDisposable
/// </summary>
Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }

/// <summary>
/// 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.
/// </summary>
object? TopLevelSpecificSceneInfo => null;

/// <summary>
/// Gets or sets a method called when <see cref="TopLevelSpecificSceneInfo"/> changes.
/// </summary>
Action<object?>? TopLevelSpecificSceneInfoChanged { get => null; set { } }

/// <summary>
/// Gets the compositor that's compatible with the toplevel
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Avalonia.Controls/TopLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ internal TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyRes

_source.Renderer.CompositionTarget.TransparencyLevel =
ToCompositionTransparencyLevel(_actualTransparencyLevel);
_source.Renderer.CompositionTarget.TopLevelSpecificSceneInfo = impl.TopLevelSpecificSceneInfo;


_accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver);
Expand All @@ -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<WindowTransparencyLevel>()));

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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; }
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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<Win32PlatformOptions>()?.UseCompositionSwapchain ?? false)
return new CompositionSwapchainRenderTarget(context, d3dDevice, _window);

return new DirectCompositedWindowRenderTarget(context, d3dDevice, _shared, _window);
}
Expand All @@ -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
Expand Down
226 changes: 226 additions & 0 deletions src/Windows/Avalonia.Win32/DirectX/CompositionSwapchainRenderTarget.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using System;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using MicroCom.Runtime;

namespace Avalonia.Win32.DirectX;

/// <summary>
/// A composed window that can display a DXGI swapchain as its content.
/// </summary>
internal interface ISwapchainVisualHost
{
EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; }
IDisposable BeginTransaction();

/// <summary>
/// Attaches the swapchain as the content of the window's visual.
/// Called with the transaction held, only when the swapchain is (re)created.
/// </summary>
void SetSwapchainContent(IUnknown swapchain);

void ResizeIfNeeded(PixelSize size);
void ApplyEffects(CompositionTransparencyLevel transparencyLevel, PlatformThemeVariant themeVariant);
}

/// <summary>
/// Renders into a DXGI flip-model swapchain created for composition and attached
/// as visual content via <see cref="ISwapchainVisualHost"/>. Unlike the composition
/// drawing surface targets, this allows DWM to use optimized presentation modes
/// (independent flip) for fullscreen windows.
/// </summary>
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<IUnknown>(d3dDevice, false).CloneReference();
_dxgiDevice = _d3dDevice.QueryInterface<IDXGIDevice>();
using var adapter = _dxgiDevice.Adapter;
var factoryGuid = MicroComRuntime.GetGuidFor(typeof(IDXGIFactory2));
_dxgiFactory = MicroComRuntime.CreateProxyFor<IDXGIFactory2>(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
};
Comment on lines +83 to +102

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: there is some duplication with WinUI embedded backend -

var desc = new DXGI_SWAP_CHAIN_DESC1
{
Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc = new DXGI_SAMPLE_DESC { Count = 1, Quality = 0 },
BufferUsage = DxgiRenderTarget.DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount = 2,
SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
AlphaMode = DXGI_ALPHA_MODE.DXGI_ALPHA_MODE_PREMULTIPLIED,
Width = (uint)pixelSize.Width,
Height = (uint)pixelSize.Height,
Flags = 0
};

I think it's fine to keep these separated, winui one is angle+winui specific. But just FYI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose WinUI backend could implement ISwapchainVisualHost and reuse part


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<IUnknown>(_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;
}
}
2 changes: 0 additions & 2 deletions src/Windows/Avalonia.Win32/IBlurHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,4 @@ internal enum BlurEffect
internal interface ICompositionEffectsSurface
{
bool IsBlurSupported(BlurEffect effect);

void SetBlur(BlurEffect enable);
}
Loading
Loading