Browse Source

Plumb child layout clips through rendering

pull/20647/head
Wiesław Šoltés 1 month ago
parent
commit
05dbca2125
  1. 37
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  2. 26
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  3. 14
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  4. 70
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs
  5. 20
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  6. 12
      src/Avalonia.Base/Visual.Composition.cs
  7. 20
      src/Avalonia.Controls/BorderVisual.cs

37
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@ -1,3 +1,4 @@
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
@ -16,6 +17,10 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
private bool _drawListChanged;
private CompositionRenderData? _drawList;
private bool _childClipChanged;
private bool _hasChildClip;
private RoundedRect _childClip;
private IGeometryImpl? _childClipGeometry;
/// <summary>
/// The list of drawing commands
@ -36,6 +41,30 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
}
}
internal void SetChildClip(RoundedRect clip, IGeometryImpl? geometryClip)
{
if (_hasChildClip && _childClip.Equals(clip) && ReferenceEquals(_childClipGeometry, geometryClip))
return;
_hasChildClip = true;
_childClip = clip;
_childClipGeometry = geometryClip;
_childClipChanged = true;
RegisterForSerialization();
}
internal void ClearChildClip()
{
if (!_hasChildClip && _childClipGeometry == null)
return;
_hasChildClip = false;
_childClip = default;
_childClipGeometry = null;
_childClipChanged = true;
RegisterForSerialization();
}
private protected override void SerializeChangesCore(BatchStreamWriter writer)
{
writer.Write((byte)(_drawListChanged ? 1 : 0));
@ -44,6 +73,14 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
writer.WriteObject(DrawList?.Server);
_drawListChanged = false;
}
writer.Write((byte)(_childClipChanged ? 1 : 0));
if (_childClipChanged)
{
writer.Write(_hasChildClip);
writer.Write(_childClip);
writer.WriteObject(_childClipGeometry);
_childClipChanged = false;
}
base.SerializeChangesCore(writer);
}

26
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Collections.Pooled;
using Avalonia.VisualTree;
@ -72,20 +73,19 @@ namespace Avalonia.Rendering.Composition
var point = parentPoint.Transform(invMatrix);
var allowChildren = true;
if (visual.ClipToBounds)
if (visual is CompositionDrawListVisual { Visual: Visual childClipProvider }
&& childClipProvider.TryGetChildClip(out var childClip, out var childClipGeometry))
{
if (visual is CompositionDrawListVisual { Visual: IVisualWithChildClip childClipProvider }
&& childClipProvider.TryGetChildClip(out var childClip))
{
if (point.X < 0 || point.Y < 0 || point.X > visual.Size.X || point.Y > visual.Size.Y)
return;
if (!childClip.ContainsExclusive(point))
allowChildren = false;
}
else if (point.X < 0 || point.Y < 0 || point.X > visual.Size.X || point.Y > visual.Size.Y)
{
return;
}
var clipContains = childClipGeometry == null
? childClip.ContainsExclusive(point)
: childClipGeometry.FillContains(point);
if (!clipContains)
allowChildren = false;
}
else if (visual.ClipToBounds &&
(point.X < 0 || point.Y < 0 || point.X > visual.Size.X || point.Y > visual.Size.Y))
{
return;
}
if (visual.Clip?.FillContains(point) == false)

14
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@ -19,6 +19,9 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
public readonly Visual UiVisual;
#endif
private ServerCompositionRenderData? _renderCommands;
private bool _hasChildClip;
private RoundedRect _childClip;
private IGeometryImpl? _childClipGeometry;
public ServerCompositionDrawListVisual(ServerCompositor compositor, Visual v) : base(compositor)
{
@ -29,6 +32,10 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
public override LtrbRect? ComputeOwnContentBounds() => _renderCommands?.Bounds;
public bool HasChildClip => _hasChildClip;
public RoundedRect ChildClip => _childClip;
public IGeometryImpl? ChildClipGeometry => _childClipGeometry;
protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
{
if (reader.Read<byte>() == 1)
@ -38,6 +45,13 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
_renderCommands?.AddObserver(this);
InvalidateContent();
}
if (reader.Read<byte>() == 1)
{
_hasChildClip = reader.Read<bool>();
_childClip = reader.Read<RoundedRect>();
_childClipGeometry = reader.ReadObject<IGeometryImpl?>();
InvalidateContent();
}
base.DeserializeChangesCore(reader, committedAt);
}

