Browse Source

Added Compositor.CreateCompositionVisualSnapshot API (#16599)

* 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
release/11.1.3
Nikita Tsukanov 2 years ago
committed by Max Katz
parent
commit
3e61cdf10b
  1. 10
      api/Avalonia.Skia.nupkg.xml
  2. 6
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  3. 8
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  4. 44
      src/Avalonia.Base/Platform/IScopedResource.cs
  5. 45
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  6. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
  7. 10
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  8. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionExperimentalAcrylicVisual.cs
  9. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs
  10. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs
  11. 2
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.DirtyRects.cs
  12. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  13. 22
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  14. 40
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs
  15. 25
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  16. 9
      src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs
  17. 75
      src/Avalonia.Base/Rendering/Composition/Server/ServerVisualRenderContext.cs
  18. 6
      src/Avalonia.Controls/BorderVisual.cs
  19. 3
      src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  20. 1
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  21. 4
      src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs
  22. 2
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  23. 22
      src/Skia/Avalonia.Skia/SkiaBackendContext.cs
  24. 11
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  25. 4
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

10
api/Avalonia.Skia.nupkg.xml

@ -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
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -229,4 +229,10 @@ namespace Avalonia.Platform
/// </summary>
bool CanBlit { get; }
}
public interface IDrawingContextLayerWithRenderContextAffinityImpl : IDrawingContextLayerImpl
{
bool HasRenderContextAffinity { get; }
IBitmapImpl CreateNonAffinedSnapshot();
}
}

8
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -215,6 +215,14 @@ namespace Avalonia.Platform
/// </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
src/Avalonia.Base/Platform/IScopedResource.cs

@ -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
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -5,6 +5,7 @@ using Avalonia.Animation;
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 @@ namespace Avalonia.Rendering.Composition
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 @@ namespace Avalonia.Rendering.Composition
_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 @@ namespace Avalonia.Rendering.Composition
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 @@ namespace Avalonia.Rendering.Composition
{
tcs.TrySetException(e);
}
});
}, postTarget);
return tcs.Task;
}
@ -275,6 +286,16 @@ namespace Avalonia.Rendering.Composition
(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

7
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs

@ -16,14 +16,13 @@ namespace Avalonia.Rendering.Composition.Server
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);
}
}

10
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@ -40,17 +40,15 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
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();

7
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionExperimentalAcrylicVisual.cs

@ -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);

5
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs

@ -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));
}
}

5
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs

@ -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 @@ internal partial class ServerCompositionSurfaceVisual
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)));
}

2
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.DirtyRects.cs

@ -35,7 +35,7 @@ internal partial class ServerCompositionTarget
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,

5
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -214,7 +214,10 @@ namespace Avalonia.Rendering.Composition.Server
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();

22
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -22,24 +22,22 @@ namespace Avalonia.Rendering.Composition.Server
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 @@ namespace Avalonia.Rendering.Composition.Server
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 @@ namespace Avalonia.Rendering.Composition.Server
if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
RenderCore(canvas, currentTransformedClip, dirtyRects);
RenderCore(context, currentTransformedClip);
if (OpacityMaskBrush != null)
canvas.PopOpacityMask();

40
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs

@ -47,4 +47,44 @@ internal partial class ServerCompositor
lock (_renderInterfaceFeaturesUserApiLock)
return _renderInterfaceFeatureCache ??= RenderInterface.Value.PublicFeatures;
}
public IBitmapImpl CreateCompositionVisualSnapshot(ServerCompositionVisual visual,
double scaling)
{
using (RenderInterface.EnsureCurrent())
{
var pixelSize = PixelSize.FromSize(new Size(visual.Size.X, visual.Size.Y), scaling);
var scaleTransform = Matrix.CreateScale(scaling, scaling);
var invertRootTransform = visual.CombinedTransformMatrix.Invert();
IDrawingContextLayerImpl? target = null;
try
{
target = RenderInterface.Value.CreateOffscreenRenderTarget(pixelSize, scaling);
using (var canvas = target.CreateDrawingContext(false))
{
var proxy = new CompositorDrawingContextProxy(canvas)
{
PostTransform = invertRootTransform * scaleTransform,
Transform = Matrix.Identity
};
var ctx = new ServerVisualRenderContext(proxy, null, true);
visual.Render(ctx, null);
}
if (target is IDrawingContextLayerWithRenderContextAffinityImpl affined)
return affined.CreateNonAffinedSnapshot();
// We are returning the original target, so prevent it from being disposed
var rv = target;
target = null;
return rv;
}
finally
{
target?.Dispose();
}
}
}
}

