Browse Source

Merge pull request #12568 from JetBrains/fixes/clip-bounds-invalidate

Enhanced Clipping and Rendered Visuals Tracking in ServerCompositionVisual
pull/12579/head
Nikita Tsukanov 3 years ago
committed by GitHub
parent
commit
c59e63ef6f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs
  2. 1
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs
  3. 22
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  4. 68
      tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs
  5. 17
      tests/Avalonia.UnitTests/CompositorTestServices.cs

2
src/Avalonia.Base/Rendering/Composition/ICompositionTargetDebugEvents.cs

@ -2,5 +2,7 @@ namespace Avalonia.Rendering.Composition;
internal interface ICompositionTargetDebugEvents
{
int RenderedVisuals { get; }
void IncrementRenderedVisuals();
void RectInvalidated(Rect rc);
}

1
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)

22
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));
@ -182,11 +183,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;
}

68
tests/Avalonia.Base.UnitTests/Rendering/CompositorInvalidationClippingTests.cs

@ -0,0 +1,68 @@
using Avalonia.Controls;
using Avalonia.Media;
using Xunit;
namespace Avalonia.Base.UnitTests.Rendering;
/// <summary>
/// Test class that verifies how clipping influences rendering in the compositor
/// </summary>
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);
}
private void AssertRenderedVisuals(bool clipToBounds, bool clipGeometry, int expectedRenderedVisualsCount)
{
using (var s = new CompositorCanvas())
{
//#1 visual is top level
//#2 visual is s.Canvas
//#3 visual is border1
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 is border2
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();
//invalidate border1
s.Canvas.Children[0].IsVisible = false;
s.RunJobs();
s.AssertRenderedVisuals(expectedRenderedVisualsCount);
}
}
}

17
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<Visual, bool> filter, params object[] expected)
=> AssertHitTest(new Point(x, y), filter, expected);
@ -110,6 +117,13 @@ public class CompositorTestServices : IDisposable
{
public List<Rect> 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);
}
}
}

Loading…
Cancel
Save