70
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual/ServerCompositionVisual.Render.cs

@ -16,6 +16,7 @@ partial class ServerCompositionVisual
private readonly bool _renderChildren;
private TreeWalkContext _walkContext;
private Stack<double> _opacityStack;
private Stack<int> _childClipSwapStack;
private double _opacity;
private bool _fullSkip;
private bool _usedCache;
@ -51,6 +52,7 @@ partial class ServerCompositionVisual
_opacity = 1;
_opacityStack = pools.DoubleStackPool.Rent();
_childClipSwapStack = pools.IntStackPool.Rent();
_skipNextVisualTransform = skipRootVisualTransform;
_renderingToBitmapCache = renderingToBitmapCache;
}
@ -122,6 +124,7 @@ partial class ServerCompositionVisual
{
VisitedVisuals++;
var bitmapCacheRoot = _renderingToBitmapCache && visual == _rootVisual;
ServerCompositionDrawListVisual? childClipVisual = null;
if (!bitmapCacheRoot) // Skip those for the root visual if we are rendering to bitmap cache
{
@ -137,6 +140,9 @@ partial class ServerCompositionVisual
if (visual.AdornedVisual != null)
AdornerHelper_RenderPreGraphPushAdornerClip(visual);
if (visual is ServerCompositionDrawListVisual drawListVisual && drawListVisual.HasChildClip)
childClipVisual = drawListVisual;
// If caching is enabled, draw from cache and skip rendering
if (visual.Cache != null)
{
@ -144,6 +150,7 @@ partial class ServerCompositionVisual
VisitedVisuals += visited;
RenderedVisuals += rendered;
_usedCache = true;
_childClipSwapStack.Push(0);
visitChildren = false;
return;
}
@ -162,7 +169,39 @@ partial class ServerCompositionVisual
effects.PushEffect(visual._subTreeBounds!.Value.ToRect(), visual.Effect);
visual.RenderCore(_publicContext, _walkContext.Clip);
var childClipSwapState = 0;
if (!bitmapCacheRoot && childClipVisual != null)
{
var clipGeometry = visual.Clip;
var hasClipGeometry = clipGeometry != null;
var useGeometryClip = childClipVisual.ChildClipGeometry != null;
var hasBoundsClip = visual.ClipToBounds;
if (hasClipGeometry)
_canvas.PopGeometryClip();
if (hasBoundsClip)
_canvas.PopClip();
if (useGeometryClip)
{
if (hasBoundsClip)
visual.PushClipToBounds(_canvas);
_canvas.PushGeometryClip(childClipVisual.ChildClipGeometry!);
}
else
{
_canvas.PushClip(childClipVisual.ChildClip);
}
if (hasClipGeometry)
_canvas.PushGeometryClip(clipGeometry!);
childClipSwapState = 1 | (useGeometryClip ? 2 : 0) | (hasClipGeometry ? 4 : 0);
}
_childClipSwapStack.Push(childClipSwapState);
visitChildren = _renderChildren;
}
@ -175,6 +214,34 @@ partial class ServerCompositionVisual
}
var bitmapCacheRoot = _renderingToBitmapCache && visual == _rootVisual;
var childClipSwapState = _childClipSwapStack.Pop();
if (childClipSwapState != 0)
{
var hasClipGeometry = (childClipSwapState & 4) != 0;
var useGeometryClip = (childClipSwapState & 2) != 0;
var hasBoundsClip = visual.ClipToBounds;
if (hasClipGeometry)
_canvas.PopGeometryClip();
if (useGeometryClip)
{
_canvas.PopGeometryClip();
if (hasBoundsClip)
_canvas.PopClip();
}
else
{
_canvas.PopClip();
}
if (hasBoundsClip)
visual.PushClipToBounds(_canvas);
if (hasClipGeometry)
_canvas.PushGeometryClip(visual.Clip!);
}
// If we've used cache, those never got pushed in PreSubgraph
if (!_usedCache)
@ -225,6 +292,7 @@ partial class ServerCompositionVisual
{
_walkContext.Dispose();
_pools.DoubleStackPool.Return(ref _opacityStack);
_pools.IntStackPool.Return(ref _childClipSwapStack);
AdornerHelper_Dispose();
}
}