25
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@ -23,6 +23,7 @@ namespace Avalonia.Rendering.Composition.Server
private readonly Queue<CompositionBatch> _batches = new Queue<CompositionBatch>();
private readonly Queue<Action> _receivedJobQueue = new();
private readonly Queue<Action> _receivedPostTargetJobQueue = new();
public long LastBatchId { get; private set; }
public Stopwatch Clock { get; } = Stopwatch.StartNew();
public TimeSpan ServerNow { get; private set; }
@ -38,6 +39,8 @@ namespace Avalonia.Rendering.Composition.Server
internal static readonly object RenderThreadDisposeStartMarker = new();
internal static readonly object RenderThreadJobsStartMarker = new();
internal static readonly object RenderThreadJobsEndMarker = new();
internal static readonly object RenderThreadPostTargetJobsStartMarker = new();
internal static readonly object RenderThreadPostTargetJobsEndMarker = new();
public CompositionOptions Options { get; }
public ServerCompositor(IRenderLoop renderLoop, IPlatformGraphics? platformGraphics,
@ -83,7 +86,12 @@ namespace Avalonia.Rendering.Composition.Server
var readObject = stream.ReadObject();
if (readObject == RenderThreadJobsStartMarker)
{
ReadServerJobs(stream);
ReadServerJobs(stream, _receivedJobQueue, RenderThreadJobsEndMarker);
continue;
}
if (readObject == RenderThreadPostTargetJobsStartMarker)
{
ReadServerJobs(stream, _receivedPostTargetJobQueue, RenderThreadPostTargetJobsEndMarker);
continue;
}
@ -111,11 +119,11 @@ namespace Avalonia.Rendering.Composition.Server
}
}
void ReadServerJobs(BatchStreamReader reader)
void ReadServerJobs(BatchStreamReader reader, Queue<Action> queue, object endMarker)
{
object? readObject;
while ((readObject = reader.ReadObject()) != RenderThreadJobsEndMarker)
_receivedJobQueue.Enqueue((Action)readObject!);
while ((readObject = reader.ReadObject()) != endMarker)
queue.Enqueue((Action)readObject!);
}
void ReadDisposeJobs(BatchStreamReader reader)
@ -128,12 +136,12 @@ namespace Avalonia.Rendering.Composition.Server
}
}
void ExecuteServerJobs()
void ExecuteServerJobs(Queue<Action> queue)
{
while(_receivedJobQueue.Count > 0)
while(queue.Count > 0)
try
{
_receivedJobQueue.Dequeue()();
queue.Dequeue()();
}
catch
{
@ -224,9 +232,10 @@ namespace Avalonia.Rendering.Composition.Server
try
{
RenderInterface.EnsureValidBackendContext();
ExecuteServerJobs();
ExecuteServerJobs(_receivedJobQueue);
foreach (var t in _activeTargets)
t.Render();
ExecuteServerJobs(_receivedPostTargetJobQueue);
}
catch (Exception e) when(RT_OnContextLostExceptionFilterObserver(e) && catchExceptions)
{

9
src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs

@ -71,11 +71,10 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
Compositor.AddToClock(this);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
protected override void RenderCore(ServerVisualRenderContext ctx, LtrbRect currentTransformedClip)
{
canvas.AutoFlush = true;
using var context = new ImmediateDrawingContext(canvas, GlobalTransformMatrix, false);
ctx.Canvas.AutoFlush = true;
using var context = new ImmediateDrawingContext(ctx.Canvas, GlobalTransformMatrix, false);
try
{
_handler.Render(context, currentTransformedClip.ToRect());
@ -86,6 +85,6 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnRender)} {{0}}", e);
}
canvas.AutoFlush = false;
ctx.Canvas.AutoFlush = false;
}
}

