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]
public interface IPlatformRenderInterfaceRegion : IDisposable
{
void AddRect(PixelRect rect);
void AddRect(LtrbPixelRect rect);
void Reset();
bool IsEmpty { get; }
PixelRect Bounds { get; }
IList<PixelRect> Rects { get; }
bool Intersects(Rect rect);
LtrbPixelRect Bounds { get; }
IList<LtrbPixelRect> Rects { get; }
bool Intersects(LtrbRect rect);
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();
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 Rect Rect => _rect ?? (RenderData.Server?.Bounds ?? default);
public Rect Rect => _rect ?? (RenderData.Server?.Bounds?.ToRect() ?? default);
public double Opacity => Brush.Opacity;
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<IServerRenderResource> _referencedResources;
private Rect? _bounds;
private LtrbRect? _bounds;
private bool _boundsValid;
private static readonly ThreadSafeObjectPool<Collector> s_resourceHashSetPool = new();
@ -67,7 +67,7 @@ class ServerCompositionRenderData : SimpleServerRenderResource
}
}
public Rect? Bounds
public LtrbRect? Bounds
{
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)
totalBounds = Rect.Union(totalBounds, item.Bounds);
totalBounds = LtrbRect.FullUnion(totalBounds, item.Bounds);
return ApplyRenderBoundsRounding(totalBounds);
}
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)
{
var r = rect.Value;
// 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
return new Rect(
new Point(Math.Floor(r.X), Math.Floor(r.Y)),
new Point(Math.Ceiling(r.Right), Math.Ceiling(r.Bottom)));
return new LtrbRect(Math.Floor(r.Left), Math.Floor(r.Top),
Math.Ceiling(r.Right), Math.Ceiling(r.Bottom));
}
return null;

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

