Skip to content

Commit

Permalink
Added Compositor.CreateCompositionVisualSnapshot API (#16599)
Browse files Browse the repository at this point in the history
* Added Compositor.CreateCompositionVisualSnapshot API

* Hurr durr api compat in [Unstable] interface

---------

Co-authored-by: Max Katz <maxkatz6@outlook.com>
#Conflicts:
#	api/Avalonia.Skia.nupkg.xml
#	samples/ControlCatalog/Pages/OpenGlPage.xaml
#	samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  • Loading branch information
kekekeks authored and maxkatz6 committed Aug 12, 2024
1 parent cb06181 commit 3e61cdf
Showing 25 changed files with 315 additions and 63 deletions.
10 changes: 8 additions & 2 deletions api/Avalonia.Skia.nupkg.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext.TryGetGrContext</Target>
<Left>baseline/netstandard2.0/Avalonia.Skia.dll</Left>
<Right>target/netstandard2.0/Avalonia.Skia.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Skia.ISkiaSharpApiLease.TryLeasePlatformGraphicsApi</Target>
<Left>baseline/netstandard2.0/Avalonia.Skia.dll</Left>
<Right>target/netstandard2.0/Avalonia.Skia.dll</Right>
</Suppression>
</Suppressions>
</Suppressions>
6 changes: 6 additions & 0 deletions src/Avalonia.Base/Platform/IDrawingContextImpl.cs
Original file line number Diff line number Diff line change
@@ -229,4 +229,10 @@ public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl
/// </summary>
bool CanBlit { get; }
}

public interface IDrawingContextLayerWithRenderContextAffinityImpl : IDrawingContextLayerImpl
{
bool HasRenderContextAffinity { get; }
IBitmapImpl CreateNonAffinedSnapshot();
}
}
8 changes: 8 additions & 0 deletions src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
Original file line number Diff line number Diff line change
@@ -215,6 +215,14 @@ public interface IPlatformRenderInterfaceContext : IOptionalFeatureProvider, IDi
/// </param>
/// <returns>An <see cref="IRenderTarget"/>.</returns>
IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces);

/// <summary>
/// Creates an offscreen render target
/// </summary>
/// <param name="pixelSize">The size, in pixels, of the render target</param>
/// <param name="scaling">The scaling which will be reported by IBitmap.Dpi</param>
/// <returns></returns>
IDrawingContextLayerImpl CreateOffscreenRenderTarget(PixelSize pixelSize, double scaling);

/// <summary>
/// Indicates that the context is no longer usable. This method should be thread-safe
44 changes: 44 additions & 0 deletions src/Avalonia.Base/Platform/IScopedResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Threading;

namespace Avalonia.Platform;

public interface IScopedResource<T> : IDisposable
{
public T Value { get; }
}

public class ScopedResource<T> : IScopedResource<T>
{
private int _disposed = 0;
private T _value;
private Action? _dispose;
private ScopedResource(T value, Action dispose)
{
_value = value;
_dispose = dispose;
}

public static IScopedResource<T> Create(T value, Action dispose) => new ScopedResource<T>(value, dispose);

public void Dispose()
{
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
{
var disp = _dispose!;
_value = default!;
_dispose = null;
disp();
}
}

public T Value
{
get
{
if (_disposed == 1)
throw new ObjectDisposedException(this.GetType().FullName);
return _value;
}
}
}
45 changes: 33 additions & 12 deletions src/Avalonia.Base/Rendering/Composition/Compositor.cs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Server;
@@ -34,6 +35,7 @@ public partial class Compositor
private CompositionBatch? _pendingBatch;
private readonly object _pendingBatchLock = new();
private readonly List<Action> _pendingServerCompositorJobs = new();
private readonly List<Action> _pendingServerCompositorPostTargetJobs = new();
private DiagnosticTextRenderer? _diagnosticTextRenderer;
private readonly Action _triggerCommitRequested;

@@ -170,14 +172,23 @@ CompositionBatch CommitCore()
_disposeOnNextBatch.Clear();
}

if (_pendingServerCompositorJobs.Count > 0)

static void SerializeServerJobs(BatchStreamWriter writer, List<Action> list, object startMarker, object endMarker)
{
writer.WriteObject(ServerCompositor.RenderThreadJobsStartMarker);
foreach (var job in _pendingServerCompositorJobs)
writer.WriteObject(job);
writer.WriteObject(ServerCompositor.RenderThreadJobsEndMarker);
if (list.Count > 0)
{
writer.WriteObject(startMarker);
foreach (var job in list)
writer.WriteObject(job);
writer.WriteObject(endMarker);
}
list.Clear();
}
_pendingServerCompositorJobs.Clear();

