From d88a94bc1cd104bba423481434a90a5f3065c786 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 5 Dec 2025 19:25:03 +0500 Subject: [PATCH] Improve effect render performance by providing Skia with information about subscene bounds (#20191) * Improve effect render performance by providing Skia with information about subscene bounds * Fixed test * apidiff * Update API diff --------- Co-authored-by: Julien Lebosquain --- api/Avalonia.nupkg.xml | 2 +- .../Platform/IDrawingContextImpl.cs | 5 ++-- .../DrawingContextProxy.PendingCommands.cs | 6 ++++- .../Composition/Server/DrawingContextProxy.cs | 8 +++++-- .../ServerCompositionContainerVisual.cs | 2 ++ .../Server/ServerCompositionVisual.cs | 23 +++++++++++++++---- .../DrawingContextImpl.Effects.cs | 7 ++++-- 7 files changed, 41 insertions(+), 12 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index d7870b9761..b51fbeec24 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -187,4 +187,4 @@ baseline/netstandard2.0/Avalonia.Controls.dll target/netstandard2.0/Avalonia.Controls.dll - \ No newline at end of file + diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index 476bca5a33..1a813f7bbe 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -201,9 +201,10 @@ namespace Avalonia.Platform object? GetFeature(Type t); } - public interface IDrawingContextImplWithEffects + [PrivateApi] + public interface IDrawingContextImplWithEffects : IDrawingContextImpl { - void PushEffect(IEffect effect); + void PushEffect(Rect? clipRect, IEffect effect); void PopEffect(); } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs index 74389903f4..ee0447629a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs @@ -48,6 +48,10 @@ internal partial class CompositorDrawingContextProxy [FieldOffset(0)] public bool IsRoundRect; [FieldOffset(4)] public RoundedRect RoundRect; [FieldOffset(4)] public Rect NormalRect; + + // PushEffect + [FieldOffset(0)] + public Rect? EffectClipRect; } struct PendingCommand @@ -140,7 +144,7 @@ internal partial class CompositorDrawingContextProxy else if (cmd.Type == PendingCommandType.PushEffect) { if (_impl is IDrawingContextImplWithEffects effects) - effects.PushEffect(cmd.ObjectUnion.Effect!); + effects.PushEffect(cmd.DataUnion.EffectClipRect, cmd.ObjectUnion.Effect!); } else if (cmd.Type == PendingCommandType.PushRenderOptions) _impl.PushRenderOptions(cmd.DataUnion.RenderOptions); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 12ee8ad41c..6b4982c490 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -285,14 +285,18 @@ internal partial class CompositorDrawingContextProxy : IDrawingContextImpl, _impl.DrawRectangle(new ImmutableSolidColorBrush(material.FallbackColor), null, rect); } - public void PushEffect(IEffect effect) + public void PushEffect(Rect? clipRect, IEffect effect) { AddCommand(new() { Type = PendingCommandType.PushEffect, ObjectUnion = { - Effect = effect + Effect = effect, + }, + DataUnion = + { + EffectClipRect = clipRect } }); } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs index 4f300503b2..396009841b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs @@ -65,6 +65,8 @@ namespace Avalonia.Rendering.Composition.Server return new(_transformedContentBounds, oldInvalidated, newInvalidated); } + protected override LtrbRect GetEffectBounds() => _transformedContentBounds ?? default; + void AddEffectPaddedDirtyRect(IImmutableEffect effect, LtrbRect transformedBounds) { var padding = effect.GetEffectOutputPadding(); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 112a817541..c2d43f5667 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -62,9 +62,8 @@ namespace Avalonia.Rendering.Composition.Server if (applyRenderOptions) canvas.PushRenderOptions(RenderOptions); - if (Effect != null) - canvas.PushEffect(Effect); - + var needPopEffect = PushEffect(canvas); + if (Opacity != 1) canvas.PushOpacity(Opacity, ClipToBounds ? boundsRect : null); if (ClipToBounds && !HandlesClipToBounds) @@ -87,12 +86,28 @@ namespace Avalonia.Rendering.Composition.Server if (Opacity != 1) canvas.PopOpacity(); - if (Effect != null) + if (needPopEffect) canvas.PopEffect(); if(applyRenderOptions) canvas.PopRenderOptions(); } + protected virtual LtrbRect GetEffectBounds() => TransformedOwnContentBounds; + + private bool PushEffect(CompositorDrawingContextProxy canvas) + { + if (Effect == null) + return false; + var clip = GetEffectBounds(); + if (clip.IsZeroSize) + return false; + var oldMatrix = canvas.Transform; + canvas.Transform = Matrix.Identity; + canvas.PushEffect(GetEffectBounds().ToRect(), Effect!); + canvas.Transform = oldMatrix; + return true; + } + protected virtual bool HandlesClipToBounds => false; private ReadbackData _readback0, _readback1, _readback2; diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs index ab528c326b..7621e8436c 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs @@ -7,13 +7,16 @@ namespace Avalonia.Skia; partial class DrawingContextImpl { - public void PushEffect(IEffect effect) + public void PushEffect(Rect? effectClipRect, IEffect effect) { CheckLease(); using var filter = CreateEffect(effect); var paint = SKPaintCache.Shared.Get(); paint.ImageFilter = filter; - Canvas.SaveLayer(paint); + if (effectClipRect.HasValue) + Canvas.SaveLayer(effectClipRect.Value.ToSKRect(), paint); + else + Canvas.SaveLayer(paint); SKPaintCache.Shared.ReturnReset(paint); }