From e895c913670bef893a74af09aedc3e70841ff7c3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 26 Nov 2022 14:38:43 +0600 Subject: [PATCH 1/5] Fixed round rect regression in compositing renderer --- .../Server/ServerCompositionVisual.cs | 6 +- src/Avalonia.Base/Visual.cs | 7 +- src/Avalonia.Controls/Border.cs | 14 ++++ src/Avalonia.Controls/BorderVisual.cs | 76 +++++++++++++++++++ 4 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Controls/BorderVisual.cs diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index b742f1d44a..d724e14298 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -48,7 +48,7 @@ namespace Avalonia.Rendering.Composition.Server if (Opacity != 1) canvas.PushOpacity(Opacity); var boundsRect = new Rect(new Size(Size.X, Size.Y)); - if(ClipToBounds) + if (ClipToBounds && !HandlesClipToBounds) canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); if (Clip != null) canvas.PushGeometryClip(Clip); @@ -65,11 +65,13 @@ namespace Avalonia.Rendering.Composition.Server canvas.PopOpacityMask(); if (Clip != null) canvas.PopGeometryClip(); - if (ClipToBounds) + if (ClipToBounds && !HandlesClipToBounds) canvas.PopClip(); if(Opacity != 1) canvas.PopOpacity(); } + + protected virtual bool HandlesClipToBounds => false; private ReadbackData _readback0, _readback1, _readback2; diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 69389def56..a86ef8ff88 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -452,12 +452,15 @@ namespace Avalonia } } + private protected virtual CompositionDrawListVisual CreateCompositionVisual(Compositor compositor) + => new CompositionDrawListVisual(compositor, + new ServerCompositionDrawListVisual(compositor.Server, this), this); + internal CompositionVisual AttachToCompositor(Compositor compositor) { if (CompositionVisual == null || CompositionVisual.Compositor != compositor) { - CompositionVisual = new CompositionDrawListVisual(compositor, - new ServerCompositionDrawListVisual(compositor.Server, this), this); + CompositionVisual = CreateCompositionVisual(compositor); } return CompositionVisual; diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index bc740c133a..1bb574acd2 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Controls.Utils; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Rendering.Composition; using Avalonia.Utilities; using Avalonia.VisualTree; @@ -73,6 +74,7 @@ namespace Avalonia.Controls private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper(); private Thickness? _layoutThickness; private double _scale; + private CompositionBorderVisual? _borderVisual; /// /// Initializes static members of the class. @@ -101,6 +103,10 @@ namespace Avalonia.Controls case nameof(BorderThickness): _layoutThickness = null; break; + case nameof(CornerRadius): + if (_borderVisual != null) + _borderVisual.CornerRadius = CornerRadius; + break; } } @@ -245,6 +251,14 @@ namespace Avalonia.Controls return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness); } + private protected override CompositionDrawListVisual CreateCompositionVisual(Compositor compositor) + { + return _borderVisual = new CompositionBorderVisual(compositor, this) + { + CornerRadius = CornerRadius + }; + } + public CornerRadius ClipToBoundsRadius => CornerRadius; } } diff --git a/src/Avalonia.Controls/BorderVisual.cs b/src/Avalonia.Controls/BorderVisual.cs new file mode 100644 index 0000000000..7afbf9edcf --- /dev/null +++ b/src/Avalonia.Controls/BorderVisual.cs @@ -0,0 +1,76 @@ +using System; +using Avalonia.Rendering.Composition; +using Avalonia.Rendering.Composition.Server; +using Avalonia.Rendering.Composition.Transport; +using Avalonia.Rendering.SceneGraph; + +namespace Avalonia.Controls; + +class CompositionBorderVisual : CompositionDrawListVisual +{ + private CornerRadius _cornerRadius; + private bool _cornerRadiusChanged; + + public CompositionBorderVisual(Compositor compositor, Visual visual) : base(compositor, + new ServerBorderVisual(compositor.Server, visual), visual) + { + } + + public CornerRadius CornerRadius + { + get => _cornerRadius; + set + { + if (_cornerRadius != value) + { + _cornerRadiusChanged = true; + _cornerRadius = value; + RegisterForSerialization(); + } + } + } + + private protected override void SerializeChangesCore(BatchStreamWriter writer) + { + base.SerializeChangesCore(writer); + writer.Write(_cornerRadiusChanged); + if (_cornerRadiusChanged) + writer.Write(_cornerRadius); + } + + class ServerBorderVisual : ServerCompositionDrawListVisual + { + private CornerRadius _cornerRadius; + public ServerBorderVisual(ServerCompositor compositor, Visual v) : base(compositor, v) + { + } + + protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) + { + if (ClipToBounds) + { + var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y))); + if (_cornerRadius.IsEmpty) + canvas.PushClip(clipRect); + else + canvas.PushClip(new RoundedRect(clipRect, _cornerRadius)); + } + + base.RenderCore(canvas, currentTransformedClip); + + if(ClipToBounds) + canvas.PopClip(); + + } + + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + { + base.DeserializeChangesCore(reader, commitedAt); + if (reader.Read()) + _cornerRadius = reader.Read(); + } + + protected override bool HandlesClipToBounds => true; + } + +} \ No newline at end of file From f820c866a08d367500b997c3ff35ad247153f2d9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 26 Nov 2022 15:54:42 +0600 Subject: [PATCH 2/5] Fixed composition key frame computation --- .../Composition/Animations/KeyFrameAnimationInstance.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs index e20a4a9ad8..570c6a6d07 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs @@ -128,8 +128,11 @@ namespace Avalonia.Rendering.Composition.Animations left = kf; right = _keyFrames[c + 1]; - break; } + else if (c == 0) + return ExpressionVariant.Create(GetKeyFrame(ref ctx, kf)); + else + break; } var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key))); From f19ba5507f21b3a4f818a187f9976a1d00df05bd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 26 Nov 2022 15:55:31 +0600 Subject: [PATCH 3/5] Implemented ElementComposition.SetElementChildVisual --- .../Composition/CompositingRenderer.cs | 20 ++++++++++++++++-- .../Composition/Compositor.Factories.cs | 3 +++ .../Composition/ElementCompositionPreview.cs | 21 +++++++++++++++++++ .../ServerCompositionSolidColorVisual.cs | 11 ++++++++++ src/Avalonia.Base/Visual.cs | 1 + src/Avalonia.Base/composition-schema.xml | 3 +++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 98a6a3600e..38d9b34937 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -146,8 +146,18 @@ public class CompositingRenderer : IRendererWithCompositor return result == 0 ? lhs.index.CompareTo(rhs.index) : result; }); } - - if (compositionChildren.Count == visualChildren.Count) + + var childVisual = v.ChildCompositionVisual; + + // Check if the current visual somehow got migrated to another compositor + if (childVisual != null && childVisual.Compositor != v.CompositionVisual.Compositor) + childVisual = null; + + var expectedCount = visualChildren.Count; + if (childVisual != null) + expectedCount++; + + if (compositionChildren.Count == expectedCount) { bool mismatch = false; if (sortedChildren != null) @@ -167,6 +177,9 @@ public class CompositingRenderer : IRendererWithCompositor break; } + if (childVisual != null && + !ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual)) + mismatch = true; if (!mismatch) { @@ -193,6 +206,9 @@ public class CompositingRenderer : IRendererWithCompositor if (compositionChild != null) compositionChildren.Add(compositionChild); } + + if (childVisual != null) + compositionChildren.Add(childVisual); } private void UpdateCore() diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs index 1a13d23acd..00fa7b3315 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs @@ -29,4 +29,7 @@ public partial class Compositor public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this); public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this); + + public CompositionSolidColorVisual CreateSolidColorVisual() => + new(this, new ServerCompositionSolidColorVisual(Server)); } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs index 5bd8e4a4d3..b01321edd8 100644 --- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs +++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs @@ -1,5 +1,8 @@ // Special license applies License.md +using System; +using Avalonia.VisualTree; + namespace Avalonia.Rendering.Composition; /// @@ -13,4 +16,22 @@ public static class ElementComposition /// /// public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual; + + /// + /// Sets a custom as the last child of the element’s visual tree. + /// + public static void SetElementChildVisual(Visual visual, CompositionVisual? compositionVisual) + { + if (compositionVisual != null && visual.CompositionVisual != null && + compositionVisual.Compositor != visual.CompositionVisual.Compositor) + throw new InvalidOperationException("Composition visuals belong to different compositor instances"); + + visual.ChildCompositionVisual = compositionVisual; + visual.GetVisualRoot()?.Renderer.RecalculateChildren(visual); + } + + /// + /// Retrieves a object previously set by a call to . + /// + public static CompositionVisual? GetElementChildVisual(Visual visual) => visual.ChildCompositionVisual; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs new file mode 100644 index 0000000000..79abd7ee17 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs @@ -0,0 +1,11 @@ +using Avalonia.Media.Immutable; + +namespace Avalonia.Rendering.Composition.Server; + +internal partial class ServerCompositionSolidColorVisual +{ + protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) + { + canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y)); + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 29559a8618..d12eb1d138 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -292,6 +292,7 @@ namespace Avalonia protected IRenderRoot? VisualRoot => _visualRoot ?? (this as IRenderRoot); internal CompositionDrawListVisual? CompositionVisual { get; private set; } + internal CompositionVisual? ChildCompositionVisual { get; set; } public bool HasNonUniformZIndexChildren { get; private set; } diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml index e0e177da44..6dfcb2e74d 100644 --- a/src/Avalonia.Base/composition-schema.xml +++ b/src/Avalonia.Base/composition-schema.xml @@ -27,6 +27,9 @@ + + + From 141706f72738be2debfa9ee5ed471c6bbc61bc0d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 26 Nov 2022 16:21:04 +0600 Subject: [PATCH 4/5] Changed the way DispatcherPriority values are defined, added PreComposition priority --- .../Threading/DispatcherPriority.cs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/Threading/DispatcherPriority.cs b/src/Avalonia.Base/Threading/DispatcherPriority.cs index f041590fc0..1ae292f280 100644 --- a/src/Avalonia.Base/Threading/DispatcherPriority.cs +++ b/src/Avalonia.Base/Threading/DispatcherPriority.cs @@ -45,32 +45,37 @@ namespace Avalonia.Threading /// /// The job will be processed after other non-idle operations have completed. /// - public static readonly DispatcherPriority Background = new(1); + public static readonly DispatcherPriority Background = new(MinValue + 1); /// /// The job will be processed with the same priority as input. /// - public static readonly DispatcherPriority Input = new(2); + public static readonly DispatcherPriority Input = new(Background + 1); /// /// The job will be processed after layout and render but before input. /// - public static readonly DispatcherPriority Loaded = new(3); - + public static readonly DispatcherPriority Loaded = new(Input + 1); + /// /// The job will be processed with the same priority as render. /// - public static readonly DispatcherPriority Render = new(5); - + public static readonly DispatcherPriority Render = new(Loaded + 1); + /// /// The job will be processed with the same priority as composition updates. /// - public static readonly DispatcherPriority Composition = new(6); - + public static readonly DispatcherPriority Composition = new(Render + 1); + /// - /// The job will be processed with the same priority as render. + /// The job will be processed with the same priority as composition updates. + /// + public static readonly DispatcherPriority PreComposition = new(Composition + 1); + + /// + /// The job will be processed with the same priority as layout. /// - public static readonly DispatcherPriority Layout = new(7); + public static readonly DispatcherPriority Layout = new(PreComposition + 1); /// /// The job will be processed with the same priority as data binding. @@ -80,7 +85,7 @@ namespace Avalonia.Threading /// /// The job will be processed before other asynchronous operations. /// - public static readonly DispatcherPriority Send = new(8); + public static readonly DispatcherPriority Send = new(Layout + 1); /// /// Maximum possible priority From 60b49639584a849c7c3677cfc2679c154ee979e2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 27 Nov 2022 16:25:14 +0300 Subject: [PATCH 5/5] Update DispatcherPriority.cs --- src/Avalonia.Base/Threading/DispatcherPriority.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Threading/DispatcherPriority.cs b/src/Avalonia.Base/Threading/DispatcherPriority.cs index 1ae292f280..b3194e249b 100644 --- a/src/Avalonia.Base/Threading/DispatcherPriority.cs +++ b/src/Avalonia.Base/Threading/DispatcherPriority.cs @@ -68,7 +68,7 @@ namespace Avalonia.Threading public static readonly DispatcherPriority Composition = new(Render + 1); /// - /// The job will be processed with the same priority as composition updates. + /// The job will be processed with before composition updates. /// public static readonly DispatcherPriority PreComposition = new(Composition + 1); @@ -128,4 +128,4 @@ namespace Avalonia.Threading /// public int CompareTo(DispatcherPriority other) => Value.CompareTo(other.Value); } -} \ No newline at end of file +}