From 4e530e4340096f0fc560379f68b55bcdb98f922f 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 | 168 ++++++++++++++++++ .../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, 208 insertions(+), 11 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 98a74e5485..84a9f4b163 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -43,6 +43,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) @@ -61,6 +67,72 @@ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.GetDataContext(Avalonia.Controls.Templates.IDataTemplate) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Controls.Templates.IDataTemplate) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Styling.IStyle) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetDataContext(Avalonia.Controls.Templates.IDataTemplate,System.Object) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.ResourceDictionary,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.Control) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.Control) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll + current/Avalonia/lib/net10.0/Avalonia.Controls.dll + CP0002 F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache @@ -109,6 +181,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(Avalonia.Media.IEffect) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) @@ -127,6 +205,72 @@ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + CP0002 + M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.GetDataContext(Avalonia.Controls.Templates.IDataTemplate) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Controls.Templates.IDataTemplate) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.GetPreviewWith(Avalonia.Styling.IStyle) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetDataContext(Avalonia.Controls.Templates.IDataTemplate,System.Object) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.ResourceDictionary,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.Control) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Controls.Templates.IDataTemplate,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.Control) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + + + CP0002 + M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.Styling.IStyle,Avalonia.Controls.ITemplate{Avalonia.Controls.Control}) + baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll + current/Avalonia/lib/net8.0/Avalonia.Controls.dll + CP0002 M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType) @@ -163,6 +307,24 @@ baseline/Avalonia/lib/netstandard2.0/Avalonia.Dialogs.dll current/Avalonia/lib/netstandard2.0/Avalonia.Dialogs.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 + + + CP0006 + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(System.Nullable{Avalonia.Rect},Avalonia.Media.IEffect) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0006 M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) @@ -253,6 +415,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0006 + M:Avalonia.Platform.IDrawingContextImplWithEffects.PushEffect(System.Nullable{Avalonia.Rect},Avalonia.Media.IEffect) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0006 M:Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32,Avalonia.Media.FontStyle,Avalonia.Media.FontWeight,Avalonia.Media.FontStretch,System.String,System.Globalization.CultureInfo,Avalonia.Media.Typeface@) 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); }