Browse Source

Use separate LtrbRect type for renderer calculations (#15112)

pull/15074/head
Nikita Tsukanov 2 years ago
committed by GitHub
parent
commit
5b59f6c0bc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      src/Avalonia.Base/Platform/IPlatformRenderInterfaceRegion.cs
  2. 268
      src/Avalonia.Base/Platform/LtrbRect.cs
  3. 2
      src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
  4. 2
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs
  5. 22
      src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs
  6. 42
      src/Avalonia.Base/Rendering/Composition/Server/DirtyRectTracker.cs
  7. 8
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
  8. 4
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  9. 4
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionExperimentalAcrylicVisual.cs
  10. 3
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs
  11. 3
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs
  12. 28
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.DirtyRects.cs
  13. 6
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  14. 61
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  15. 9
      src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs
  16. 3
      src/Avalonia.Controls/BorderVisual.cs
  17. 4
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  18. 17
      src/Skia/Avalonia.Skia/SkiaRegionImpl.cs
  19. 20
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  20. 2
      tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

8
src/Avalonia.Base/Platform/IPlatformRenderInterfaceRegion.cs

@ -7,11 +7,11 @@ namespace Avalonia.Platform;
[Unstable, PrivateApi] [Unstable, PrivateApi]
public interface IPlatformRenderInterfaceRegion : IDisposable public interface IPlatformRenderInterfaceRegion : IDisposable
{ {
void AddRect(PixelRect rect); void AddRect(LtrbPixelRect rect);
void Reset(); void Reset();
bool IsEmpty { get; } bool IsEmpty { get; }
PixelRect Bounds { get; } LtrbPixelRect Bounds { get; }
IList<PixelRect> Rects { get; } IList<LtrbPixelRect> Rects { get; }
bool Intersects(Rect rect); bool Intersects(LtrbRect rect);
bool Contains(Point pt); bool Contains(Point pt);
} }

268
src/Avalonia.Base/Platform/LtrbRect.cs

@ -0,0 +1,268 @@
// ReSharper disable CompareOfFloatsByEqualityOperator
using System;
using Avalonia.Metadata;
namespace Avalonia.Platform;
/// <summary>
/// This struct is essentially the same thing as MilRectD
/// Unlike our "normal" Rect which is more human-readable and human-usable
/// this struct is optimized for actual processing that doesn't really care
/// about Width and Height but pretty much always only cares about
/// Right and Bottom edge coordinates
///
/// Not having to constantly convert between Width/Height and Right/Bottom for no actual reason
/// saves us some perf
///
/// This structure is intended to be mostly internal, but it's exposed as a PrivateApi type so it can
/// be passed to the drawing backend when needed
/// </summary>
[PrivateApi]
public struct LtrbRect
{
public double Left, Top, Right, Bottom;
internal LtrbRect(double x, double y, double right, double bottom)
{
Left = x;
Top = y;
Right = right;
Bottom = bottom;
}
internal LtrbRect(Rect rc)
{
rc = rc.Normalize();
Left = rc.X;
Top = rc.Y;
Right = rc.Right;
Bottom = rc.Bottom;
}
internal bool IsZeroSize => Left == Right && Top == Bottom;
internal LtrbRect Intersect(LtrbRect rect)
{
var newLeft = (rect.Left > Left) ? rect.Left : Left;
var newTop = (rect.Top > Top) ? rect.Top : Top;
var newRight = (rect.Right < Right) ? rect.Right : Right;
var newBottom = (rect.Bottom < Bottom) ? rect.Bottom : Bottom;
if ((newRight > newLeft) && (newBottom > newTop))
{
return new LtrbRect(newLeft, newTop, newRight, newBottom);
}
else
{
return default;
}
}
internal bool Intersects(LtrbRect rect)
{
return (rect.Left < Right) && (Left < rect.Right) && (rect.Top < Bottom) && (Top < rect.Bottom);
}
internal Rect ToRect() => new(Left, Top, Right - Left, Bottom - Top);
internal LtrbRect Inflate(Thickness thickness)
{
return new LtrbRect(Left - thickness.Left, Top - thickness.Top, Right + thickness.Right,
Bottom + thickness.Bottom);
}
public static bool operator ==(LtrbRect left, LtrbRect right)=>
left.Left == right.Left && left.Top == right.Top && left.Right == right.Right && left.Bottom == right.Bottom;
public static bool operator !=(LtrbRect left, LtrbRect right) =>
left.Left != right.Left || left.Top != right.Top || left.Right != right.Right || left.Bottom != right.Bottom;
public bool Equals(LtrbRect other) =>
other.Left == Left && other.Top == Top && other.Right == Right && other.Bottom == Bottom;
public bool Equals(ref LtrbRect other) =>
other.Left == Left && other.Top == Top && other.Right == Right && other.Bottom == Bottom;
internal Point TopLeft => new Point(Left, Top);
internal Point TopRight => new Point(Right, Top);
internal Point BottomLeft => new Point(Left, Bottom);
internal Point BottomRight => new Point(Right, Bottom);
internal LtrbRect TransformToAABB(Matrix matrix)
{
ReadOnlySpan<Point> points = stackalloc Point[4]
{
TopLeft.Transform(matrix),
TopRight.Transform(matrix),
BottomRight.Transform(matrix),
BottomLeft.Transform(matrix)
};
var left = double.MaxValue;
var right = double.MinValue;
var top = double.MaxValue;
var bottom = double.MinValue;
foreach (var p in points)
{
if (p.X < left) left = p.X;
if (p.X > right) right = p.X;
if (p.Y < top) top = p.Y;
if (p.Y > bottom) bottom = p.Y;
}
return new LtrbRect(left, top, right, bottom);
}
/// <summary>
/// Perform _WPF-like_ union operation
/// </summary>
private LtrbRect FullUnionCore(LtrbRect rect)
{
var x1 = Math.Min(Left, rect.Left);
var x2 = Math.Max(Right, rect.Right);
var y1 = Math.Min(Top, rect.Top);
var y2 = Math.Max(Bottom, rect.Bottom);
return new(x1, y1, x2, y2);
}
internal static LtrbRect? FullUnion(LtrbRect? left, LtrbRect? right)
{
if (left == null)
return right;
if (right == null)
return left;
return right.Value.FullUnionCore(left.Value);
}
internal static LtrbRect? FullUnion(LtrbRect? left, Rect? right)
{
if (right == null)
return left;
if (left == null)
return new(right.Value);
return left.Value.FullUnionCore(new(right.Value));
}
public override bool Equals(object? obj)
{
if (obj is LtrbRect other)
return Equals(other);
return false;
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = (hash * 23) + Left.GetHashCode();
hash = (hash * 23) + Top.GetHashCode();
hash = (hash * 23) + Right.GetHashCode();
hash = (hash * 23) + Bottom.GetHashCode();
return hash;
}
}
}
/// <summary>
/// This struct is essentially the same thing as RECT from win32 API
/// Unlike our "normal" PixelRect which is more human-readable and human-usable
/// this struct is optimized for actual processing that doesn't really care
/// about Width and Height but pretty much always only cares about
/// Right and Bottom edge coordinates
///
/// Not having to constantly convert between Width/Height and Right/Bottom for no actual reason
/// saves us some perf
///
/// This structure is intended to be mostly internal, but it's exposed as a PrivateApi type so it can
/// be passed to the drawing backend when needed
/// </summary>
[PrivateApi]
public struct LtrbPixelRect
{
public int Left, Top, Right, Bottom;
internal LtrbPixelRect(int x, int y, int right, int bottom)
{
Left = x;
Top = y;
Right = right;
Bottom = bottom;
}
internal LtrbPixelRect(PixelSize size)
{
Left = 0;
Top = 0;
Right = size.Width;
Bottom = size.Height;
}
internal bool IsEmpty => Left == Right && Top == Bottom;
internal PixelRect ToPixelRect() => new(Left, Top, Right - Left, Bottom - Top);
internal LtrbPixelRect Union(LtrbPixelRect rect)
{
if (IsEmpty)
return rect;
if (rect.IsEmpty)
return this;
var x1 = Math.Min(Left, rect.Left);
var x2 = Math.Max(Right, rect.Right);
var y1 = Math.Min(Top, rect.Top);
var y2 = Math.Max(Bottom, rect.Bottom);
return new(x1, y1, x2, y2);
}
internal Rect ToRectWithNoScaling() => new(Left, Top, (Right - Left), (Bottom - Top));
internal bool Contains(int x, int y)
{
return x >= Left && x <= Right && y >= Top && y <= Bottom;
}
internal static LtrbPixelRect FromRectWithNoScaling(LtrbRect rect) =>
new((int)rect.Left, (int)rect.Top, (int)Math.Ceiling(rect.Right),
(int)Math.Ceiling(rect.Bottom));
public static bool operator ==(LtrbPixelRect left, LtrbPixelRect right)=>
left.Left == right.Left && left.Top == right.Top && left.Right == right.Right && left.Bottom == right.Bottom;
public static bool operator !=(LtrbPixelRect left, LtrbPixelRect right) =>
left.Left != right.Left || left.Top != right.Top || left.Right != right.Right || left.Bottom != right.Bottom;
public bool Equals(LtrbPixelRect other) =>
other.Left == Left && other.Top == Top && other.Right == Right && other.Bottom == Bottom;
public override bool Equals(object? obj)
{
if (obj is LtrbPixelRect other)
return Equals(other);
return false;
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = (hash * 23) + Left.GetHashCode();
hash = (hash * 23) + Top.GetHashCode();
hash = (hash * 23) + Right.GetHashCode();
hash = (hash * 23) + Bottom.GetHashCode();
return hash;
}
}
internal Rect ToRectUnscaled() => new(Left, Top, Right - Left, Bottom - Top);
internal static LtrbPixelRect FromRectUnscaled(LtrbRect rect)
{
return new LtrbPixelRect((int)rect.Left, (int)rect.Top, (int)Math.Ceiling(rect.Right),
(int)Math.Ceiling(rect.Bottom));
}
}

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