75
src/Avalonia.Base/Rendering/Composition/Server/ServerVisualRenderContext.cs

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server;
internal class ServerVisualRenderContext
{
public IDirtyRectTracker? DirtyRects { get; }
public bool DetachedRendering { get; }
public CompositorDrawingContextProxy Canvas { get; }
private readonly Stack<Matrix>? _transformStack;
public ServerVisualRenderContext(CompositorDrawingContextProxy canvas, IDirtyRectTracker? dirtyRects,
bool detachedRendering)
{
Canvas = canvas;
DirtyRects = dirtyRects;
DetachedRendering = detachedRendering;
if (detachedRendering)
{
_transformStack = new();
_transformStack.Push(canvas.Transform);
}
}
public bool ShouldRender(ServerCompositionVisual visual, LtrbRect currentTransformedClip)
{
if (DetachedRendering)
return true;
if (currentTransformedClip.IsZeroSize)
return false;
if (DirtyRects?.Intersects(currentTransformedClip) == false)
return false;
return true;
}
public bool ShouldRenderOwnContent(ServerCompositionVisual visual, LtrbRect currentTransformedClip)
{
if (DetachedRendering)
return true;
return currentTransformedClip.Intersects(visual.TransformedOwnContentBounds)
&& DirtyRects?.Intersects(visual.TransformedOwnContentBounds) != false;
}
public RestoreTransform SetOrPushTransform(ServerCompositionVisual visual)
{
if (!DetachedRendering)
{
Canvas.Transform = visual.GlobalTransformMatrix;
return default;
}
else
{
var transform = visual.CombinedTransformMatrix * _transformStack!.Peek();
Canvas.Transform = transform;
_transformStack.Push(transform);
return new RestoreTransform(this);
}
}
public struct RestoreTransform(ServerVisualRenderContext? parent) : IDisposable
{
public void Dispose()
{
if (parent != null)
{
parent._transformStack!.Pop();
parent.Canvas.Transform = parent._transformStack.Peek();
}
}
}
}

6
src/Avalonia.Controls/BorderVisual.cs

@ -46,9 +46,9 @@ class CompositionBorderVisual : CompositionDrawListVisual
{
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
protected override void RenderCore(ServerVisualRenderContext ctx, LtrbRect currentTransformedClip)
{
var canvas = ctx.Canvas;
if (ClipToBounds)
{
var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y)));
@ -58,7 +58,7 @@ class CompositionBorderVisual : CompositionDrawListVisual
canvas.PushClip(new RoundedRect(clipRect, _cornerRadius));
}
base.RenderCore(canvas, currentTransformedClip, dirtyRects);
base.RenderCore(ctx, currentTransformedClip);
if(ClipToBounds)
canvas.PopClip();

3
src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -61,6 +61,9 @@ namespace Avalonia.Headless
=> new HeadlessGeometryStub(g1.Bounds.Union(g2.Bounds));
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();
public IDrawingContextLayerImpl CreateOffscreenRenderTarget(PixelSize pixelSize, double scaling) =>
new HeadlessBitmapStub(pixelSize, new Vector(96 * scaling, 96 * scaling));
public bool IsLost => false;
public IReadOnlyDictionary<Type, object> PublicFeatures { get; } = new Dictionary<Type, object>();
public object? TryGetFeature(Type featureType) => null;

1
src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@ -31,6 +31,7 @@ namespace Avalonia.Skia
public interface ISkiaGpuWithPlatformGraphicsContext : ISkiaGpu
{
IPlatformGraphicsContext? PlatformGraphicsContext { get; }
IScopedResource<GRContext>? TryGetGrContext();
}
public interface ISkiaSurface : IDisposable

