diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
index 7eb9b91f9a..884740fe65 100644
--- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
+++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
@@ -35,7 +35,7 @@ namespace Avalonia.Media
}
}
- internal ImmediateDrawingContext(IDrawingContextImpl impl, bool ownsImpl) : this(impl, impl.Transform, ownsImpl)
+ internal ImmediateDrawingContext(IDrawingContextImpl impl, bool ownsImpl) : this(impl, impl.Transform.ToMatrix(), ownsImpl)
{
}
diff --git a/src/Avalonia.Base/Media/PlatformDrawingContext.cs b/src/Avalonia.Base/Media/PlatformDrawingContext.cs
index 312cae2c52..ee0e0357d0 100644
--- a/src/Avalonia.Base/Media/PlatformDrawingContext.cs
+++ b/src/Avalonia.Base/Media/PlatformDrawingContext.cs
@@ -80,7 +80,7 @@ internal sealed class PlatformDrawingContext : DrawingContext
{
_transforms ??= TransformStackPool.Get();
var current = _impl.Transform;
- _transforms.Push(current);
+ _transforms.Push(current.ToMatrix());
_impl.Transform = matrix * current;
}
diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
index 1a813f7bbe..c5b57e5601 100644
--- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
@@ -4,6 +4,7 @@ using Avalonia.Utilities;
using Avalonia.Metadata;
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
+using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Platform
{
@@ -16,7 +17,7 @@ namespace Avalonia.Platform
///
/// Gets or sets the current transform of the drawing context.
///
- Matrix Transform { get; set; }
+ CompositionMatrix Transform { get; set; }
///
/// Clears the render target to the specified color.
diff --git a/src/Avalonia.Base/Platform/LtrbRect.cs b/src/Avalonia.Base/Platform/LtrbRect.cs
index 188f75c010..213c3fe58b 100644
--- a/src/Avalonia.Base/Platform/LtrbRect.cs
+++ b/src/Avalonia.Base/Platform/LtrbRect.cs
@@ -2,6 +2,7 @@
using System;
using Avalonia.Metadata;
+using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Platform;
@@ -115,6 +116,32 @@ public struct LtrbRect
return new LtrbRect(left, top, right, bottom);
}
+ internal LtrbRect TransformToAABB(CompositionMatrix matrix)
+ {
+ ReadOnlySpan points = stackalloc Point[4]
+ {
+ matrix.Transform(TopLeft),
+ matrix.Transform(TopRight),
+ matrix.Transform(BottomRight),
+ matrix.Transform(BottomLeft)
+ };
+
+ 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);
+ }
+
///
/// Perform _WPF-like_ union operation
///
@@ -165,6 +192,11 @@ public struct LtrbRect
return hash;
}
}
+
+ public override string ToString()
+ {
+ return $"{Left}:{Top}-{Right}:{Bottom}";
+ }
}
///
@@ -265,4 +297,9 @@ public struct LtrbPixelRect
return new LtrbPixelRect((int)rect.Left, (int)rect.Top, (int)Math.Ceiling(rect.Right),
(int)Math.Ceiling(rect.Bottom));
}
+
+ public override string ToString()
+ {
+ return $"{Left}:{Top}-{Right}:{Bottom}";
+ }
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
index 8cf4cca0a5..51e9274228 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs
@@ -97,14 +97,14 @@ public abstract class CompositionCustomVisualHandler
protected bool RenderClipContains(Point pt)
{
VerifyInRender();
- pt *= _host!.GlobalTransformMatrix;
+ pt = _host!.GlobalTransformMatrix.Transform(pt);
return _currentTransformedClip.Contains(pt) && _host.Root!.DirtyRects.Contains(pt);
}
protected bool RenderClipIntersectes(Rect rc)
{
VerifyInRender();
- rc = rc.TransformToAABB(_host!.GlobalTransformMatrix);
+ rc = rc.TransformToAABB(_host!.GlobalTransformMatrix.ToMatrix());
return _currentTransformedClip.Intersects(rc) && _host.Root!.DirtyRects.Intersects(new (rc));
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTransform.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTransform.cs
index bd7165398a..bb1c2eb2b1 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionTransform.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionTransform.cs
@@ -5,5 +5,5 @@ namespace Avalonia.Rendering.Composition.Server;
internal partial class ServerCompositionSimpleTransform : ITransform
{
-
+ Matrix ITransform.Value => this.Value.ToMatrix();
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs
index ac0a5c4cb7..f40b128c28 100644
--- a/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Drawing/Nodes/RenderDataNodes.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
+using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Utilities;
@@ -29,8 +30,8 @@ interface IRenderDataItemWithServerResources : IRenderDataItem
struct RenderDataNodeRenderContext : IDisposable
{
- private Stack? _stack;
- private static readonly ThreadSafeObjectPool> s_matrixStackPool = new();
+ private Stack? _stack;
+ private static readonly ThreadSafeObjectPool> s_matrixStackPool = new();
public RenderDataNodeRenderContext(IDrawingContextImpl context)
{
@@ -38,7 +39,7 @@ struct RenderDataNodeRenderContext : IDisposable
}
public IDrawingContextImpl Context { get; }
- public Stack MatrixStack
+ public Stack MatrixStack
{
get => _stack ??= s_matrixStackPool.Get();
}
diff --git a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs
index 4ec1326103..6faf6d1e36 100644
--- a/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs
+++ b/src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs
@@ -1,25 +1,26 @@
using System.Numerics;
+using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition
{
static class MatrixUtils
{
- public static Matrix ComputeTransform(Vector size, Vector anchorPoint, Vector3D centerPoint,
- Matrix transformMatrix, Vector3D scale, float rotationAngle, Quaternion orientation, Vector3D offset)
+ public static CompositionMatrix ComputeTransform(Vector size, Vector anchorPoint, Vector3D centerPoint,
+ CompositionMatrix transformMatrix, Vector3D scale, float rotationAngle, Quaternion orientation, Vector3D offset)
{
// The math here follows the *observed* UWP behavior since there are no docs on how it's supposed to work
var anchor = Vector.Multiply(size, anchorPoint);
- var mat = Matrix.CreateTranslation(-anchor.X, -anchor.Y);
+ var mat = CompositionMatrix.CreateTranslation(-anchor.X, -anchor.Y);
var center = new Vector3D(centerPoint.X, centerPoint.Y, centerPoint.Z);
- if (!transformMatrix.IsIdentity)
+ if (!transformMatrix.GuaranteedIdentity)
mat = transformMatrix * mat;
if (scale != new Vector3D(1, 1, 1))
- mat *= ToMatrix(Matrix4x4.CreateScale(scale.ToVector3(), center.ToVector3()));
+ mat = CompositionMatrix.CreateScale(scale, center);
//TODO: RotationAxis support
if (rotationAngle != 0)
@@ -39,10 +40,7 @@ namespace Avalonia.Rendering.Composition
if (offset != default)
{
- if (offset.Z == 0)
- mat *= Matrix.CreateTranslation(offset.X, offset.Y);
- else
- mat *= ToMatrix(Matrix4x4.CreateTranslation(offset.ToVector3()));
+ mat *= CompositionMatrix.CreateTranslation(offset.X, offset.Y);
}
return mat;
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/CompositionMatrix.cs b/src/Avalonia.Base/Rendering/Composition/Server/CompositionMatrix.cs
new file mode 100644
index 0000000000..da1cd57d30
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/Composition/Server/CompositionMatrix.cs
@@ -0,0 +1,274 @@
+//#define DEBUG_COMPOSITION_MATRIX
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Avalonia.Metadata;
+// ReSharper disable CompareOfFloatsByEqualityOperator
+
+namespace Avalonia.Rendering.Composition.Server;
+[PrivateApi]
+public struct CompositionMatrix(Matrix matrix)
+{
+ enum Type
+ {
+ Identity = 0,
+ TranslateAndScale = 1,
+ Full = 2
+ }
+
+ private double _scaleX_11;
+ private double _skewY_12;
+ private double _perspX_13;
+ private double _skewX_21;
+ private double _scaleY_22;
+ private double _perspY_23;
+ private double _offsetX_31;
+ private double _offsetY_32;
+ private double _perspZ_33;
+ private Type _type;
+ public bool GuaranteedIdentity => _type == Type.Identity;
+ public bool GuaranteedTranslateAndScaleOnly => _type == Type.TranslateAndScale;
+ public bool GuaranteedTranslateAndScaleOnlyOrIdentity => _type is Type.TranslateAndScale or Type.Identity;
+
+ public double ScaleX => _scaleX_11;
+ public double SkewY => _skewY_12;
+ public double PerspX => _perspX_13;
+
+ public double SkewX => _skewX_21;
+ public double ScaleY => _scaleY_22;
+ public double PerspY => _perspY_23;
+
+ public double OffsetX => _offsetX_31;
+ public double OffsetY => _offsetY_32;
+ public double PerspZ => _perspZ_33;
+
+ public static CompositionMatrix Identity { get; } = default;
+
+ public static CompositionMatrix CreateTranslation(double offsetX, double offsetY)
+ {
+ if(offsetX == 0 && offsetY == 0)
+ return default;
+
+ return new CompositionMatrix
+ {
+ _type = Type.TranslateAndScale,
+ _offsetX_31 = offsetX,
+ _offsetY_32 = offsetY,
+ _scaleX_11 = 1,
+ _scaleY_22 = 1,
+
+ };
+ }
+
+ public static CompositionMatrix CreateScale(in Vector3D scales, in Vector3D centerPoint)
+ {
+ var cp = centerPoint * (Vector3D.One - scales);
+ return new CompositionMatrix()
+ {
+ _type = Type.TranslateAndScale,
+ _scaleX_11 = scales.X,
+ _scaleY_22 = scales.Y,
+ _offsetX_31 = cp.X,
+ _offsetY_32 = cp.Y,
+ _perspZ_33 = 1
+ };
+ }
+
+ public static CompositionMatrix CreateScale(double scaleX, double scaleY)
+ {
+ if (scaleX == 1 && scaleY == 1)
+ return default;
+ return new CompositionMatrix()
+ {
+ _type = Type.TranslateAndScale,
+ _scaleX_11 = scaleX,
+ _scaleY_22 = scaleY,
+ _perspZ_33 = 1
+ };
+ }
+
+ [SkipLocalsInit]
+ public static unsafe CompositionMatrix FromMatrix(in Matrix m)
+ {
+ CompositionMatrix rv;
+ if(m.M13 != 0 || m.M23 != 0 || m.M33 != 1)
+ rv._type = Type.Full;
+ else if (m.M11 != 1 || m.M22 != 1 || m.M31 != 0 || m.M32 != 0 || m.M12 != 0 || m.M21 != 0)
+ rv._type = Type.TranslateAndScale;
+ else
+ return default;
+ *(Matrix*)&rv = m;
+ return rv;
+ }
+
+ public Matrix ToMatrix()
+ {
+ if (_type == Type.Identity)
+ return Matrix.Identity;
+
+ if (_type == Type.TranslateAndScale)
+ return new Matrix(_scaleX_11, 0, 0, _scaleY_22, _offsetX_31, _offsetY_32);
+
+ return new Matrix(_scaleX_11, _skewY_12, _skewX_21, _scaleY_22, _offsetX_31, _offsetY_32, _perspX_13, _perspY_23, _perspZ_33);
+ }
+
+ //public static implicit operator Matrix(CompositionMatrix m) => m.ToMatrix();
+ public static implicit operator CompositionMatrix(Matrix m) => FromMatrix(m);
+
+ public bool Equals(CompositionMatrix m)
+ {
+ if(m._type == Type.Identity && _type == Type.Identity)
+ return true;
+ if(m._type == Type.TranslateAndScale && _type == Type.TranslateAndScale)
+ {
+ return m._scaleX_11 == _scaleX_11 &&
+ m._scaleY_22 == _scaleY_22 &&
+ m._offsetX_31 == _offsetX_31 &&
+ m._offsetY_32 == _offsetY_32;
+ }
+
+ return m._scaleX_11 == _scaleX_11 &&
+ m._skewY_12 == _skewY_12 &&
+ m._perspX_13 == _perspX_13 &&
+ m._skewX_21 == _skewX_21 &&
+ m._scaleY_22 == _scaleY_22 &&
+ m._perspY_23 == _perspY_23 &&
+ m._offsetX_31 == _offsetX_31 &&
+ m._offsetY_32 == _offsetY_32 &&
+ m._perspZ_33 == _perspZ_33;
+ }
+
+ public static bool operator ==(CompositionMatrix left, CompositionMatrix right) => left.Equals(right);
+ public static bool operator !=(CompositionMatrix left, CompositionMatrix right) => !left.Equals(right);
+
+
+ [SkipLocalsInit]
+ private static CompositionMatrix Concat(ref CompositionMatrix left, ref CompositionMatrix right)
+#if DEBUG_COMPOSITION_MATRIX
+ {
+ var res = ConcatCore(ref left, ref right);
+ var expected = left.ToMatrix() * right.ToMatrix();
+ var actual = res.ToMatrix();
+ if (!expected.Equals(actual))
+ {
+ Console.WriteLine("BUG!");
+ }
+
+ return res;
+ }
+
+ static CompositionMatrix ConcatCore(ref CompositionMatrix left, ref CompositionMatrix right)
+#endif
+ {
+ if (left._type == Type.Identity)
+ return right;
+ if (right._type == Type.Identity)
+ return left;
+
+ if(left._type == Type.TranslateAndScale && right._type == Type.TranslateAndScale)
+ {
+ return new CompositionMatrix
+ {
+ _type = Type.TranslateAndScale,
+ _scaleX_11 = left._scaleX_11 * right._scaleX_11,
+ _scaleY_22 = left._scaleY_22 * right._scaleY_22,
+ _offsetX_31 = (left._offsetX_31 * right._scaleX_11) + right._offsetX_31,
+ _offsetY_32 = (left._offsetY_32 * right._scaleY_22) + right._offsetY_32,
+ _perspZ_33 = 1
+ };
+ }
+
+ return new CompositionMatrix
+ {
+ _type = Type.Full,
+ _scaleX_11 =
+ (left._scaleX_11 * right._scaleX_11) + (left._skewY_12 * right._skewX_21) +
+ (left._perspX_13 * right._offsetX_31),
+ _skewY_12 =
+ (left._scaleX_11 * right._skewY_12) + (left._skewY_12 * right._scaleY_22) +
+ (left._perspX_13 * right._offsetY_32),
+ _perspX_13 =
+ (left._scaleX_11 * right._perspX_13) + (left._skewY_12 * right._perspY_23) +
+ (left._perspX_13 * right._perspZ_33),
+ _skewX_21 =
+ (left._skewX_21 * right._scaleX_11) + (left._scaleY_22 * right._skewX_21) +
+ (left._perspY_23 * right._offsetX_31),
+ _scaleY_22 =
+ (left._skewX_21 * right._skewY_12) + (left._scaleY_22 * right._scaleY_22) +
+ (left._perspY_23 * right._offsetY_32),
+ _perspY_23 =
+ (left._skewX_21 * right._perspX_13) + (left._scaleY_22 * right._perspY_23) +
+ (left._perspY_23 * right._perspZ_33),
+ _offsetX_31 =
+ (left._offsetX_31 * right._scaleX_11) + (left._offsetY_32 * right._skewX_21) +
+ (left._perspZ_33 * right._offsetX_31),
+ _offsetY_32 =
+ (left._offsetX_31 * right._skewY_12) + (left._offsetY_32 * right._scaleY_22) +
+ (left._perspZ_33 * right._offsetY_32),
+ _perspZ_33 =
+ (left._offsetX_31 * right._perspX_13) + (left._offsetY_32 * right._perspY_23) +
+ (left._perspZ_33 * right._perspZ_33)
+ };
+ }
+
+ public static CompositionMatrix operator*(CompositionMatrix left, CompositionMatrix right)
+ {
+ return Concat(ref left, ref right);
+ }
+
+ public Point Transform(Point p)
+#if DEBUG_COMPOSITION_MATRIX
+ {
+ var res = TransformCore(p);
+ var expected = p.Transform(ToMatrix());
+ if (res != expected)
+ {
+ Console.WriteLine("Bug!");
+ }
+
+ return res;
+ }
+
+ public Point TransformCore(Point p)
+#endif
+ {
+ if (_type == Type.Identity)
+ return p;
+ if (_type == Type.TranslateAndScale)
+ {
+ return new Point(
+ (p.X * _scaleX_11) + _offsetX_31,
+ (p.Y * _scaleY_22) + _offsetY_32);
+ }
+
+ var m44 = new Matrix4x4(
+ (float)_scaleX_11, (float)_skewY_12, (float)_perspX_13, 0,
+ (float)_skewX_21, (float)_scaleY_22, (float)_perspY_23, 0,
+ (float)_offsetX_31, (float)_offsetY_32, (float)_perspZ_33, 0,
+ 0, 0, 0, 1
+ );
+
+ var vector = new Vector3((float)p.X, (float)p.Y, 1);
+ var transformedVector = Vector3.Transform(vector, m44);
+ var z = 1 / transformedVector.Z;
+
+ return new Point(transformedVector.X * z, transformedVector.Y * z);
+ }
+
+ public override string ToString()
+ {
+ if (_type == Type.Identity)
+ return "Identity";
+ var m = ToMatrix();
+ if(m.IsIdentity)
+ return "Identity";
+ return m.ToString();
+ /*
+ if (_type == Type.Identity)
+ return "Identity";
+ if (_type == Type.TranslateAndScale)
+ return $"{_scaleX_11} 0 0 / 0 {_scaleY_22} 0 / {_offsetX_31} {_offsetY_32} / 1";
+ return $"{_scaleX_11} {_skewY_12} {_perspX_13} / {_skewX_21} {_scaleY_22} {_perspY_23} / {_offsetX_31} {_offsetY_32} {_perspZ_33}";*/
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs
index ee0447629a..5c375e6426 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.PendingCommands.cs
@@ -40,7 +40,7 @@ internal partial class CompositorDrawingContextProxy
[FieldOffset(0)] public double Opacity;
[FieldOffset(8)] public Rect? NullableOpacityRect;
- [FieldOffset(0)] public Matrix Transform;
+ [FieldOffset(0)] public CompositionMatrix Transform;
[FieldOffset(0)] public RenderOptions RenderOptions;
@@ -73,7 +73,7 @@ internal partial class CompositorDrawingContextProxy
}
}
- public void SetTransform(Matrix m)
+ public void SetTransform(CompositionMatrix m)
{
if (_autoFlush)
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
index 6b4982c490..8ab8a0eb99 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
@@ -17,8 +17,8 @@ internal partial class CompositorDrawingContextProxy : IDrawingContextImpl,
IDrawingContextWithAcrylicLikeSupport, IDrawingContextImplWithEffects
{
private readonly IDrawingContextImpl _impl;
- private static readonly ThreadSafeObjectPool> s_transformStackPool = new();
- private Stack? _transformStack = s_transformStackPool.Get();
+ private static readonly ThreadSafeObjectPool> s_transformStackPool = new();
+ private Stack? _transformStack = s_transformStackPool.Get();
public CompositorDrawingContextProxy(IDrawingContextImpl impl)
{
@@ -38,17 +38,17 @@ internal partial class CompositorDrawingContextProxy : IDrawingContextImpl,
s_transformStackPool.ReturnAndSetNull(ref _transformStack);
}
- public Matrix? PostTransform { get; set; }
+ public CompositionMatrix? PostTransform { get; set; }
// Transform that was most recently passed to set_Transform or restored by a PopXXX operation
// We use it to report the transform that would correspond to the current state if all commands were executed
- Matrix _reportedTransform = Matrix.Identity;
+ CompositionMatrix _reportedTransform = CompositionMatrix.Identity;
// Transform that was most recently passed to SetImplTransform or restored by a PopXXX operation
// We use it to save the effective transform before executing a Push operation
- Matrix _effectiveTransform = Matrix.Identity;
+ CompositionMatrix _effectiveTransform = CompositionMatrix.Identity;
- public Matrix Transform
+ public CompositionMatrix Transform
{
get => _reportedTransform;
set
@@ -58,7 +58,7 @@ internal partial class CompositorDrawingContextProxy : IDrawingContextImpl,
}
}
- void SetImplTransform(Matrix m)
+ void SetImplTransform(CompositionMatrix m)
{
_effectiveTransform = m;
if (PostTransform.HasValue)
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
index 396009841b..0bfe3e6f81 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs
@@ -29,16 +29,16 @@ namespace Avalonia.Rendering.Composition.Server
}
}
- public override UpdateResult Update(ServerCompositionTarget root, Matrix parentCombinedTransform)
+ public override UpdateResult Update(ServerCompositionTarget root, ref CompositionMatrix parentCombinedTransform)
{
- var (combinedBounds, oldInvalidated, newInvalidated) = base.Update(root, parentCombinedTransform);
+ var (combinedBounds, oldInvalidated, newInvalidated) = base.Update(root, ref parentCombinedTransform);
foreach (var child in Children)
{
if (child.AdornedVisual != null)
root.EnqueueAdornerUpdate(child);
else
{
- var res = child.Update(root, GlobalTransformMatrix);
+ var res = child.Update(root, ref GlobalTransformMatrix);
oldInvalidated |= res.InvalidatedOld;
newInvalidated |= res.InvalidatedNew;
combinedBounds = LtrbRect.FullUnion(combinedBounds, res.Bounds);
@@ -79,24 +79,26 @@ namespace Avalonia.Rendering.Composition.Server
// We are in a weird position here: bounds are in global coordinates while padding gets applied in local ones
// Since we have optimizations to AVOID recomputing transformed bounds and since visuals with effects are relatively rare
// we instead apply the transformation matrix to rescale the bounds
-
-
- // If we only have translation and scale, just scale the padding
- if (CombinedTransformMatrix is
- {
- M12: 0, M13: 0,
- M21: 0, M23: 0,
- M31: 0, M32: 0
- })
- padding = new Thickness(padding.Left * CombinedTransformMatrix.M11,
- padding.Top * CombinedTransformMatrix.M22,
- padding.Right * CombinedTransformMatrix.M11,
- padding.Bottom * CombinedTransformMatrix.M22);
- else
+
+ if (!CombinedTransformMatrix.GuaranteedIdentity)
{
- // Conservatively use the transformed rect size
- var transformedPaddingRect = new Rect().Inflate(padding).TransformToAABB(CombinedTransformMatrix);
- padding = new(Math.Max(transformedPaddingRect.Width, transformedPaddingRect.Height));
+
+ // If we only have translation and scale, just scale the padding
+ if (CombinedTransformMatrix.GuaranteedTranslateAndScaleOnly)
+ {
+ padding = new Thickness(padding.Left * CombinedTransformMatrix.ScaleX,
+ padding.Top * CombinedTransformMatrix.ScaleY,
+ padding.Right * CombinedTransformMatrix.ScaleX,
+ padding.Bottom * CombinedTransformMatrix.ScaleY);
+ }
+ else
+ {
+ // Conservatively use the transformed rect size
+ var transformedPaddingRect =
+ new Rect().Inflate(padding).TransformToAABB(CombinedTransformMatrix.ToMatrix());
+ padding = new(Math.Max(transformedPaddingRect.Width, transformedPaddingRect.Height));
+ }
+
}
AddDirtyRect(transformedBounds.Inflate(padding));
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
index 90b973c3a8..0555c2aac8 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
@@ -84,6 +84,31 @@ namespace Avalonia.Rendering.Composition.Server
_fullRedrawRequested = true;
}
+ // Public for usage in benchmarks
+ public void Update()
+ {
+ Revision++;
+
+ _overlays.MarkUpdateCallStart();
+ using (Diagnostic.BeginCompositorUpdatePass())
+ {
+ var transform = CompositionMatrix.CreateScale(Scaling, Scaling);
+ // Update happens in a separate phase to extend dirty rect if needed
+ Root!.Update(this, ref transform);
+
+ while (_adornerUpdateQueue.Count > 0)
+ {
+ var adorner = _adornerUpdateQueue.Dequeue();
+ adorner.Update(this, ref adorner.AdornedVisual!.GlobalTransformMatrix);
+ }
+
+ _updateRequested = false;
+ Readback.CompleteWrite(Revision);
+
+ _overlays.MarkUpdateCallEnd();
+ }
+ }
+
public void Render()
{
if (_disposed)
@@ -120,26 +145,7 @@ namespace Avalonia.Rendering.Composition.Server
if (DirtyRects.IsEmpty && !_redrawRequested && !_updateRequested)
return;
- Revision++;
-
- _overlays.MarkUpdateCallStart();
- using (Diagnostic.BeginCompositorUpdatePass())
- {
- var transform = Matrix.CreateScale(Scaling, Scaling);
- // Update happens in a separate phase to extend dirty rect if needed
- Root.Update(this, transform);
-
- while (_adornerUpdateQueue.Count > 0)
- {
- var adorner = _adornerUpdateQueue.Dequeue();
- adorner.Update(this, transform);
- }
-
- _updateRequested = false;
- Readback.CompleteWrite(Revision);
-
- _overlays.MarkUpdateCallEnd();
- }
+ Update();
if (!_redrawRequested)
return;
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
index c2d43f5667..86a4de4814 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
@@ -18,7 +18,6 @@ namespace Avalonia.Rendering.Composition.Server
{
private bool _isDirtyForUpdate;
private LtrbRect _oldOwnContentBounds;
- private bool _isBackface;
private LtrbRect? _transformedClipBounds;
private LtrbRect _combinedTransformedClipBounds;
@@ -125,8 +124,8 @@ namespace Avalonia.Rendering.Composition.Server
return ref _readback2;
}
- public Matrix CombinedTransformMatrix { get; private set; } = Matrix.Identity;
- public Matrix GlobalTransformMatrix { get; private set; }
+ public CompositionMatrix CombinedTransformMatrix = Matrix.Identity;
+ public CompositionMatrix GlobalTransformMatrix;
public record struct UpdateResult(LtrbRect? Bounds, bool InvalidatedOld, bool InvalidatedNew)
{
@@ -136,7 +135,7 @@ namespace Avalonia.Rendering.Composition.Server
}
}
- public virtual UpdateResult Update(ServerCompositionTarget root, Matrix parentVisualTransform)
+ public virtual UpdateResult Update(ServerCompositionTarget root, ref CompositionMatrix parentTransform)
{
if (Parent == null && Root == null)
return default;
@@ -153,7 +152,6 @@ namespace Avalonia.Rendering.Composition.Server
_combinedTransformDirty = false;
}
- var parentTransform = AdornedVisual?.GlobalTransformMatrix ?? parentVisualTransform;
var newTransform = CombinedTransformMatrix * parentTransform;
@@ -161,8 +159,6 @@ namespace Avalonia.Rendering.Composition.Server
var positionChanged = false;
if (GlobalTransformMatrix != newTransform)
{
- _isBackface = Vector3.Transform(
- new Vector3(0, 0, float.PositiveInfinity), MatrixUtils.ToMatrix4x4(GlobalTransformMatrix)).Z <= 0;
positionChanged = true;
}
@@ -232,7 +228,6 @@ namespace Avalonia.Rendering.Composition.Server
IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false
&& Visible
- && !_isBackface
&& !(_combinedTransformedClipBounds.IsZeroSize);
IsVisibleInFrame = IsHitTestVisibleInFrame
@@ -259,7 +254,7 @@ namespace Avalonia.Rendering.Composition.Server
var i = Root!.Readback;
ref var readback = ref GetReadback(i.WriteIndex);
readback.Revision = root.Revision;
- readback.Matrix = GlobalTransformMatrix;
+ readback.Matrix = GlobalTransformMatrix.ToMatrix();
readback.TargetId = Root.Id;
readback.Visible = IsHitTestVisibleInFrame;
return new(TransformedOwnContentBounds, invalidateNewBounds, invalidateOldBounds);
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs
index cb4da6723f..19158c2d39 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.UserApis.cs
@@ -56,7 +56,7 @@ internal partial class ServerCompositor
var pixelSize = PixelSize.FromSize(new Size(visual.Size.X, visual.Size.Y), scaling);
var scaleTransform = Matrix.CreateScale(scaling, scaling);
- var invertRootTransform = visual.CombinedTransformMatrix.Invert();
+ var invertRootTransform = visual.CombinedTransformMatrix.ToMatrix().Invert();
IDrawingContextLayerImpl? target = null;
try
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs
index 8ba5470e7f..74e68a023f 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs
@@ -74,7 +74,7 @@ internal sealed class ServerCompositionCustomVisual : ServerCompositionContainer
protected override void RenderCore(ServerVisualRenderContext ctx, LtrbRect currentTransformedClip)
{
ctx.Canvas.AutoFlush = true;
- using var context = new ImmediateDrawingContext(ctx.Canvas, GlobalTransformMatrix, false);
+ using var context = new ImmediateDrawingContext(ctx.Canvas, GlobalTransformMatrix.ToMatrix(), false);
try
{
_handler.Render(context, currentTransformedClip.ToRect());
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerVisualRenderContext.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerVisualRenderContext.cs
index 4ee22d3bff..a8fef45491 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerVisualRenderContext.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerVisualRenderContext.cs
@@ -10,7 +10,7 @@ internal class ServerVisualRenderContext
public bool DetachedRendering { get; }
public bool RenderChildren { get; }
public CompositorDrawingContextProxy Canvas { get; }
- private readonly Stack? _transformStack;
+ private readonly Stack? _transformStack;
public ServerVisualRenderContext(CompositorDrawingContextProxy canvas, IDirtyRectTracker? dirtyRects,
bool detachedRendering, bool renderChildren)
@@ -38,7 +38,7 @@ internal class ServerVisualRenderContext
return true;
}
- public bool ShouldRenderOwnContent(ServerCompositionVisual visual, LtrbRect currentTransformedClip)
+ public bool ShouldRenderOwnContent(ServerCompositionVisual visual, ref LtrbRect currentTransformedClip)
{
if (DetachedRendering)
return true;
diff --git a/src/Avalonia.Base/Vector3D.cs b/src/Avalonia.Base/Vector3D.cs
index 9502898677..add27705f7 100644
--- a/src/Avalonia.Base/Vector3D.cs
+++ b/src/Avalonia.Base/Vector3D.cs
@@ -75,6 +75,11 @@ public readonly record struct Vector3D(double X, double Y, double Z)
public static Vector3D Multiply(Vector3D left, Vector3D right) =>
new(left.X * right.X, left.Y * right.Y, left.Z * right.Z);
+ ///
+ /// Multiplies the vector by the second vector.
+ ///
+ public static Vector3D operator *(Vector3D left, Vector3D right) => Multiply(left, right);
+
///
/// Multiplies the vector by the given scalar.
///
@@ -123,6 +128,8 @@ public readonly record struct Vector3D(double X, double Y, double Z)
/// Length of the vector.
///
public double Length => Math.Sqrt(Dot(this, this));
+
+ public static Vector3D One { get; } = new Vector3D(1, 1, 1);
///
/// Returns a normalized version of this vector.
diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml
index ce989296b1..ab2bfb2496 100644
--- a/src/Avalonia.Base/composition-schema.xml
+++ b/src/Avalonia.Base/composition-schema.xml
@@ -13,6 +13,10 @@
+