20
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -50,9 +50,12 @@ internal class ImmediateRenderer
transform = Matrix.CreateTranslation(bounds.Position);
}
var childClipProvider = visual.ClipToBounds ? visual as IVisualWithChildClip : null;
var childClipProvider = visual;
RoundedRect childClip = default;
var hasChildClip = childClipProvider != null && childClipProvider.TryGetChildClip(out childClip);
Geometry? childClipGeometry = null;
var hasChildClip = childClipProvider != null &&
childClipProvider.TryGetChildClip(out childClip, out childClipGeometry);
var useGeometryClip = hasChildClip && childClipGeometry != null;
using (visual.TextOptions != default ? context.PushTextOptions(visual.TextOptions) : default(DrawingContext.PushedState?))
using (visual.RenderOptions != default ? context.PushRenderOptions(visual.RenderOptions) : default(DrawingContext.PushedState?))
@ -83,10 +86,19 @@ internal class ImmediateRenderer
if (visual.ClipToBounds)
{
totalTransform = Matrix.Identity;
clipRect = hasChildClip ? childClip.Rect : rect;
clipRect = hasChildClip
? (useGeometryClip ? rect : childClip.Rect)
: rect;
}
using (hasChildClip ? context.PushClip(childClip) : default(DrawingContext.PushedState?))
using (hasChildClip && useGeometryClip && visual.ClipToBounds
? context.PushClip(rect)
: default(DrawingContext.PushedState?))
using (hasChildClip
? (useGeometryClip
? context.PushGeometryClip(childClipGeometry!)
: context.PushClip(childClip))
: default(DrawingContext.PushedState?))
{
foreach (var child in childrenEnumerable)
{

12
src/Avalonia.Base/Visual.Composition.cs

@ -140,6 +140,18 @@ public partial class Visual
comp.Opacity = (float)Opacity;
comp.ClipToBounds = ClipToBounds;
comp.Clip = Clip?.PlatformImpl;
if (comp is CompositionDrawListVisual drawListVisual)
{
if (TryGetChildClip(out var childClip, out var childClipGeometry))
{
drawListVisual.SetChildClip(childClip, childClipGeometry?.PlatformImpl);
}
else
{
drawListVisual.ClearChildClip();
}
}
if (!Equals(comp.OpacityMask, OpacityMask))
comp.OpacityMask = OpacityMask;

20
src/Avalonia.Controls/BorderVisual.cs

@ -70,13 +70,6 @@ class CompositionBorderVisual : CompositionDrawListVisual
protected override void RenderCore(ServerVisualRenderContext ctx, LtrbRect currentTransformedClip)
{
if (ClipToBounds && Clip == null)
{
base.RenderCore(ctx, currentTransformedClip);
ReplaceBoundsClipWithChildClip(ctx.Canvas);
return;
}
base.RenderCore(ctx, currentTransformedClip);
}
protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
@ -94,19 +87,6 @@ class CompositionBorderVisual : CompositionDrawListVisual
canvas.PushClip(new Rect(0, 0, Size.X, Size.Y));
}
private void ReplaceBoundsClipWithChildClip(IDrawingContextImpl canvas)
{
canvas.PopClip();
var clipRect = new Rect(new Size(Size.X, Size.Y));
var keypoints = GeometryBuilder.CalculateRoundedCornersRectangleWinUI(
clipRect,
_borderThickness,
_cornerRadius,
BackgroundSizing.InnerBorderEdge);
canvas.PushClip(keypoints.ToRoundedRect());
}
}
}

Loading…
Cancel
Save