@ -10,38 +10,40 @@ namespace Avalonia.Rendering.Composition.Server;
internal interface IDirtyRectTracker
{
void AddRect(PixelRect rect);
void AddRect(LtrbPixelRect rect);
IDisposable BeginDraw(IDrawingContextImpl ctx);
bool IsEmpty { get; }
bool Intersects(Rect rect);
bool Intersects(LtrbRect rect);
bool Contains(Point pt);
void Reset();
void Visualize(IDrawingContextImpl context);
PixelRect CombinedRect { get; }
IList<PixelRect> Rects { get; }
LtrbPixelRect CombinedRect { get; }
IList<LtrbPixelRect> Rects { get; }
}
internal class DirtyRectTracker : IDirtyRectTracker
{
private PixelRect _rect;
private LtrbPixelRect _rect;
private Rect _doubleRect;
private PixelRect[] _rectsForApi = new PixelRect[1];
private LtrbRect _normalRect;
private LtrbPixelRect[] _rectsForApi = new LtrbPixelRect[1];
private Random _random = new();
public void AddRect(PixelRect rect)
public void AddRect(LtrbPixelRect rect)
{
_rect = _rect.Union(rect);
}
public IDisposable BeginDraw(IDrawingContextImpl ctx)
{
ctx.PushClip(_rect.ToRect(1));
_doubleRect = _rect.ToRect(1);
ctx.PushClip(_rect.ToRectWithNoScaling());
_doubleRect = _rect.ToRectWithNoScaling();
_normalRect = new(_doubleRect);
return Disposable.Create(ctx.PopClip);
}
public bool IsEmpty => _rect.Width == 0 | _rect.Height == 0;
public bool Intersects(Rect rect) => _doubleRect.Intersects(rect);
public bool Contains(Point pt) => _rect.Contains(PixelPoint.FromPoint(pt, 1));
public bool IsEmpty => _rect.IsEmpty;
public bool Intersects(LtrbRect rect) => _normalRect.Intersects(rect);
public bool Contains(Point pt) => _rect.Contains((int)pt.X, (int)pt.Y);
public void Reset() => _rect = default;
public void Visualize(IDrawingContextImpl context)
@ -52,14 +54,14 @@ internal class DirtyRectTracker : IDirtyRectTracker
null, _doubleRect);
}
public PixelRect CombinedRect => _rect;
public LtrbPixelRect CombinedRect => _rect;
public IList<PixelRect> Rects
public IList<LtrbPixelRect> Rects
{
get
{
if (_rect.Width == 0 || _rect.Height == 0)
return Array.Empty<PixelRect>();
if (_rect.IsEmpty)
return Array.Empty<LtrbPixelRect>();
_rectsForApi[0] = _rect;
return _rectsForApi;
}
@ -76,7 +78,7 @@ internal class RegionDirtyRectTracker : IDirtyRectTracker
_region = platformRender.CreateRegion();
}
public void AddRect(PixelRect rect) => _region.AddRect(rect);
public void AddRect(LtrbPixelRect rect) => _region.AddRect(rect);
public IDisposable BeginDraw(IDrawingContextImpl ctx)
{
@ -85,7 +87,7 @@ internal class RegionDirtyRectTracker : IDirtyRectTracker
}
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 void Reset() => _region.Reset();
@ -98,6 +100,6 @@ internal class RegionDirtyRectTracker : IDirtyRectTracker
null, _region);
}
public PixelRect CombinedRect => _region.Bounds;
public IList<PixelRect> Rects => _region.Rects;
public LtrbPixelRect CombinedRect => _region.Bounds;
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
{
public ServerCompositionVisualCollection Children { get; private set; } = null!;
private Rect? _transformedContentBounds;
private LtrbRect? _transformedContentBounds;
private IImmutableEffect? _oldEffect;
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
base.RenderCore(canvas, currentTransformedClip, dirtyRects);
@ -39,7 +39,7 @@ namespace Avalonia.Rendering.Composition.Server
var res = child.Update(root, GlobalTransformMatrix);
oldInvalidated |= res.InvalidatedOld;
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);
}
void AddEffectPaddedDirtyRect(IImmutableEffect effect, Rect transformedBounds)
void AddEffectPaddedDirtyRect(IImmutableEffect effect, LtrbRect transformedBounds)
{
var padding = effect.GetEffectOutputPadding();
if (padding == default)

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

@ -27,7 +27,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
#endif
}
public override Rect OwnContentBounds => _renderCommands?.Bounds ?? default;
public override LtrbRect OwnContentBounds => _renderCommands?.Bounds ?? default;
protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt)
{
@ -40,7 +40,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
base.DeserializeChangesCore(reader, committedAt);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
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
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
var cornerRadius = CornerRadius;
@ -19,7 +19,7 @@ internal partial class ServerCompositionExperimentalAcrylicVisual
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)
{

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

@ -1,10 +1,11 @@
using Avalonia.Media.Immutable;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionSolidColorVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
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;
namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionSurfaceVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
if (Surface == null)

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

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections.Pooled;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server;
@ -8,27 +9,26 @@ internal partial class ServerCompositionTarget
{
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;
var snapped = PixelRect.FromRect(SnapToDevicePixels(rect, Scaling), 1);
DebugEvents?.RectInvalidated(rect);
var snapped = LtrbPixelRect.FromRectWithNoScaling(SnapToDevicePixels(rect, Scaling));
DebugEvents?.RectInvalidated(rect.ToRect());
DirtyRects.AddRect(snapped);
_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(
new Point(
Math.Floor(rect.X * scale) / scale,
Math.Floor(rect.Y * scale) / scale),
new Point(
Math.Ceiling(rect.Right * scale) / scale,
Math.Ceiling(rect.Bottom * scale) / scale));
return new LtrbRect(
Math.Floor(rect.Left * scale) / scale,
Math.Floor(rect.Top * 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 = renderTargetContext.CreateLayer(PixelSize);
_layerSize = PixelSize;
DirtyRects.AddRect(new PixelRect(_layerSize));
DirtyRects.AddRect(new LtrbPixelRect(_layerSize));
}
else if (!needLayer)
{
@ -168,7 +168,7 @@ namespace Avalonia.Rendering.Composition.Server
if (_fullRedrawRequested || (!needLayer && !properties.PreviousFrameIsRetained))
{
DirtyRects.AddRect(new PixelRect(_layerSize));
DirtyRects.AddRect(new LtrbPixelRect(_layerSize));
_fullRedrawRequested = false;
}
@ -212,7 +212,7 @@ namespace Avalonia.Rendering.Composition.Server
{
context.Clear(Colors.Transparent);
if (useLayerClip)
context.PushLayer(DirtyRects.CombinedRect.ToRect(1));
context.PushLayer(DirtyRects.CombinedRect.ToRectUnscaled());
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
{
private bool _isDirtyForUpdate;
private Rect _oldOwnContentBounds;
private LtrbRect _oldOwnContentBounds;
private bool _isBackface;
private Rect? _transformedClipBounds;
private Rect _combinedTransformedClipBounds;
private LtrbRect? _transformedClipBounds;
private LtrbRect _combinedTransformedClipBounds;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
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)
return;
@ -37,7 +37,7 @@ namespace Avalonia.Rendering.Composition.Server
var currentTransformedClip = parentTransformedClip.HasValue
? parentTransformedClip.Value.Intersect(_combinedTransformedClipBounds)
: _combinedTransformedClipBounds;
if (currentTransformedClip.Width == 0 && currentTransformedClip.Height == 0)
if (currentTransformedClip.IsZeroSize)
return;
if(!dirtyRects.Intersects(currentTransformedClip))
return;
@ -52,7 +52,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PostTransform = Matrix.Identity;
canvas.Transform = Matrix.Identity;
if (AdornerIsClipped)
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds.ToRect());
}
var transform = GlobalTransformMatrix;
canvas.PostTransform = transform;
@ -68,7 +68,7 @@ namespace Avalonia.Rendering.Composition.Server
if (Opacity != 1)
canvas.PushOpacity(Opacity, ClipToBounds ? boundsRect : null);
if (ClipToBounds && !HandlesClipToBounds)
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
canvas.PushClip(boundsRect);
if (Clip != null)
canvas.PushGeometryClip(Clip);
if (OpacityMaskBrush != null)
@ -117,7 +117,7 @@ namespace Avalonia.Rendering.Composition.Server
public Matrix CombinedTransformMatrix { get; private set; } = Matrix.Identity;
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)
{
@ -178,7 +178,7 @@ namespace Avalonia.Rendering.Composition.Server
if (ownBounds != _oldOwnContentBounds || positionChanged)
{
_oldOwnContentBounds = ownBounds;
if (ownBounds.Width == 0 && ownBounds.Height == 0)
if (ownBounds.IsZeroSize)
TransformedOwnContentBounds = default;
else
TransformedOwnContentBounds =
@ -187,22 +187,23 @@ namespace Avalonia.Rendering.Composition.Server
if (_clipSizeDirty || positionChanged)
{
Rect? transformedVisualBounds = null;
Rect? transformedClipBounds = null;
LtrbRect? transformedVisualBounds = null;
LtrbRect? 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
transformedVisualBounds =
new LtrbRect(0, 0, Size.X, Size.Y).TransformToAABB(GlobalTransformMatrix);
if (Clip != null)
transformedClipBounds = new LtrbRect(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;
@ -211,7 +212,7 @@ namespace Avalonia.Rendering.Composition.Server
_combinedTransformedClipBounds =
(AdornerIsClipped ? AdornedVisual?._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)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
@ -221,7 +222,7 @@ namespace Avalonia.Rendering.Composition.Server
IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false
&& Visible
&& !_isBackface
&& (_combinedTransformedClipBounds.Width != 0 || _combinedTransformedClipBounds.Height != 0);
&& !(_combinedTransformedClipBounds.IsZeroSize);
IsVisibleInFrame = IsHitTestVisibleInFrame
&& _parent?.IsVisibleInFrame != false
@ -253,7 +254,7 @@ namespace Avalonia.Rendering.Composition.Server
return new(TransformedOwnContentBounds, invalidateNewBounds, invalidateOldBounds);
}
protected void AddDirtyRect(Rect rc)
protected void AddDirtyRect(LtrbRect rc)
{
if (rc == default)
return;
@ -311,7 +312,7 @@ namespace Avalonia.Rendering.Composition.Server
public bool IsVisibleInFrame { get; set; }
public bool IsHitTestVisibleInFrame { get; set; }
public double EffectiveOpacity { get; set; }
public Rect TransformedOwnContentBounds { get; set; }
public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y);
public LtrbRect TransformedOwnContentBounds { get; set; }
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 Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server;
@ -41,7 +42,7 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
Compositor.Animations.RemoveFromClock(this);
}
public override Rect OwnContentBounds => _handler.GetRenderBounds();
public override LtrbRect OwnContentBounds => new(_handler.GetRenderBounds());
protected override void OnAttachedToRoot(ServerCompositionTarget target)
{
@ -60,7 +61,7 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
internal void HandlerInvalidate(Rect rc)
{
Root?.AddDirtyRect(rc.TransformToAABB(GlobalTransformMatrix));
Root?.AddDirtyRect(new LtrbRect(rc).TransformToAABB(GlobalTransformMatrix));
}
internal void HandlerRegisterForNextAnimationFrameUpdate()
@ -70,13 +71,13 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
Compositor.Animations.AddToClock(this);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip,
protected override void RenderCore(CompositorDrawingContextProxy canvas, LtrbRect currentTransformedClip,
IDirtyRectTracker dirtyRects)
{
using var context = new ImmediateDrawingContext(canvas, false);
try
{
_handler.Render(context, currentTransformedClip);
_handler.Render(context, currentTransformedClip.ToRect());
}
catch (Exception e)
{

3
src/Avalonia.Controls/BorderVisual.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Server;
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)
{
if (ClipToBounds)

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

@ -545,14 +545,14 @@ namespace Avalonia.Skia
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);
}
}
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)
{

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

@ -10,17 +10,17 @@ internal class SkiaRegionImpl : IPlatformRenderInterfaceRegion
private SKRegion? _region = new();
public SKRegion Region => _region ?? throw new ObjectDisposedException(nameof(SkiaRegionImpl));
private bool _rectsValid;
private List<PixelRect>? _rects;
private List<LtrbPixelRect>? _rects;
public void Dispose()
{
_region?.Dispose();
_region = null;
}
public void AddRect(PixelRect rect)
public void AddRect(LtrbPixelRect rect)
{
_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()
@ -30,9 +30,9 @@ internal class SkiaRegionImpl : IPlatformRenderInterfaceRegion
}
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
{
@ -42,12 +42,15 @@ internal class SkiaRegionImpl : IPlatformRenderInterfaceRegion
_rects.Clear();
using var iter = Region.CreateRectIterator();
while (iter.Next(out var rc))
_rects.Add(rc.ToAvaloniaPixelRect());
_rects.Add(rc.ToAvaloniaLtrbPixelRect());
}
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);
}

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);
}
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)
{
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)
{
@ -101,10 +111,20 @@ namespace Avalonia.Skia
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)
{
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)
{

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

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

Loading…
Cancel
Save