From 827b13272907fb725f950ee04555a47c6f203e8a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 1 Dec 2025 23:17:11 +0500 Subject: [PATCH 1/3] Improve effect render performance by providing Skia with information about subscene bounds --- src/Avalonia.Base/Platform/IDrawingContextImpl.cs | 5 +++-- .../Server/DrawingContextProxy.PendingCommands.cs | 6 +++++- .../Rendering/Composition/Server/DrawingContextProxy.cs | 8 ++++++-- .../Composition/Server/ServerCompositionVisual.cs | 9 +++++++-- src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs | 7 +++++-- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index 476bca5a33e..1a813f7bbe7 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -201,9 +201,10 @@ void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, 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 74389903f4f..ee0447629a6 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 @@ struct PendingCommandDataUnion [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 @@ void ExecCommand(ref PendingCommand cmd) 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 12ee8ad41c5..6b4982c4903 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -285,14 +285,18 @@ public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rec _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/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 112a817541c..e7844805832 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -63,8 +63,13 @@ public void Render(ServerVisualRenderContext context, LtrbRect? parentTransforme if (applyRenderOptions) canvas.PushRenderOptions(RenderOptions); if (Effect != null) - canvas.PushEffect(Effect); - + { + var oldMatrix = canvas.Transform; + canvas.Transform = Matrix.Identity; + canvas.PushEffect(TransformedOwnContentBounds.ToRect(), Effect); + canvas.Transform = oldMatrix; + } + if (Opacity != 1) canvas.PushOpacity(Opacity, ClipToBounds ? boundsRect : null); if (ClipToBounds && !HandlesClipToBounds) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.Effects.cs index ab528c326bc..7621e8436c3 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); } From 93e4047d9f2b4acd041ccd1a7f3ccd8344a836dc Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 2 Dec 2025 00:01:53 +0500 Subject: [PATCH 2/3] Fixed test --- .../ServerCompositionContainerVisual.cs | 2 ++ .../Server/ServerCompositionVisual.cs | 26 +++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs index 4f300503b2f..396009841b9 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs @@ -65,6 +65,8 @@ public override UpdateResult Update(ServerCompositionTarget root, Matrix parentC 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 e7844805832..06ada8157d0 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -62,13 +62,7 @@ public void Render(ServerVisualRenderContext context, LtrbRect? parentTransforme if (applyRenderOptions) canvas.PushRenderOptions(RenderOptions); - if (Effect != null) - { - var oldMatrix = canvas.Transform; - canvas.Transform = Matrix.Identity; - canvas.PushEffect(TransformedOwnContentBounds.ToRect(), Effect); - canvas.Transform = oldMatrix; - } + var needPopEffect = PushEffect(canvas); if (Opacity != 1) canvas.PushOpacity(Opacity, ClipToBounds ? boundsRect : null); @@ -92,12 +86,28 @@ public void Render(ServerVisualRenderContext context, LtrbRect? parentTransforme 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; From 813ed7bc603bf95aeae6133e85c0bed72f79aee1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 2 Dec 2025 00:43:59 +0500 Subject: [PATCH 3/3] API diff --- api/Avalonia.nupkg.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index d7870b97614..fed3b83aafd 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -37,6 +37,18 @@ baseline/netstandard2.0/Avalonia.Base.dll target/netstandard2.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.IDrawingContextImplWithEffects.PopEffect + baseline/netstandard2.0/Avalonia.Base.dll + target/netstandard2.0/Avalonia.Base.dll + + + CP0002 + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect) + baseline/netstandard2.0/Avalonia.Base.dll + target/netstandard2.0/Avalonia.Base.dll + CP0002 M:Avalonia.Controls.Primitives.IPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})