4
src/Skia/Avalonia.Skia/Gpu/Metal/SkiaMetalGpu.cs

@ -34,6 +34,10 @@ internal class SkiaMetalGpu : ISkiaGpu, ISkiaGpuWithPlatformGraphicsContext
public IDisposable EnsureCurrent() => _device.EnsureCurrent();
public IPlatformGraphicsContext? PlatformGraphicsContext => _device;
public IScopedResource<GRContext> TryGetGrContext() =>
ScopedResource<GRContext>.Create(_context ?? throw new ObjectDisposedException(nameof(SkiaMetalApi)),
EnsureCurrent().Dispose);
public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable<object> surfaces)
{
foreach (var surface in surfaces)

2
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@ -154,6 +154,8 @@ namespace Avalonia.Skia
public bool IsLost => _glContext.IsLost;
public IDisposable EnsureCurrent() => _glContext.EnsureCurrent();
public IPlatformGraphicsContext? PlatformGraphicsContext => _glContext;
public IScopedResource<GRContext> TryGetGrContext() =>
ScopedResource<GRContext>.Create(GrContext, EnsureCurrent().Dispose);
public object? TryGetFeature(Type featureType)
{

22
src/Skia/Avalonia.Skia/SkiaBackendContext.cs

@ -5,6 +5,7 @@ using System.Linq;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.OpenGL;
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia;
@ -60,6 +61,27 @@ internal class SkiaContext : IPlatformRenderInterfaceContext
"Don't know how to create a Skia render target from any of provided surfaces");
}
public IDrawingContextLayerImpl CreateOffscreenRenderTarget(PixelSize pixelSize, double scaling)
{
using (var gr = (_gpu as ISkiaGpuWithPlatformGraphicsContext)?.TryGetGrContext())
{
var createInfo = new SurfaceRenderTarget.CreateInfo
{
Width = pixelSize.Width,
Height = pixelSize.Height,
Dpi = new Vector(96 * scaling, 96 * scaling),
Format = null,
DisableTextLcdRendering = false,
GrContext = gr?.Value,
Gpu = _gpu,
DisableManualFbo = true,
Session = null
};
return new SurfaceRenderTarget(createInfo);
}
}
public bool IsLost => _gpu?.IsLost ?? false;
public IReadOnlyDictionary<Type, object> PublicFeatures { get; }

11
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -11,7 +11,7 @@ namespace Avalonia.Skia
/// <summary>
/// Skia render target that writes to a surface.
/// </summary>
internal class SurfaceRenderTarget : IDrawingContextLayerImpl, IDrawableBitmapImpl
internal class SurfaceRenderTarget : IDrawingContextLayerImpl, IDrawableBitmapImpl, IDrawingContextLayerWithRenderContextAffinityImpl
{
private readonly ISkiaSurface _surface;
private readonly SKCanvas _canvas;
@ -234,5 +234,14 @@ namespace Avalonia.Skia
public bool DisableManualFbo;
}
public bool HasRenderContextAffinity => _grContext != null;
public IBitmapImpl CreateNonAffinedSnapshot()
{
if (!HasRenderContextAffinity)
throw new InvalidOperationException();
using var image = SnapshotImage();
return new ImmutableBitmap(image.ToRasterImage(true));
}
}
}

4
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -179,6 +179,10 @@ namespace Avalonia.Direct2D1
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => _platform.CreateRenderTarget(surfaces);
public IDrawingContextLayerImpl CreateOffscreenRenderTarget(PixelSize pixelSize, double scaling) =>
new WicRenderTargetBitmapImpl(pixelSize, new Vector(96 * scaling, 96 * scaling));
public bool IsLost => false;
public IReadOnlyDictionary<Type, object> PublicFeatures { get; } = new Dictionary<Type, object>();
}

Loading…
Cancel
Save