SerializeServerJobs(writer, _pendingServerCompositorJobs, ServerCompositor.RenderThreadJobsStartMarker,
ServerCompositor.RenderThreadJobsEndMarker);
SerializeServerJobs(writer, _pendingServerCompositorPostTargetJobs, ServerCompositor.RenderThreadPostTargetJobsStartMarker,
ServerCompositor.RenderThreadPostTargetJobsEndMarker);
}

_nextCommit.CommittedAt = Server.Clock.Elapsed;
@@ -227,21 +238,21 @@ public void RequestCompositionUpdate(Action action)
RequestCommitAsync();
}

internal void PostServerJob(Action job)
internal void PostServerJob(Action job, bool postTarget = false)
{
Dispatcher.VerifyAccess();
_pendingServerCompositorJobs.Add(job);
(postTarget ? _pendingServerCompositorPostTargetJobs : _pendingServerCompositorJobs).Add(job);
RequestCommitAsync();
}

internal Task InvokeServerJobAsync(Action job) =>
internal Task InvokeServerJobAsync(Action job, bool postTarget = false) =>
InvokeServerJobAsync<object?>(() =>
{
job();
return null;
});
}, postTarget);

internal Task<T> InvokeServerJobAsync<T>(Func<T> job)
internal Task<T> InvokeServerJobAsync<T>(Func<T> job, bool postTarget = false)
{
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
PostServerJob(() =>
@@ -254,7 +265,7 @@ internal Task<T> InvokeServerJobAsync<T>(Func<T> job)
{
tcs.TrySetException(e);
}
});
}, postTarget);
return tcs.Task;
}

@@ -275,6 +286,16 @@ internal ValueTask<IReadOnlyDictionary<Type, object>> GetRenderInterfacePublicFe
(await GetRenderInterfacePublicFeatures().ConfigureAwait(false)).TryGetValue(featureType, out var rv);
return rv;
}

public async Task<Bitmap> CreateCompositionVisualSnapshot(CompositionVisual visual, double scaling)
{
if (visual.Compositor != this)
throw new InvalidOperationException();
if (visual.Root == null)
throw new InvalidOperationException();
var impl = await InvokeServerJobAsync(() => _server.CreateCompositionVisualSnapshot(visual.Server, scaling), true);
return new Bitmap(RefCountable.Create(impl));
}

/// <summary>
/// Attempts to query for GPU interop feature from the platform render interface
Original file line number Diff line number Diff line change
@@ -16,14 +16,13 @@ internal partial class ServerCompositionContainerVisual : ServerCompositionVisua
private LtrbRect? _transformedContentBounds;
private IImmutableEffect? _oldEffect;

protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
protected override void RenderCore(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
{
base.RenderCore(canvas, currentTransformedClip, dirtyRects);
base.RenderCore(context, currentTransformedClip);

foreach (var ch in Children)
{
ch.Render(canvas, currentTransformedClip, dirtyRects);
ch.Render(context, currentTransformedClip);
}
}

Original file line number Diff line number Diff line change
@@ -40,17 +40,15 @@ protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpa
base.DeserializeChangesCore(reader, committedAt);
}

protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
protected override void RenderCore(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
{
if (_renderCommands != null
&& currentTransformedClip.Intersects(TransformedOwnContentBounds)
&& dirtyRects.Intersects(TransformedOwnContentBounds))
&& context.ShouldRenderOwnContent(this, currentTransformedClip))
{
_renderCommands.Render(canvas);
_renderCommands.Render(context.Canvas);
}

base.RenderCore(canvas, currentTransformedClip, dirtyRects);
base.RenderCore(context, currentTransformedClip);
}

public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated();
Original file line number Diff line number Diff line change
@@ -5,18 +5,17 @@ namespace Avalonia.Rendering.Composition.Server;

internal partial class ServerCompositionExperimentalAcrylicVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
protected override void RenderCore(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
{
var cornerRadius = CornerRadius;
canvas.DrawRectangle(
context.Canvas.DrawRectangle(
Material,
new RoundedRect(
new Rect(0, 0, Size.X, Size.Y),
cornerRadius.TopLeft, cornerRadius.TopRight,
cornerRadius.BottomRight, cornerRadius.BottomLeft));

base.RenderCore(canvas, currentTransformedClip, dirtyRects);
base.RenderCore(context, currentTransformedClip);
}

public override LtrbRect OwnContentBounds => new(0, 0, Size.X, Size.Y);
Original file line number Diff line number Diff line change
@@ -5,9 +5,8 @@ namespace Avalonia.Rendering.Composition.Server;

internal partial class ServerCompositionSolidColorVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
protected override void RenderCore(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
{
canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y));
context.Canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y));
}
}
Original file line number Diff line number Diff line change
@@ -5,8 +5,7 @@ namespace Avalonia.Rendering.Composition.Server;

internal partial class ServerCompositionSurfaceVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
protected override void RenderCore(ServerVisualRenderContext context, LtrbRect currentTransformedClip)
{
if (Surface == null)
return;
@@ -15,7 +14,7 @@ protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRec
var bmp = Surface.Bitmap.Item;

//TODO: add a way to always render the whole bitmap instead of just assuming 96 DPI
canvas.DrawBitmap(Surface.Bitmap.Item, 1, new Rect(bmp.PixelSize.ToSize(1)), new Rect(
context.Canvas.DrawBitmap(Surface.Bitmap.Item, 1, new Rect(bmp.PixelSize.ToSize(1)), new Rect(
new Size(Size.X, Size.Y)));
}

Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ public void AddDirtyRect(LtrbRect rect)
public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(new(rect), Scaling).ToRect();
public LtrbRect SnapToDevicePixels(LtrbRect rect) => SnapToDevicePixels(rect, Scaling);

private static LtrbRect SnapToDevicePixels(LtrbRect rect, double scale)
public static LtrbRect SnapToDevicePixels(LtrbRect rect, double scale)
{
return new LtrbRect(
Math.Floor(rect.Left * scale) / scale,
Original file line number Diff line number Diff line change
@@ -214,7 +214,10 @@ void RenderRootToContextWithClip(IDrawingContextImpl context, ServerCompositionV
context.PushLayer(DirtyRects.CombinedRect.ToRectUnscaled());

using (var proxy = new CompositorDrawingContextProxy(context))
root.Render(proxy, null, DirtyRects);
{
var ctx = new ServerVisualRenderContext(proxy, DirtyRects, false);
root.Render(ctx, null);
}

if (useLayerClip)
context.PopLayer();
Original file line number Diff line number Diff line change
@@ -22,24 +22,22 @@ partial class ServerCompositionVisual : ServerObject
private LtrbRect? _transformedClipBounds;
private LtrbRect _combinedTransformedClipBounds;

protected virtual void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
protected virtual void RenderCore(ServerVisualRenderContext canvas, LtrbRect currentTransformedClip)
{
}

public void Render(CompositorDrawingContextProxy canvas, LtrbRect? parentTransformedClip, IDirtyRectTracker dirtyRects)
public void Render(ServerVisualRenderContext context, LtrbRect? parentTransformedClip)
{
if (Visible == false || IsVisibleInFrame == false)
return;
if (Opacity == 0)
return;

var canvas = context.Canvas;

var currentTransformedClip = parentTransformedClip.HasValue
? parentTransformedClip.Value.Intersect(_combinedTransformedClipBounds)
: _combinedTransformedClipBounds;
if (currentTransformedClip.IsZeroSize)
return;
if(!dirtyRects.Intersects(currentTransformedClip))
if(!context.ShouldRender(this, currentTransformedClip))
return;

Root!.RenderedVisuals++;
@@ -49,12 +47,16 @@ public void Render(CompositorDrawingContextProxy canvas, LtrbRect? parentTransfo

if (AdornedVisual != null)
{
// Adorners are currently not supported in detached rendering mode
if(context.DetachedRendering)
return;

canvas.Transform = Matrix.Identity;
if (AdornerIsClipped)
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds.ToRect());
}
var transform = GlobalTransformMatrix;
canvas.Transform = transform;

using var _ = context.SetOrPushTransform(this);

var applyRenderOptions = RenderOptions != default;

@@ -72,7 +74,7 @@ public void Render(CompositorDrawingContextProxy canvas, LtrbRect? parentTransfo
if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);

RenderCore(canvas, currentTransformedClip, dirtyRects);
RenderCore(context, currentTransformedClip);

if (OpacityMaskBrush != null)
canvas.PopOpacityMask();
Loading

0 comments on commit 3e61cdf

Please sign in to comment.