@ -105,6 +105,6 @@ public abstract class CompositionCustomVisualHandler
{ {
VerifyInRender(); VerifyInRender();
rc = rc.TransformToAABB(_host!.GlobalTransformMatrix); rc = rc.TransformToAABB(_host!.GlobalTransformMatrix);
return _currentTransformedClip.Intersects(rc) && _host.Root!.DirtyRects.Intersects(rc); return _currentTransformedClip.Intersects(rc) && _host.Root!.DirtyRects.Intersects(new (rc));
} }
} }

2
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionRenderDataSceneBrushContent.cs

@ -19,7 +19,7 @@ internal class CompositionRenderDataSceneBrushContent : ISceneBrushContent
} }
public ITileBrush Brush { get; } public ITileBrush Brush { get; }
public Rect Rect => _rect ?? (RenderData.Server?.Bounds ?? default); public Rect Rect => _rect ?? (RenderData.Server?.Bounds?.ToRect() ?? default);
public double Opacity => Brush.Opacity; public double Opacity => Brush.Opacity;
public ITransform? Transform => Brush.Transform; public ITransform? Transform => Brush.Transform;

22
src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs

@ -14,7 +14,7 @@ class ServerCompositionRenderData : SimpleServerRenderResource
{ {
private PooledInlineList<IRenderDataItem> _items; private PooledInlineList<IRenderDataItem> _items;
private PooledInlineList<IServerRenderResource> _referencedResources; private PooledInlineList<IServerRenderResource> _referencedResources;
private Rect? _bounds; private LtrbRect? _bounds;
private bool _boundsValid; private bool _boundsValid;
private static readonly ThreadSafeObjectPool<Collector> s_resourceHashSetPool = new(); private static readonly ThreadSafeObjectPool<Collector> s_resourceHashSetPool = new();
@ -67,7 +67,7 @@ class ServerCompositionRenderData : SimpleServerRenderResource
} }
} }
public Rect? Bounds public LtrbRect? Bounds
{ {
get get
{ {
@ -80,25 +80,31 @@ class ServerCompositionRenderData : SimpleServerRenderResource
} }
} }
private Rect? CalculateRenderBounds() private LtrbRect? CalculateRenderBounds()
{ {
Rect? totalBounds = null; LtrbRect? totalBounds = null;
foreach (var item in _items) foreach (var item in _items)
totalBounds = Rect.Union(totalBounds, item.Bounds); totalBounds = LtrbRect.FullUnion(totalBounds, item.Bounds);
return ApplyRenderBoundsRounding(totalBounds); return ApplyRenderBoundsRounding(totalBounds);
} }
public static Rect? ApplyRenderBoundsRounding(Rect? rect) public static Rect? ApplyRenderBoundsRounding(Rect? rect)
{
if (rect == null)
return null;
return ApplyRenderBoundsRounding(new LtrbRect(rect.Value))?.ToRect();
}
public static LtrbRect? ApplyRenderBoundsRounding(LtrbRect? rect)
{ {
if (rect != null) if (rect != null)
{ {
var r = rect.Value; var r = rect.Value;
// I don't believe that it's correct to do here (rather than in CompositionVisual), // I don't believe that it's correct to do here (rather than in CompositionVisual),
// but it's the old behavior, so I'm keeping it for now // but it's the old behavior, so I'm keeping it for now
return new Rect( return new LtrbRect(Math.Floor(r.Left), Math.Floor(r.Top),
new Point(Math.Floor(r.X), Math.Floor(r.Y)), Math.Ceiling(r.Right), Math.Ceiling(r.Bottom));
new Point(Math.Ceiling(r.Right), Math.Ceiling(r.Bottom)));
} }
return null; return null;

42
src/Avalonia.Base/Rendering/Composition/Server/DirtyRectTracker.cs

@ -10,38 +10,40 @@ namespace Avalonia.Rendering.Composition.Server;
internal interface IDirtyRectTracker internal interface IDirtyRectTracker
{ {
void AddRect(PixelRect rect); void AddRect(LtrbPixelRect rect);
IDisposable BeginDraw(IDrawingContextImpl ctx); IDisposable BeginDraw(IDrawingContextImpl ctx);
bool IsEmpty { get; } bool IsEmpty { get; }
bool Intersects(Rect rect); bool Intersects(LtrbRect rect);
bool Contains(Point pt); bool Contains(Point pt);
void Reset(); void Reset();
void Visualize(IDrawingContextImpl context); void Visualize(IDrawingContextImpl context);
PixelRect CombinedRect { get; } LtrbPixelRect CombinedRect { get; }
IList<PixelRect> Rects { get; } IList<LtrbPixelRect> Rects { get; }
} }
internal class DirtyRectTracker : IDirtyRectTracker internal class DirtyRectTracker : IDirtyRectTracker
{ {
private PixelRect _rect; private LtrbPixelRect _rect;
private Rect _doubleRect; private Rect _doubleRect;
private PixelRect[] _rectsForApi = new PixelRect[1]; private LtrbRect _normalRect;
private LtrbPixelRect[] _rectsForApi = new LtrbPixelRect[1];
private Random _random = new(); private Random _random = new();
public void AddRect(PixelRect rect) public void AddRect(LtrbPixelRect rect)
{ {
_rect = _rect.Union(rect); _rect = _rect.Union(rect);
} }
public IDisposable BeginDraw(IDrawingContextImpl ctx) public IDisposable BeginDraw(IDrawingContextImpl ctx)
{ {
ctx.PushClip(_rect.ToRect(1)); ctx.PushClip(_rect.ToRectWithNoScaling());
_doubleRect = _rect.ToRect(1); _doubleRect = _rect.ToRectWithNoScaling();
_normalRect = new(_doubleRect);
return Disposable.Create(ctx.PopClip); return Disposable.Create(ctx.PopClip);
} }
public bool IsEmpty => _rect.Width == 0 | _rect.Height == 0; public bool IsEmpty => _rect.IsEmpty;
public bool Intersects(Rect rect) => _doubleRect.Intersects(rect); public bool Intersects(LtrbRect rect) => _normalRect.Intersects(rect);
public bool Contains(Point pt) => _rect.Contains(PixelPoint.FromPoint(pt, 1)); public bool Contains(Point pt) => _rect.Contains((int)pt.X, (int)pt.Y);
public void Reset() => _rect = default; public void Reset() => _rect = default;
public void Visualize(IDrawingContextImpl context) public void Visualize(IDrawingContextImpl context)
@ -52,14 +54,14 @@ internal class DirtyRectTracker : IDirtyRectTracker
null, _doubleRect); null, _doubleRect);
} }
public PixelRect CombinedRect => _rect; public LtrbPixelRect CombinedRect => _rect;
public IList<PixelRect> Rects public IList<LtrbPixelRect> Rects
{ {
get get
{ {
if (_rect.Width == 0 || _rect.Height == 0) if (_rect.IsEmpty)
return Array.Empty<PixelRect>(); return Array.Empty<LtrbPixelRect>();
_rectsForApi[0] = _rect; _rectsForApi[0] = _rect;
return _rectsForApi; return _rectsForApi;
} }
@ -76,7 +78,7 @@ internal class RegionDirtyRectTracker : IDirtyRectTracker
_region = platformRender.CreateRegion(); _region = platformRender.CreateRegion();
} }
public void AddRect(PixelRect rect) => _region.AddRect(rect); public void AddRect(LtrbPixelRect rect) => _region.AddRect(rect);
public IDisposable BeginDraw(IDrawingContextImpl ctx) public IDisposable BeginDraw(IDrawingContextImpl ctx)
{ {
@ -85,7 +87,7 @@ internal class RegionDirtyRectTracker : IDirtyRectTracker
} }
public bool IsEmpty => _region.IsEmpty; public bool IsEmpty => _region.IsEmpty;
public bool Intersects(Rect rect) => _region.Intersects(rect); public bool Intersects(LtrbRect rect) => _region.Intersects(rect);
public bool Contains(Point pt) => _region.Contains(pt); public bool Contains(Point pt) => _region.Contains(pt);
public void Reset() => _region.Reset(); public void Reset() => _region.Reset();
@ -98,6 +100,6 @@ internal class RegionDirtyRectTracker : IDirtyRectTracker
null, _region); null, _region);
} }
public PixelRect CombinedRect => _region.Bounds; public LtrbPixelRect CombinedRect => _region.Bounds;
public IList<PixelRect> Rects => _region.Rects; public IList<LtrbPixelRect> Rects => _region.Rects;
} }

