27 changed files with 643 additions and 89 deletions
@ -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}";*/ |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Rendering.Composition.Server; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace Avalonia.Benchmarks; |
|||
|
|||
public class CompositionMatrixOperations |
|||
{ |
|||
|
|||
private Matrix _matrix0 = Matrix.CreateScale(2, 2); // Simulate DPI scaling of the root
|
|||
private Matrix _matrix1 = Matrix.Identity; |
|||
private Matrix _matrix2 = Matrix.CreateTranslation(10, 10) * Matrix.CreateScale(1.5, 1.5); |
|||
private Matrix _matrix3 = Matrix.CreateTranslation(10, 10) * Matrix.CreateScale(1.5, 1.5); |
|||
private Matrix _matrix4 = Matrix.Identity; |
|||
|
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private void Consume<T>(ref T t) |
|||
{ |
|||
|
|||
} |
|||
|
|||
[Benchmark] |
|||
public void MultiplyMatrix() |
|||
{ |
|||
|
|||
var m = Matrix.Identity; |
|||
for (var c = 0; c < 1000; c++) |
|||
{ |
|||
m = m * _matrix0 * _matrix1 * _matrix2 * _matrix3 * _matrix4; |
|||
} |
|||
Consume(ref m); |
|||
} |
|||
|
|||
private CompositionMatrix _cmatrix0 = CompositionMatrix.CreateScale(2, 2); // Simulate DPI scaling of the root
|
|||
private CompositionMatrix _cmatrix1 = CompositionMatrix.Identity; |
|||
private CompositionMatrix _cmatrix2 = CompositionMatrix.CreateTranslation(10, 10) * CompositionMatrix.CreateScale(1.5, 1.5); |
|||
private CompositionMatrix _cmatrix3 = CompositionMatrix.CreateTranslation(10, 10) * CompositionMatrix.CreateScale(1.5, 1.5); |
|||
private CompositionMatrix _cmatrix4 = CompositionMatrix.Identity; |
|||
|
|||
[Benchmark] |
|||
public void MultiplyCompositionMatrix() |
|||
{ |
|||
var m = CompositionMatrix.Identity; |
|||
for (var c = 0; c < 1000; c++) |
|||
{ |
|||
m = m * _cmatrix0 * _cmatrix1 * _cmatrix2 * _cmatrix3 * _cmatrix4; |
|||
} |
|||
Consume(ref m); |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Controls.Platform.Surfaces; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Rendering.Composition; |
|||
using Avalonia.Rendering.Composition.Drawing; |
|||
using Avalonia.Rendering.Composition.Server; |
|||
using Avalonia.Threading; |
|||
using Avalonia.UnitTests; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace Avalonia.Benchmarks; |
|||
|
|||
public class CompositionTargetUpdateOnly : IDisposable |
|||
{ |
|||
private readonly bool _includeRender; |
|||
private readonly IDisposable _app; |
|||
private readonly Compositor _compositor; |
|||
private readonly CompositionTarget _target; |
|||
|
|||
class Timer : IRenderTimer |
|||
{ |
|||
public event Action<TimeSpan> Tick; |
|||
public bool RunsInBackground { get; } |
|||
} |
|||
|
|||
class ManualScheduler : ICompositorScheduler |
|||
{ |
|||
public void CommitRequested(Compositor compositor) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
|
|||
class NullFramebuffer : IFramebufferPlatformSurface |
|||
{ |
|||
private static readonly IntPtr Buffer = Marshal.AllocHGlobal(4); |
|||
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => |
|||
new FuncFramebufferRenderTarget(() => new LockedFramebuffer(Buffer, new PixelSize(1, 1), 4, new Vector(96, 96), PixelFormat.Rgba8888, |
|||
null)); |
|||
} |
|||
|
|||
|
|||
public CompositionTargetUpdateOnly() : this(false) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected CompositionTargetUpdateOnly(bool includeRender) |
|||
{ |
|||
_includeRender = includeRender; |
|||
_app = UnitTestApplication.Start(TestServices.StyledWindow); |
|||
_compositor = new Compositor(new RenderLoop(new Timer()), null, true, new ManualScheduler(), true, |
|||
Dispatcher.UIThread, null); |
|||
_target = _compositor.CreateCompositionTarget(() => [new NullFramebuffer()]); |
|||
_target.PixelSize = new PixelSize(1000, 1000); |
|||
_target.Scaling = 1; |
|||
var root = _compositor.CreateContainerVisual(); |
|||
root.Size = new Vector(1000, 1000); |
|||
_target.Root = root; |
|||
if (includeRender) |
|||
_target.IsEnabled = true; |
|||
CreatePyramid(root, 7); |
|||
_compositor.Commit(); |
|||
} |
|||
|
|||
void CreatePyramid(CompositionContainerVisual visual, int depth) |
|||
{ |
|||
for (var c = 0; c < 4; c++) |
|||
{ |
|||
var child = new CompositionDrawListVisual(visual.Compositor, |
|||
new ServerCompositionDrawListVisual(visual.Compositor.Server, null!), null!); |
|||
double right = c == 1 || c == 3 ? 1 : 0; |
|||
double bottom = c > 1 ? 1 : 0; |
|||
|
|||
var rect = new Rect( |
|||
visual.Size.X / 2 * right, |
|||
visual.Size.Y / 2 * bottom, |
|||
visual.Size.X / 2, |
|||
visual.Size.Y / 2 |
|||
); |
|||
child.Offset = new(rect.X, rect.Y, 0); |
|||
child.Size = new Vector(rect.Width, rect.Height); |
|||
|
|||
var ctx = new RenderDataDrawingContext(child.Compositor); |
|||
ctx.DrawRectangle(Brushes.Aqua, null, new Rect(rect.Size)); |
|||
child.DrawList = ctx.GetRenderResults(); |
|||
child.Visible = true; |
|||
visual.Children.Add(child); |
|||
if (depth > 0) |
|||
CreatePyramid(child, depth - 1); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void TargetUpdate() |
|||
{ |
|||
_target.Root.Offset = new Vector3D(_target.Root.Offset.X == 0 ? 1 : 0, 0, 0); |
|||
_compositor.Commit(); |
|||
_compositor.Server.Render(); |
|||
if (!_includeRender) |
|||
_target.Server.Update(); |
|||
|
|||
} |
|||
|
|||
|
|||
public void Dispose() |
|||
{ |
|||
_app.Dispose(); |
|||
|
|||
} |
|||
} |
|||
|
|||
public class CompositionTargetUpdateWithRender : CompositionTargetUpdateOnly |
|||
{ |
|||
public CompositionTargetUpdateWithRender() : base(true) |
|||
{ |
|||
} |
|||
} |
|||
Loading…
Reference in new issue