From 815545dfcd758ece930125d5072e26c8f421c6dc Mon Sep 17 00:00:00 2001 From: stepan_govorko Date: Wed, 16 Aug 2023 13:13:15 +0200 Subject: [PATCH 1/5] Consider Clip.Bounds in clipping calculation in ServerCompositionVisual; In addition, the ServerCompositionVisual now tracks changes to the clip property, allowing for re-calculation when the clip is altered. --- ...ServerCompositionVisual.DirtyProperties.cs | 1 + .../Server/ServerCompositionVisual.cs | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs index c1037d5c67..51414c2250 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs @@ -38,6 +38,7 @@ partial class ServerCompositionVisual CompositionVisualChangedFields.Size | CompositionVisualChangedFields.SizeAnimated | CompositionVisualChangedFields.ClipToBounds + | CompositionVisualChangedFields.Clip | CompositionVisualChangedFields.ClipToBoundsAnimated; partial void OnFieldsDeserialized(CompositionVisualChangedFields changed) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 45515a37e2..fd1e2165b7 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -182,11 +182,24 @@ namespace Avalonia.Rendering.Composition.Server if (_clipSizeDirty || positionChanged) { - _transformedClipBounds = ClipToBounds - ? new Rect(new Size(Size.X, Size.Y)) - .TransformToAABB(GlobalTransformMatrix) - : null; + Rect? transformedVisualBounds = null; + Rect? transformedClipBounds = null; + if (ClipToBounds) + transformedVisualBounds = new Rect(new Size(Size.X, Size.Y)).TransformToAABB(GlobalTransformMatrix); + + if (Clip != null) + transformedClipBounds = Clip.Bounds.TransformToAABB(GlobalTransformMatrix); + + if (transformedVisualBounds != null && transformedClipBounds != null) + _transformedClipBounds = transformedVisualBounds.Value.Intersect(transformedClipBounds.Value); + else if (transformedVisualBounds != null) + _transformedClipBounds = transformedVisualBounds; + else if (transformedClipBounds != null) + _transformedClipBounds = transformedClipBounds; + else + _transformedClipBounds = null; + _clipSizeDirty = false; } From 77dc35bf469fbc583d9c09add0fa255e9badd1bc Mon Sep 17 00:00:00 2001 From: stepan_govorko Date: Thu, 17 Aug 2023 09:26:25 +0200 Subject: [PATCH 2/5] Introduced counting of rendered visuals in ServerCompositionVisual and added relevant unit tests in CompositorInvalidationClippingTests. The new tests ensure that visuals that are not in dirty rect are rendered correctly with different ClipToBounds and Clip geometry parameters. --- .../ICompositionTargetDebugEvents.cs | 2 + .../Server/ServerCompositionVisual.cs | 1 + .../CompositorInvalidationClippingTests.cs | 60 +++++++++++++++++++ .../CompositorTestServices.cs | 17 +++++- 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs diff --git a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs index c830ca2c49..cfbce221d6 100644 --- a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs +++ b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs @@ -2,5 +2,7 @@ namespace Avalonia.Rendering.Composition; internal interface ICompositionTargetDebugEvents { + public int RenderedVisuals { get; } + void IncrementRenderedVisuals(); void RectInvalidated(Rect rc); } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index fd1e2165b7..aeb228282e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -38,6 +38,7 @@ namespace Avalonia.Rendering.Composition.Server return; Root!.RenderedVisuals++; + Root!.DebugEvents?.IncrementRenderedVisuals(); var boundsRect = new Rect(new Size(Size.X, Size.Y)); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs new file mode 100644 index 0000000000..1de2cfa717 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs @@ -0,0 +1,60 @@ +using Avalonia.Controls; +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Base.UnitTests.Rendering; + +public class CompositorInvalidationClippingTests : CompositorTestsBase +{ + [Fact] + public void Siblings_Should_Be_Rendered_On_Invalidate_Without_ClipToBounds() + { + AssertRenderedVisuals(clipToBounds: false, clipGeometry: false, expectedRenderedVisualsCount: 4); + } + + [Fact] + public void Siblings_Should_Not_Be_Rendered_On_Invalidate_With_ClipToBounds() + { + AssertRenderedVisuals(clipToBounds: true, clipGeometry: false, expectedRenderedVisualsCount: 3); + } + + [Fact] + public void Siblings_Should_Not_Be_Rendered_On_Invalidate_With_Clip() + { + AssertRenderedVisuals(clipToBounds: false, clipGeometry: true, expectedRenderedVisualsCount: 3); + } + + private void AssertRenderedVisuals(bool clipToBounds, bool clipGeometry, int expectedRenderedVisualsCount) + { + using (var s = new CompositorCanvas()) + { + //#1 visual to render is root + //#2 visual to render is s.Canvas + + //#3 visual to render + s.Canvas.Children.Add(new Border() + { + [Canvas.LeftProperty] = 0, [Canvas.TopProperty] = 0, + Width = 20, Height = 10, + Background = Brushes.Red, + ClipToBounds = clipToBounds, + Clip = clipGeometry ? new RectangleGeometry(new Rect(new Size(20, 10))) : null + }); + + //#4 visual to render + s.Canvas.Children.Add(new Border() + { + [Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50, + Width = 20, Height = 10, + Background = Brushes.Red, + ClipToBounds = clipToBounds, + Clip = clipGeometry ? new RectangleGeometry(new Rect(new Size(20, 10))) : null + }); + s.RunJobs(); + s.Events.Reset(); + s.Canvas.Children[0].IsVisible = false; + s.RunJobs(); + s.AssertRenderedVisuals(expectedRenderedVisualsCount); + } + } +} diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index 53fd610a17..00645259a5 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -89,6 +89,13 @@ public class CompositorTestServices : IDisposable Events.Rects.Clear(); } + public void AssertRenderedVisuals(int renderVisuals) + { + RunJobs(); + Assert.Equal(Events.RenderedVisuals, renderVisuals); + Events.Rects.Clear(); + } + public void AssertHitTest(double x, double y, Func filter, params object[] expected) => AssertHitTest(new Point(x, y), filter, expected); @@ -110,6 +117,13 @@ public class CompositorTestServices : IDisposable { public List Rects = new(); + public int RenderedVisuals { get; private set; } + + public void IncrementRenderedVisuals() + { + RenderedVisuals++; + } + public void RectInvalidated(Rect rc) { Rects.Add(rc); @@ -118,6 +132,7 @@ public class CompositorTestServices : IDisposable public void Reset() { Rects.Clear(); + RenderedVisuals = 0; } } @@ -218,4 +233,4 @@ public class DispatcherCompositorScheduler : ICompositorScheduler { Dispatcher.UIThread.Post(() => compositor.Commit(), DispatcherPriority.UiThreadRender); } -} \ No newline at end of file +} From f5c93a50abb12ce6c1e4bf40200a1eaf916c9f12 Mon Sep 17 00:00:00 2001 From: stepan_govorko Date: Thu, 17 Aug 2023 09:51:04 +0200 Subject: [PATCH 3/5] added more comments --- .../CompositorInvalidationClippingTests.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs index 1de2cfa717..c158ff4e75 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs @@ -3,22 +3,27 @@ using Avalonia.Media; using Xunit; namespace Avalonia.Base.UnitTests.Rendering; - +/// +/// Test class that verifies how clipping influences rendering in the compositor +/// public class CompositorInvalidationClippingTests : CompositorTestsBase { [Fact] + // Test case: When the ClipToBounds is false, all visuals should be rendered public void Siblings_Should_Be_Rendered_On_Invalidate_Without_ClipToBounds() { AssertRenderedVisuals(clipToBounds: false, clipGeometry: false, expectedRenderedVisualsCount: 4); } [Fact] + // Test case: When the ClipToBounds is true, only visuals within the clipped boundary should be rendered public void Siblings_Should_Not_Be_Rendered_On_Invalidate_With_ClipToBounds() { AssertRenderedVisuals(clipToBounds: true, clipGeometry: false, expectedRenderedVisualsCount: 3); } [Fact] + // Test case: When the Clip is used, only visuals within the clip geometry should be rendered public void Siblings_Should_Not_Be_Rendered_On_Invalidate_With_Clip() { AssertRenderedVisuals(clipToBounds: false, clipGeometry: true, expectedRenderedVisualsCount: 3); @@ -28,10 +33,10 @@ public class CompositorInvalidationClippingTests : CompositorTestsBase { using (var s = new CompositorCanvas()) { - //#1 visual to render is root - //#2 visual to render is s.Canvas + //#1 visual is top level + //#2 visual is s.Canvas - //#3 visual to render + //#3 visual is border1 s.Canvas.Children.Add(new Border() { [Canvas.LeftProperty] = 0, [Canvas.TopProperty] = 0, @@ -41,7 +46,7 @@ public class CompositorInvalidationClippingTests : CompositorTestsBase Clip = clipGeometry ? new RectangleGeometry(new Rect(new Size(20, 10))) : null }); - //#4 visual to render + //#4 visual is border2 s.Canvas.Children.Add(new Border() { [Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50, @@ -52,8 +57,11 @@ public class CompositorInvalidationClippingTests : CompositorTestsBase }); s.RunJobs(); s.Events.Reset(); + + //invalidate border1 s.Canvas.Children[0].IsVisible = false; s.RunJobs(); + s.AssertRenderedVisuals(expectedRenderedVisualsCount); } } From fa6939aa39496bd3e468b2cd6eec090fffc7dda8 Mon Sep 17 00:00:00 2001 From: stepan_govorko Date: Fri, 18 Aug 2023 06:22:27 +0200 Subject: [PATCH 4/5] The unneeded public access modifier was removed from the RenderedVisuals property in the ICompositionTargetDebugEvents interface. --- .../Rendering/Composition/ICompositionTargetDebugEvents.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs index cfbce221d6..27aca436b8 100644 --- a/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs +++ b/src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs @@ -2,7 +2,7 @@ namespace Avalonia.Rendering.Composition; internal interface ICompositionTargetDebugEvents { - public int RenderedVisuals { get; } + int RenderedVisuals { get; } void IncrementRenderedVisuals(); void RectInvalidated(Rect rc); }