8
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs

@ -13,10 +13,10 @@ namespace Avalonia.Rendering.Composition.Server
internal partial class ServerCompositionContainerVisual : ServerCompositionVisual internal partial class ServerCompositionContainerVisual : ServerCompositionVisual
{ {
public ServerCompositionVisualCollection Children { get; private set; } = null!; public ServerCompositionVisualCollection Children { get; private set; } = null!;
private Rect? _transformedContentBounds; private LtrbRect? _transformedContentBounds;
private IImmutableEffect? _oldEffect; private IImmutableEffect? _oldEffect;
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects) IDirtyRectTracker dirtyRects)
{ {
base.RenderCore(canvas, currentTransformedClip, dirtyRects); base.RenderCore(canvas, currentTransformedClip, dirtyRects);
@ -39,7 +39,7 @@ namespace Avalonia.Rendering.Composition.Server
var res = child.Update(root, GlobalTransformMatrix); var res = child.Update(root, GlobalTransformMatrix);
oldInvalidated |= res.InvalidatedOld; oldInvalidated |= res.InvalidatedOld;
newInvalidated |= res.InvalidatedNew; newInvalidated |= res.InvalidatedNew;
combinedBounds = Rect.Union(combinedBounds, res.Bounds); combinedBounds = LtrbRect.FullUnion(combinedBounds, res.Bounds);
} }
} }
@ -63,7 +63,7 @@ namespace Avalonia.Rendering.Composition.Server
return new(_transformedContentBounds, oldInvalidated, newInvalidated); return new(_transformedContentBounds, oldInvalidated, newInvalidated);
} }
void AddEffectPaddedDirtyRect(IImmutableEffect effect, Rect transformedBounds) void AddEffectPaddedDirtyRect(IImmutableEffect effect, LtrbRect transformedBounds)
{ {
var padding = effect.GetEffectOutputPadding(); var padding = effect.GetEffectOutputPadding();
if (padding == default) if (padding == default)

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

@ -27,7 +27,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
#endif #endif
} }
public override Rect OwnContentBounds => _renderCommands?.Bounds ?? default; public override LtrbRect OwnContentBounds => _renderCommands?.Bounds ?? default;
protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
{ {
@ -40,7 +40,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
base.DeserializeChangesCore(reader, committedAt); base.DeserializeChangesCore(reader, committedAt);
} }
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects) IDirtyRectTracker dirtyRects)
{ {
if (_renderCommands != null) if (_renderCommands != null)

4
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionExperimentalAcrylicVisual.cs

@ -5,7 +5,7 @@ namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionExperimentalAcrylicVisual internal partial class ServerCompositionExperimentalAcrylicVisual
{ {
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects) IDirtyRectTracker dirtyRects)
{ {
var cornerRadius = CornerRadius; var cornerRadius = CornerRadius;
@ -19,7 +19,7 @@ internal partial class ServerCompositionExperimentalAcrylicVisual
base.RenderCore(canvas, currentTransformedClip, dirtyRects); base.RenderCore(canvas, currentTransformedClip, dirtyRects);
} }
public override Rect OwnContentBounds => new(0, 0, Size.X, Size.Y); public override LtrbRect OwnContentBounds => new(0, 0, Size.X, Size.Y);
public ServerCompositionExperimentalAcrylicVisual(ServerCompositor compositor, Visual v) : base(compositor, v) public ServerCompositionExperimentalAcrylicVisual(ServerCompositor compositor, Visual v) : base(compositor, v)
{ {

3
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs

@ -1,10 +1,11 @@
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionSolidColorVisual internal partial class ServerCompositionSolidColorVisual
{ {
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects) IDirtyRectTracker dirtyRects)
{ {
canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y)); canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y));

3
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs

@ -1,10 +1,11 @@
using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionSurfaceVisual internal partial class ServerCompositionSurfaceVisual
{ {
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects) IDirtyRectTracker dirtyRects)
{ {
if (Surface == null) if (Surface == null)

28
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.DirtyRects.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Collections.Pooled; using Avalonia.Collections.Pooled;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Server;
@ -8,27 +9,26 @@ internal partial class ServerCompositionTarget
{ {
public readonly IDirtyRectTracker DirtyRects; public readonly IDirtyRectTracker DirtyRects;
public void AddDirtyRect(Rect rect) public void AddDirtyRect(LtrbRect rect)
{ {
if (rect.Width == 0 && rect.Height == 0) if (rect.IsZeroSize)
return; return;
var snapped = PixelRect.FromRect(SnapToDevicePixels(rect, Scaling), 1); var snapped = LtrbPixelRect.FromRectWithNoScaling(SnapToDevicePixels(rect, Scaling));
DebugEvents?.RectInvalidated(rect); DebugEvents?.RectInvalidated(rect.ToRect());
DirtyRects.AddRect(snapped); DirtyRects.AddRect(snapped);
_redrawRequested = true; _redrawRequested = true;
} }
public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(rect, Scaling); public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(new(rect), Scaling).ToRect();
public LtrbRect SnapToDevicePixels(LtrbRect rect) => SnapToDevicePixels(rect, Scaling);
private static Rect SnapToDevicePixels(Rect rect, double scale) private static LtrbRect SnapToDevicePixels(LtrbRect rect, double scale)
{ {
return new Rect( return new LtrbRect(
new Point( Math.Floor(rect.Left * scale) / scale,
Math.Floor(rect.X * scale) / scale, Math.Floor(rect.Top * scale) / scale,
Math.Floor(rect.Y * scale) / scale), Math.Ceiling(rect.Right * scale) / scale,
new Point( Math.Ceiling(rect.Bottom * scale) / scale);
Math.Ceiling(rect.Right * scale) / scale,
Math.Ceiling(rect.Bottom * scale) / scale));
} }

6
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -158,7 +158,7 @@ namespace Avalonia.Rendering.Composition.Server
_layer = null; _layer = null;
_layer = renderTargetContext.CreateLayer(PixelSize); _layer = renderTargetContext.CreateLayer(PixelSize);
_layerSize = PixelSize; _layerSize = PixelSize;
DirtyRects.AddRect(new PixelRect(_layerSize)); DirtyRects.AddRect(new LtrbPixelRect(_layerSize));
} }
else if (!needLayer) else if (!needLayer)
{ {
@ -168,7 +168,7 @@ namespace Avalonia.Rendering.Composition.Server
if (_fullRedrawRequested || (!needLayer && !properties.PreviousFrameIsRetained)) if (_fullRedrawRequested || (!needLayer && !properties.PreviousFrameIsRetained))
{ {
DirtyRects.AddRect(new PixelRect(_layerSize)); DirtyRects.AddRect(new LtrbPixelRect(_layerSize));
_fullRedrawRequested = false; _fullRedrawRequested = false;
} }
@ -212,7 +212,7 @@ namespace Avalonia.Rendering.Composition.Server
{ {
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
if (useLayerClip) if (useLayerClip)
context.PushLayer(DirtyRects.CombinedRect.ToRect(1)); context.PushLayer(DirtyRects.CombinedRect.ToRectUnscaled());
root.Render(new CompositorDrawingContextProxy(context), null, DirtyRects); root.Render(new CompositorDrawingContextProxy(context), null, DirtyRects);

61
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -17,17 +17,17 @@ namespace Avalonia.Rendering.Composition.Server
partial class ServerCompositionVisual : ServerObject partial class ServerCompositionVisual : ServerObject
{ {
private bool _isDirtyForUpdate; private bool _isDirtyForUpdate;
private Rect _oldOwnContentBounds; private LtrbRect _oldOwnContentBounds;
private bool _isBackface; private bool _isBackface;
private Rect? _transformedClipBounds; private LtrbRect? _transformedClipBounds;
private Rect _combinedTransformedClipBounds; private LtrbRect _combinedTransformedClipBounds;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, protected virtual void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects) IDirtyRectTracker dirtyRects)
{ {
} }
public void Render(CompositorDrawingContextProxy canvas, Rect? parentTransformedClip, IDirtyRectTracker dirtyRects) public void Render(CompositorDrawingContextProxy canvas, LtrbRect? parentTransformedClip, IDirtyRectTracker dirtyRects)
{ {
if (Visible == false || IsVisibleInFrame == false) if (Visible == false || IsVisibleInFrame == false)
return; return;
@ -37,7 +37,7 @@ namespace Avalonia.Rendering.Composition.Server
var currentTransformedClip = parentTransformedClip.HasValue var currentTransformedClip = parentTransformedClip.HasValue
? parentTransformedClip.Value.Intersect(_combinedTransformedClipBounds) ? parentTransformedClip.Value.Intersect(_combinedTransformedClipBounds)
: _combinedTransformedClipBounds; : _combinedTransformedClipBounds;
if (currentTransformedClip.Width == 0 && currentTransformedClip.Height == 0) if (currentTransformedClip.IsZeroSize)
return; return;
if(!dirtyRects.Intersects(currentTransformedClip)) if(!dirtyRects.Intersects(currentTransformedClip))
return; return;
@ -52,7 +52,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PostTransform = Matrix.Identity; canvas.PostTransform = Matrix.Identity;
canvas.Transform = Matrix.Identity; canvas.Transform = Matrix.Identity;
if (AdornerIsClipped) if (AdornerIsClipped)
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds); canvas.PushClip(AdornedVisual._combinedTransformedClipBounds.ToRect());
} }
var transform = GlobalTransformMatrix; var transform = GlobalTransformMatrix;
canvas.PostTransform = transform; canvas.PostTransform = transform;
@ -68,7 +68,7 @@ namespace Avalonia.Rendering.Composition.Server
if (Opacity != 1) if (Opacity != 1)
canvas.PushOpacity(Opacity, ClipToBounds ? boundsRect : null); canvas.PushOpacity(Opacity, ClipToBounds ? boundsRect : null);
if (ClipToBounds && !HandlesClipToBounds) if (ClipToBounds && !HandlesClipToBounds)
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); canvas.PushClip(boundsRect);
if (Clip != null) if (Clip != null)
canvas.PushGeometryClip(Clip); canvas.PushGeometryClip(Clip);
if (OpacityMaskBrush != null) if (OpacityMaskBrush != null)
@ -117,7 +117,7 @@ namespace Avalonia.Rendering.Composition.Server
public Matrix CombinedTransformMatrix { get; private set; } = Matrix.Identity; public Matrix CombinedTransformMatrix { get; private set; } = Matrix.Identity;
public Matrix GlobalTransformMatrix { get; private set; } public Matrix GlobalTransformMatrix { get; private set; }
public record struct UpdateResult(Rect? Bounds, bool InvalidatedOld, bool InvalidatedNew) public record struct UpdateResult(LtrbRect? Bounds, bool InvalidatedOld, bool InvalidatedNew)
{ {
public UpdateResult() : this(null, false, false) public UpdateResult() : this(null, false, false)
{ {
@ -178,7 +178,7 @@ namespace Avalonia.Rendering.Composition.Server
if (ownBounds != _oldOwnContentBounds || positionChanged) if (ownBounds != _oldOwnContentBounds || positionChanged)
{ {
_oldOwnContentBounds = ownBounds; _oldOwnContentBounds = ownBounds;
if (ownBounds.Width == 0 && ownBounds.Height == 0) if (ownBounds.IsZeroSize)
TransformedOwnContentBounds = default; TransformedOwnContentBounds = default;
else else
TransformedOwnContentBounds = TransformedOwnContentBounds =
@ -187,22 +187,23 @@ namespace Avalonia.Rendering.Composition.Server
if (_clipSizeDirty || positionChanged) if (_clipSizeDirty || positionChanged)
{ {
Rect? transformedVisualBounds = null; LtrbRect? transformedVisualBounds = null;
Rect? transformedClipBounds = null; LtrbRect? transformedClipBounds = null;
if (ClipToBounds) if (ClipToBounds)
transformedVisualBounds = new Rect(new Size(Size.X, Size.Y)).TransformToAABB(GlobalTransformMatrix); transformedVisualBounds =
new LtrbRect(0, 0, Size.X, Size.Y).TransformToAABB(GlobalTransformMatrix);
if (Clip != null)
transformedClipBounds = Clip.Bounds.TransformToAABB(GlobalTransformMatrix); if (Clip != null)
transformedClipBounds = new LtrbRect(Clip.Bounds).TransformToAABB(GlobalTransformMatrix);
if (transformedVisualBounds != null && transformedClipBounds != null)
_transformedClipBounds = transformedVisualBounds.Value.Intersect(transformedClipBounds.Value); if (transformedVisualBounds != null && transformedClipBounds != null)
else if (transformedVisualBounds != null) _transformedClipBounds = transformedVisualBounds.Value.Intersect(transformedClipBounds.Value);
_transformedClipBounds = transformedVisualBounds; else if (transformedVisualBounds != null)
else if (transformedClipBounds != null) _transformedClipBounds = transformedVisualBounds;
_transformedClipBounds = transformedClipBounds; else if (transformedClipBounds != null)
else _transformedClipBounds = transformedClipBounds;
else
_transformedClipBounds = null; _transformedClipBounds = null;
_clipSizeDirty = false; _clipSizeDirty = false;
@ -211,7 +212,7 @@ namespace Avalonia.Rendering.Composition.Server
_combinedTransformedClipBounds = _combinedTransformedClipBounds =
(AdornerIsClipped ? AdornedVisual?._combinedTransformedClipBounds : null) (AdornerIsClipped ? AdornedVisual?._combinedTransformedClipBounds : null)
?? (Parent?.Effect == null ? Parent?._combinedTransformedClipBounds : null) ?? (Parent?.Effect == null ? Parent?._combinedTransformedClipBounds : null)
?? new Rect(Root!.PixelSize.ToSize(1)); ?? new LtrbRect(0, 0, Root!.PixelSize.Width, Root!.PixelSize.Height);
if (_transformedClipBounds != null) if (_transformedClipBounds != null)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value); _combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
@ -221,7 +222,7 @@ namespace Avalonia.Rendering.Composition.Server
IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false
&& Visible && Visible
&& !_isBackface && !_isBackface
&& (_combinedTransformedClipBounds.Width != 0 || _combinedTransformedClipBounds.Height != 0); && !(_combinedTransformedClipBounds.IsZeroSize);
IsVisibleInFrame = IsHitTestVisibleInFrame IsVisibleInFrame = IsHitTestVisibleInFrame
&& _parent?.IsVisibleInFrame != false && _parent?.IsVisibleInFrame != false
@ -253,7 +254,7 @@ namespace Avalonia.Rendering.Composition.Server
return new(TransformedOwnContentBounds, invalidateNewBounds, invalidateOldBounds); return new(TransformedOwnContentBounds, invalidateNewBounds, invalidateOldBounds);
} }
protected void AddDirtyRect(Rect rc) protected void AddDirtyRect(LtrbRect rc)
{ {
if (rc == default) if (rc == default)
return; return;
@ -311,7 +312,7 @@ namespace Avalonia.Rendering.Composition.Server
public bool IsVisibleInFrame { get; set; } public bool IsVisibleInFrame { get; set; }
public bool IsHitTestVisibleInFrame { get; set; } public bool IsHitTestVisibleInFrame { get; set; }
public double EffectiveOpacity { get; set; } public double EffectiveOpacity { get; set; }
public Rect TransformedOwnContentBounds { get; set; } public LtrbRect TransformedOwnContentBounds { get; set; }
public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y); public virtual LtrbRect OwnContentBounds => new (0, 0, Size.X, Size.Y);
} }
} }

9
src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Server;
@ -41,7 +42,7 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
Compositor.Animations.RemoveFromClock(this); Compositor.Animations.RemoveFromClock(this);
} }
public override Rect OwnContentBounds => _handler.GetRenderBounds(); public override LtrbRect OwnContentBounds => new(_handler.GetRenderBounds());
protected override void OnAttachedToRoot(ServerCompositionTarget target) protected override void OnAttachedToRoot(ServerCompositionTarget target)
{ {
@ -60,7 +61,7 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
internal void HandlerInvalidate(Rect rc) internal void HandlerInvalidate(Rect rc)
{ {
Root?.AddDirtyRect(rc.TransformToAABB(GlobalTransformMatrix)); Root?.AddDirtyRect(new LtrbRect(rc).TransformToAABB(GlobalTransformMatrix));
} }
internal void HandlerRegisterForNextAnimationFrameUpdate() internal void HandlerRegisterForNextAnimationFrameUpdate()
@ -70,13 +71,13 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
Compositor.Animations.AddToClock(this); Compositor.Animations.AddToClock(this);
} }
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects) IDirtyRectTracker dirtyRects)
{ {
using var context = new ImmediateDrawingContext(canvas, false); using var context = new ImmediateDrawingContext(canvas, false);
try try
{ {
_handler.Render(context, currentTransformedClip); _handler.Render(context, currentTransformedClip.ToRect());
} }
catch (Exception e) catch (Exception e)
{ {

3
src/Avalonia.Controls/BorderVisual.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Platform;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport; using Avalonia.Rendering.Composition.Transport;
@ -45,7 +46,7 @@ class CompositionBorderVisual : CompositionDrawListVisual
{ {
} }
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip, protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects) IDirtyRectTracker dirtyRects)
{ {
if (ClipToBounds) if (ClipToBounds)

4
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -545,14 +545,14 @@ namespace Avalonia.Skia
if (brush != null) if (brush != null)
{ {
using (var fill = CreatePaint(_fillPaint, brush, r.Bounds.ToRect(1))) using (var fill = CreatePaint(_fillPaint, brush, r.Bounds.ToRectUnscaled()))
{ {
Canvas.DrawRegion(r.Region, fill.Paint); Canvas.DrawRegion(r.Region, fill.Paint);
} }
} }
if (pen is not null if (pen is not null
&& TryCreatePaint(_strokePaint, pen, r.Bounds.ToRect(1).Inflate(new Thickness(pen.Thickness / 2))) is { } stroke) && TryCreatePaint(_strokePaint, pen, r.Bounds.ToRectUnscaled().Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
{ {
using (stroke) using (stroke)
{ {

17
src/Skia/Avalonia.Skia/SkiaRegionImpl.cs

@ -10,17 +10,17 @@ internal class SkiaRegionImpl : IPlatformRenderInterfaceRegion
private SKRegion? _region = new(); private SKRegion? _region = new();
public SKRegion Region => _region ?? throw new ObjectDisposedException(nameof(SkiaRegionImpl)); public SKRegion Region => _region ?? throw new ObjectDisposedException(nameof(SkiaRegionImpl));
private bool _rectsValid; private bool _rectsValid;
private List<PixelRect>? _rects; private List<LtrbPixelRect>? _rects;
public void Dispose() public void Dispose()
{ {
_region?.Dispose(); _region?.Dispose();
_region = null; _region = null;
} }
public void AddRect(PixelRect rect) public void AddRect(LtrbPixelRect rect)
{ {
_rectsValid = false; _rectsValid = false;
Region.Op(rect.X, rect.Y, rect.Right, rect.Bottom, SKRegionOperation.Union); Region.Op(rect.Left, rect.Top, rect.Right, rect.Bottom, SKRegionOperation.Union);
} }
public void Reset() public void Reset()
@ -30,9 +30,9 @@ internal class SkiaRegionImpl : IPlatformRenderInterfaceRegion
} }
public bool IsEmpty => Region.IsEmpty; public bool IsEmpty => Region.IsEmpty;
public PixelRect Bounds => Region.Bounds.ToAvaloniaPixelRect(); public LtrbPixelRect Bounds => Region.Bounds.ToAvaloniaLtrbPixelRect();
public IList<PixelRect> Rects public IList<LtrbPixelRect> Rects
{ {
get get
{ {
@ -42,12 +42,15 @@ internal class SkiaRegionImpl : IPlatformRenderInterfaceRegion
_rects.Clear(); _rects.Clear();
using var iter = Region.CreateRectIterator(); using var iter = Region.CreateRectIterator();
while (iter.Next(out var rc)) while (iter.Next(out var rc))
_rects.Add(rc.ToAvaloniaPixelRect()); _rects.Add(rc.ToAvaloniaLtrbPixelRect());
} }
return _rects; return _rects;
} }
} }
public bool Intersects(Rect rect) => Region.Intersects(PixelRect.FromRect(rect, 1).ToSKRectI()); public bool Intersects(LtrbRect rect) => Region.Intersects(
new SKRectI((int)rect.Left, (int)rect.Top,
(int)Math.Ceiling(rect.Right), (int)Math.Ceiling(rect.Bottom)));
public bool Contains(Point pt) => Region.Contains((int)pt.X, (int)pt.Y); public bool Contains(Point pt) => Region.Contains((int)pt.X, (int)pt.Y);
} }

20
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@ -76,10 +76,20 @@ namespace Avalonia.Skia
return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom); return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom);
} }
internal static SKRect ToSKRect(this LtrbRect r)
{
return new SKRect((float)r.Left, (float)r.Right, (float)r.Right, (float)r.Bottom);
}
public static SKRectI ToSKRectI(this PixelRect r) public static SKRectI ToSKRectI(this PixelRect r)
{ {
return new SKRectI(r.X, r.Y, r.Right, r.Bottom); return new SKRectI(r.X, r.Y, r.Right, r.Bottom);
} }
internal static SKRectI ToSKRectI(this LtrbPixelRect r)
{
return new SKRectI(r.Left, r.Top, r.Right, r.Bottom);
}
public static SKRoundRect ToSKRoundRect(this RoundedRect r) public static SKRoundRect ToSKRoundRect(this RoundedRect r)
{ {
@ -101,10 +111,20 @@ namespace Avalonia.Skia
return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top); return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
} }
internal static LtrbRect ToAvaloniaLtrbRect(this SKRect r)
{
return new LtrbRect(r.Left, r.Top, r.Right, r.Bottom);
}
public static PixelRect ToAvaloniaPixelRect(this SKRectI r) public static PixelRect ToAvaloniaPixelRect(this SKRectI r)
{ {
return new PixelRect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top); return new PixelRect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
} }
internal static LtrbPixelRect ToAvaloniaLtrbPixelRect(this SKRectI r)
{
return new LtrbPixelRect(r.Left, r.Top, r.Right, r.Bottom);
}
public static SKMatrix ToSKMatrix(this Matrix m) public static SKMatrix ToSKMatrix(this Matrix m)
{ {

2
tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

@ -40,7 +40,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
if (renderData == null) if (renderData == null)
return null; return null;
ForceRender(); ForceRender();
return renderData.Server.Bounds; return renderData.Server.Bounds?.ToRect();
} }
} }

Loading…
Cancel
Save