diff --git a/src/Avalonia.Base/Collections/Pooled/PooledList.cs b/src/Avalonia.Base/Collections/Pooled/PooledList.cs index 73e1b28798..a6e02c355e 100644 --- a/src/Avalonia.Base/Collections/Pooled/PooledList.cs +++ b/src/Avalonia.Base/Collections/Pooled/PooledList.cs @@ -525,6 +525,10 @@ namespace Avalonia.Collections.Pooled public ReadOnlyCollection AsReadOnly() => new ReadOnlyCollection(this); + /// Gets a view over the data in a list. + /// Items should not be added or removed from the list while the returned span is in use. + public Span AsSpan() => new(_items, 0, _size); + /// /// Searches a section of the list for a given element using a binary search /// algorithm. diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs index 1db53bd576..7eb9b91f9a 100644 --- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs +++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs @@ -35,11 +35,16 @@ namespace Avalonia.Media } } - internal ImmediateDrawingContext(IDrawingContextImpl impl, bool ownsImpl) + internal ImmediateDrawingContext(IDrawingContextImpl impl, bool ownsImpl) : this(impl, impl.Transform, ownsImpl) + { + + } + + internal ImmediateDrawingContext(IDrawingContextImpl impl, Matrix transform, bool ownsImpl) { _ownsImpl = ownsImpl; PlatformImpl = impl; - _currentContainerTransform = impl.Transform; + _currentContainerTransform = transform; } public IDrawingContextImpl PlatformImpl { get; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs new file mode 100644 index 0000000000..bbf648d71f --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs @@ -0,0 +1,152 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Avalonia.Collections.Pooled; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.Rendering.Composition.Server; + +internal partial class CompositorDrawingContextProxy +{ + + private PooledList _commands = new(); + private bool _autoFlush; + + + enum PendingCommandType + { + SetTransform, + PushClip, + PushOpacity, + PushOpacityMask, + PushGeometryClip, + PushRenderOptions, + PushEffect + } + + [StructLayout(LayoutKind.Explicit)] + struct PendingCommandObjectUnion + { + [FieldOffset(0)] public IEffect? Effect; + [FieldOffset(0)] public IBrush? Mask; + [FieldOffset(0)] public IGeometryImpl? Clip; + } + + [StructLayout(LayoutKind.Explicit)] + struct PendingCommandDataUnion + { + // PushOpacity + [FieldOffset(0)] public double Opacity; + [FieldOffset(8)] public Rect? NullableOpacityRect; + + [FieldOffset(0)] public Matrix Transform; + + [FieldOffset(0)] public RenderOptions RenderOptions; + + // PushClip/PushOpacityMask + [FieldOffset(0)] public bool IsRoundRect; + [FieldOffset(4)] public RoundedRect RoundRect; + [FieldOffset(4)] public Rect NormalRect; + } + + struct PendingCommand + { + public PendingCommandType Type; + public PendingCommandObjectUnion ObjectUnion; + public PendingCommandDataUnion DataUnion; + + } + + public bool AutoFlush + { + get => _autoFlush; + set + { + _autoFlush = value; + if (value) + Flush(); + } + } + + public void SetTransform(Matrix m) + { + if (_autoFlush) + { + SetImplTransform(m); + return; + } + var cmd = new PendingCommand + { + Type = PendingCommandType.SetTransform, + DataUnion = { Transform = m } + }; + if (_commands.Count > 0 && _commands[_commands.Count - 1].Type == PendingCommandType.SetTransform) + _commands[_commands.Count - 1] = cmd; + else + _commands.Add(cmd); + } + + + private bool TryDiscard(PendingCommandType type) + { + while (_commands.Count > 0 && _commands[_commands.Count - 1].Type == PendingCommandType.SetTransform) + _commands.RemoveAt(_commands.Count - 1); + if (_commands.Count == 0) + return false; + if (_commands[_commands.Count - 1].Type == type) + { + _commands.RemoveAt(_commands.Count - 1); + return true; + } + + // Not sure how exactly can we get here, but flush commands just in case + Flush(); + return false; + } + + void AddCommand(PendingCommand command) + { + if(_autoFlush) + ExecCommand(ref command); + else + _commands.Add(command); + } + + void ExecCommand(ref PendingCommand cmd) + { + if (cmd.Type == PendingCommandType.SetTransform) + SetImplTransform(cmd.DataUnion.Transform); + else if (cmd.Type == PendingCommandType.PushOpacity) + _impl.PushOpacity(cmd.DataUnion.Opacity, cmd.DataUnion.NullableOpacityRect); + else if (cmd.Type == PendingCommandType.PushOpacityMask) + _impl.PushOpacityMask(cmd.ObjectUnion.Mask!, cmd.DataUnion.NormalRect); + else if (cmd.Type == PendingCommandType.PushClip) + { + if (cmd.DataUnion.IsRoundRect) + _impl.PushClip(cmd.DataUnion.RoundRect); + else + _impl.PushClip(cmd.DataUnion.NormalRect); + } + else if (cmd.Type == PendingCommandType.PushGeometryClip) + _impl.PushGeometryClip(cmd.ObjectUnion.Clip!); + else if (cmd.Type == PendingCommandType.PushEffect) + { + if (_impl is IDrawingContextImplWithEffects effects) + effects.PushEffect(cmd.ObjectUnion.Effect!); + } + else if (cmd.Type == PendingCommandType.PushRenderOptions) + _impl.PushRenderOptions(cmd.DataUnion.RenderOptions); + else + Debug.Assert(false); + } + + public void Flush() + { + var commands = _commands.AsSpan(); + for (var index = 0; index < commands.Length; index++) + ExecCommand(ref commands[index]); + + _commands.Clear(); + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 0533278056..75dc13b1e0 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -11,13 +11,9 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Server; /// -/// A bunch of hacks to make the existing rendering operations and IDrawingContext -/// to work with composition rendering infrastructure. -/// 1) Keeps and applies the transform of the current visual since drawing operations think that -/// they have information about the full render transform (they are not) -/// 2) Keeps the draw list for the VisualBrush contents of the current drawing operation. + /// -internal class CompositorDrawingContextProxy : IDrawingContextImpl, +internal partial class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport, IDrawingContextImplWithEffects { private readonly IDrawingContextImpl _impl; @@ -27,60 +23,84 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, _impl = impl; } - public Matrix PostTransform { get; set; } = Matrix.Identity; - public void Dispose() { - _impl.Dispose(); + Flush(); + _commands.Dispose(); } - Matrix _transform; + public Matrix? PostTransform { get; set; } + Matrix _transform; + public Matrix Transform { get => _transform; - set => _impl.Transform = (_transform = value) * PostTransform; + set + { + _transform = value; + SetTransform(value); + } + } + + void SetImplTransform(Matrix m) + { + _transform = m; + if (PostTransform.HasValue) + m = m * PostTransform.Value; + _impl.Transform = m; } public void Clear(Color color) { + Flush(); _impl.Clear(color); } public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) { + Flush(); _impl.DrawBitmap(source, opacity, sourceRect, destRect); } public void DrawBitmap(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { + Flush(); _impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect); } public void DrawLine(IPen? pen, Point p1, Point p2) { + Flush(); _impl.DrawLine(pen, p1, p2); } public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) { + Flush(); _impl.DrawGeometry(brush, pen, geometry); } public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default) { + Flush(); _impl.DrawRectangle(brush, pen, rect, boxShadows); } - public void DrawRegion(IBrush? brush, IPen? pen, IPlatformRenderInterfaceRegion region) => + public void DrawRegion(IBrush? brush, IPen? pen, IPlatformRenderInterfaceRegion region) + { + Flush(); _impl.DrawRegion(brush, pen, region); + } public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect) { + Flush(); _impl.DrawEllipse(brush, pen, rect); } public void DrawGlyphRun(IBrush? foreground, IGlyphRunImpl glyphRun) { + Flush(); _impl.DrawGlyphRun(foreground, glyphRun); } @@ -91,70 +111,140 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, public void PushClip(Rect clip) { - _impl.PushClip(clip); + AddCommand(new() + { + Type = PendingCommandType.PushClip, + DataUnion = + { + NormalRect = clip + } + }); } public void PushClip(RoundedRect clip) { - _impl.PushClip(clip); + AddCommand(new() + { + Type = PendingCommandType.PushClip, + DataUnion = + { + IsRoundRect = true, + RoundRect = clip + } + }); } - public void PushClip(IPlatformRenderInterfaceRegion region) => _impl.PushClip(region); + public void PushClip(IPlatformRenderInterfaceRegion region) + { + Flush(); + _impl.PushClip(region); + } public void PopClip() { - _impl.PopClip(); + if (!TryDiscard(PendingCommandType.PushClip)) + _impl.PopClip(); } - public void PushLayer(Rect bounds) => _impl.PushLayer(bounds); + public void PushLayer(Rect bounds) + { + Flush(); + _impl.PushLayer(bounds); + } - public void PopLayer() => _impl.PopLayer(); + public void PopLayer() + { + Flush(); + _impl.PopLayer(); + } public void PushOpacity(double opacity, Rect? bounds) { - _impl.PushOpacity(opacity, bounds); + AddCommand(new PendingCommand + { + Type = PendingCommandType.PushOpacity, + DataUnion = + { + Opacity = opacity, + NullableOpacityRect = bounds + } + }); } public void PopOpacity() { - _impl.PopOpacity(); + if (!TryDiscard(PendingCommandType.PushOpacity)) + _impl.PopOpacity(); } public void PushOpacityMask(IBrush mask, Rect bounds) { - _impl.PushOpacityMask(mask, bounds); - } - - public void PushRenderOptions(RenderOptions renderOptions) - { - _impl.PushRenderOptions(renderOptions); + AddCommand(new() + { + Type = PendingCommandType.PushOpacityMask, + DataUnion = + { + NormalRect = bounds + }, + ObjectUnion = + { + Mask = mask + } + }); } public void PopOpacityMask() { - _impl.PopOpacityMask(); + if (!TryDiscard(PendingCommandType.PushOpacityMask)) + _impl.PopOpacityMask(); } public void PushGeometryClip(IGeometryImpl clip) { - _impl.PushGeometryClip(clip); + AddCommand(new PendingCommand + { + Type = PendingCommandType.PushGeometryClip, + ObjectUnion = + { + Clip = clip + } + }); } public void PopGeometryClip() { - _impl.PopGeometryClip(); + if (!TryDiscard(PendingCommandType.PushGeometryClip)) + _impl.PopGeometryClip(); + } + + public void PushRenderOptions(RenderOptions renderOptions) + { + AddCommand(new() + { + Type = PendingCommandType.PushRenderOptions, + DataUnion = + { + RenderOptions = renderOptions + } + }); } public void PopRenderOptions() { - _impl.PopRenderOptions(); + if (!TryDiscard(PendingCommandType.PushRenderOptions)) + _impl.PopRenderOptions(); + } + + public object? GetFeature(Type t) + { + Flush(); + return _impl.GetFeature(t); } - public object? GetFeature(Type t) => _impl.GetFeature(t); - public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect) { + Flush(); if (_impl is IDrawingContextWithAcrylicLikeSupport acrylic) acrylic.DrawRectangle(material, rect); else @@ -163,13 +253,19 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, public void PushEffect(IEffect effect) { - if (_impl is IDrawingContextImplWithEffects effects) - effects.PushEffect(effect); + AddCommand(new() + { + Type = PendingCommandType.PushEffect, + ObjectUnion = + { + Effect = effect + } + }); } public void PopEffect() { - if (_impl is IDrawingContextImplWithEffects effects) + if (!TryDiscard(PendingCommandType.PushEffect) && _impl is IDrawingContextImplWithEffects effects) effects.PopEffect(); } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 060ba2c820..3adf028438 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -43,7 +43,9 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip, IDirtyRectTracker dirtyRects) { - if (_renderCommands != null) + if (_renderCommands != null + && currentTransformedClip.Intersects(TransformedOwnContentBounds) + && dirtyRects.Intersects(TransformedOwnContentBounds)) { _renderCommands.Render(canvas); } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 9eef4bd7e2..a39b3ae03f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -214,8 +214,8 @@ namespace Avalonia.Rendering.Composition.Server if (useLayerClip) context.PushLayer(DirtyRects.CombinedRect.ToRectUnscaled()); - - root.Render(new CompositorDrawingContextProxy(context), null, DirtyRects); + using (var proxy = new CompositorDrawingContextProxy(context)) + root.Render(proxy, null, DirtyRects); if (useLayerClip) context.PopLayer(); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 87243f4024..24939634d2 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -49,14 +49,12 @@ namespace Avalonia.Rendering.Composition.Server if (AdornedVisual != null) { - canvas.PostTransform = Matrix.Identity; canvas.Transform = Matrix.Identity; if (AdornerIsClipped) canvas.PushClip(AdornedVisual._combinedTransformedClipBounds.ToRect()); } var transform = GlobalTransformMatrix; - canvas.PostTransform = transform; - canvas.Transform = Matrix.Identity; + canvas.Transform = transform; var applyRenderOptions = RenderOptions != default; @@ -76,10 +74,6 @@ namespace Avalonia.Rendering.Composition.Server RenderCore(canvas, currentTransformedClip, dirtyRects); - // Hack to force invalidation of SKMatrix - canvas.PostTransform = transform; - canvas.Transform = Matrix.Identity; - if (OpacityMaskBrush != null) canvas.PopOpacityMask(); if (Clip != null) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs index 68b2823c67..203a530c9b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs @@ -74,7 +74,8 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip, IDirtyRectTracker dirtyRects) { - using var context = new ImmediateDrawingContext(canvas, false); + canvas.AutoFlush = true; + using var context = new ImmediateDrawingContext(canvas, GlobalTransformMatrix, false); try { _handler.Render(context, currentTransformedClip.ToRect()); @@ -84,5 +85,7 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer Logger.TryGet(LogEventLevel.Error, LogArea.Visual) ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnRender)} {{0}}", e); } + + canvas.AutoFlush = false; } } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 0c5cd4382f..eecb56e90a 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Skia // TODO: Get rid of this value, it's currently used to calculate intermediate sizes for tile brushes // but does so ignoring the current transform private readonly Vector _intermediateSurfaceDpi; - private readonly Stack _maskStack = new(); + private readonly Stack<(SKMatrix matrix, PaintWrapper paint)> _maskStack = new(); private readonly Stack _opacityStack = new(); private readonly Stack _renderOptionsStack = new(); private readonly Matrix? _postTransform; @@ -813,7 +813,7 @@ namespace Avalonia.Skia var paint = SKPaintCache.Shared.Get(); Canvas.SaveLayer(bounds.ToSKRect(), paint); - _maskStack.Push(CreatePaint(paint, mask, bounds)); + _maskStack.Push((Canvas.TotalMatrix, CreatePaint(paint, mask, bounds))); } /// @@ -826,9 +826,10 @@ namespace Avalonia.Skia Canvas.SaveLayer(paint); SKPaintCache.Shared.ReturnReset(paint); - - PaintWrapper paintWrapper; - using (paintWrapper = _maskStack.Pop()) + + var (transform, paintWrapper) = _maskStack.Pop(); + Canvas.SetMatrix(transform); + using (paintWrapper) { Canvas.DrawPaint(paintWrapper.Paint); }