1
0
Эх сурвалжийг харах

WIP: Matrix3x3, some invalidation support, dirty rects drawing

Nikita Tsukanov 3 жил өмнө
parent
commit
16dde385d0

+ 4 - 0
samples/RenderDemo/App.xaml.cs

@@ -29,6 +29,10 @@ namespace RenderDemo
                .With(new Win32PlatformOptions
                {
                    OverlayPopups = true,
+               })
+               .With(new X11PlatformOptions
+               {
+                   UseCompositor = true
                })
                 .UsePlatformDetect()
                 .LogToTrace();

+ 19 - 7
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@@ -37,9 +37,19 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
         _target.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor);
         _update = Update;
     }
-    
-    public bool DrawFps { get; set; }
-    public bool DrawDirtyRects { get; set; }
+
+    public bool DrawFps
+    {
+        get => _target.DrawFps;
+        set => _target.DrawFps = value;
+    }
+
+    public bool DrawDirtyRects
+    {
+        get => _target.DrawDirtyRects;
+        set => _target.DrawDirtyRects = value;
+    }
+
     public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
 
     void QueueUpdate()
@@ -57,7 +67,7 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
 
     public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter)
     {
-        var res = _target.TryHitTest(new Vector2((float)p.X, (float)p.Y));
+        var res = _target.TryHitTest(p);
         if(res == null)
             yield break;
         for (var index = res.Count - 1; index >= 0; index--)
@@ -146,7 +156,7 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
                 renderTransform *= mirrorMatrix;
             }
 
-            comp.TransformMatrix = renderTransform;
+            comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform);
 
             _recorder.BeginUpdate(comp.DrawList ?? new CompositionDrawList());
             visual.Render(_recordingContext);
@@ -159,6 +169,8 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
                 SyncChildren(v);
         _dirty.Clear();
         _recalculateChildren.Clear();
+        _target.Size = _root.ClientSize;
+        _target.Scaling = _root.RenderScaling;
     }
     
     public void Resized(Size size)
@@ -169,7 +181,7 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
     {
         // We render only on the render thread for now
         Update();
-        
+        _target.RequestRedraw();
         Compositor.RequestCommitAsync().Wait();
     }
 
@@ -177,7 +189,7 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
 
     public void Stop()
     {
-        _target.IsEnabled = true;
+        _target.IsEnabled = false;
     }
     
     public void Dispose()

+ 1 - 2
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@@ -29,11 +29,10 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
         Visual = visual;
     }
 
-    internal override bool HitTest(Vector2 point)
+    internal override bool HitTest(Point pt)
     {
         if (DrawList == null)
             return false;
-        var pt = new Point(point.X, point.Y);
         if (Visual is ICustomHitTest custom)
             return custom.HitTest(pt);
         foreach (var op in DrawList)

+ 12 - 12
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Rendering.Composition
                 Root.Root = null;
         }
         
-        public PooledList<CompositionVisual>? TryHitTest(Vector2 point)
+        public PooledList<CompositionVisual>? TryHitTest(Point point)
         {
             Server.Readback.NextRead();
             if (Root == null)
@@ -28,12 +28,12 @@ namespace Avalonia.Rendering.Composition
             return res;
         }
 
-        public Vector2? TryTransformToVisual(CompositionVisual visual, Vector2 point)
+        public Point? TryTransformToVisual(CompositionVisual visual, Point point)
         {
             if (visual.Root != this)
                 return null;
             var v = visual;
-            var m = Matrix3x2.Identity;
+            var m = Matrix.Identity;
             while (v != null)
             {
                 if (!TryGetInvertedTransform(v, out var cm))
@@ -42,10 +42,10 @@ namespace Avalonia.Rendering.Composition
                 v = v.Parent;
             }
 
-            return Vector2.Transform(point, m);
+            return point * m;
         }
 
-        bool TryGetInvertedTransform(CompositionVisual visual, out Matrix3x2 matrix)
+        bool TryGetInvertedTransform(CompositionVisual visual, out Matrix matrix)
         {
             var m = visual.TryGetServerTransform();
             if (m == null)
@@ -54,24 +54,22 @@ namespace Avalonia.Rendering.Composition
                 return false;
             }
 
-            // TODO: Use Matrix3x3
-            var m32 = new Matrix3x2(m.Value.M11, m.Value.M12, m.Value.M21, m.Value.M22, m.Value.M41, m.Value.M42);
-            
-            return Matrix3x2.Invert(m32, out matrix);
+            var m33 = MatrixUtils.ToMatrix(m.Value);
+            return m33.TryInvert(out matrix);
         }
 
-        bool TryTransformTo(CompositionVisual visual, ref Vector2 v)
+        bool TryTransformTo(CompositionVisual visual, ref Point v)
         {
             if (TryGetInvertedTransform(visual, out var m))
             {
-                v = Vector2.Transform(v, m);
+                v = v * m;
                 return true;
             }
 
             return false;
         }
         
-        bool HitTestCore(CompositionVisual visual, Vector2 point, PooledList<CompositionVisual> result)
+        bool HitTestCore(CompositionVisual visual, Point point, PooledList<CompositionVisual> result)
         {
             //TODO: Check readback too
             if (visual.Visible == false)
@@ -103,5 +101,7 @@ namespace Avalonia.Rendering.Composition
 
             return false;
         }
+
+        public void RequestRedraw() => Changes.RedrawRequested.Value = true;
     }
 }

+ 1 - 0
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Numerics;
 using System.Threading.Tasks;
+using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Rendering.Composition.Animations;
 using Avalonia.Rendering.Composition.Server;

+ 20 - 0
src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs

@@ -42,5 +42,25 @@ namespace Avalonia.Rendering.Composition
 
             return mat;
         }
+
+        public static Matrix4x4 ToMatrix4x4(Matrix matrix) =>
+            new Matrix4x4(
+                (float)matrix.M11, (float)matrix.M12, 0, (float)matrix.M13,
+                (float)matrix.M21, (float)matrix.M22, 0, (float)matrix.M23,
+                0, 0, 1, 0,
+                (float)matrix.M31, (float)matrix.M32, 0, (float)matrix.M33
+            );
+        
+        public static Matrix ToMatrix(Matrix4x4 matrix44) =>
+            new Matrix(
+                matrix44.M11,
+                matrix44.M12,
+                matrix44.M14,
+                matrix44.M21,
+                matrix44.M22,
+                matrix44.M24,
+                matrix44.M41,
+                matrix44.M42,
+                matrix44.M44);
     }
 }

+ 0 - 4
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@@ -135,8 +135,4 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl
     {
         _impl.Custom(custom);
     }
-
-    public Matrix CutTransform(Matrix4x4 transform) => new Matrix(transform.M11, transform.M12, transform.M21,
-        transform.M22, transform.M41,
-        transform.M42);
 }

+ 73 - 0
src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using Avalonia.Media;
+using Avalonia.Media.TextFormatting;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal class FpsCounter
+{
+    private readonly GlyphTypeface _typeface;
+    private readonly bool _useManualFpsCounting;
+    private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
+    private int _framesThisSecond;
+    private int _fps;
+    private TimeSpan _lastFpsUpdate;
+    private GlyphRun[] _runs = new GlyphRun[10];
+    
+    public FpsCounter(GlyphTypeface typeface, bool useManualFpsCounting = false)
+    {
+        for (var c = 0; c <= 9; c++)
+        {
+            var s = c.ToString();
+            var glyph = typeface.GetGlyph((uint)(s[0]));
+            _runs[c] = new GlyphRun(typeface, 18, new ReadOnlySlice<char>(s.AsMemory()), new ushort[] { glyph });
+        }
+        _typeface = typeface;
+        _useManualFpsCounting = useManualFpsCounting;
+    }
+
+    public void FpsTick() => _framesThisSecond++;
+
+    public void RenderFps(IDrawingContextImpl context)
+    {
+        var now = _stopwatch.Elapsed;
+        var elapsed = now - _lastFpsUpdate;
+
+        if (!_useManualFpsCounting)
+            ++_framesThisSecond;
+
+        if (elapsed.TotalSeconds > 1)
+        {
+            _fps = (int)(_framesThisSecond / elapsed.TotalSeconds);
+            _framesThisSecond = 0;
+            _lastFpsUpdate = now;
+        }
+
+        var fpsLine = _fps.ToString("000");
+        double width = 0;
+        double height = 0;
+        foreach (var ch in fpsLine)
+        {
+            var run = _runs[ch - '0'];
+            width +=  run.Size.Width;
+            height = Math.Max(height, run.Size.Height);
+        }
+
+        var rect = new Rect(0, 0, width + 3, height + 3);
+
+        context.DrawRectangle(Brushes.Black, null, rect);
+
+        double offset = 0;
+        foreach (var ch in fpsLine)
+        {
+            var run = _runs[ch - '0'];
+            context.Transform = Matrix.CreateTranslation(offset, 0);
+            context.DrawGlyphRun(Brushes.White, run);
+            offset += run.Size.Width;
+        }
+    }
+}

+ 20 - 0
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@@ -16,6 +16,25 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
     {
     }
 
+    Rect? _contentBounds;
+
+    public override Rect ContentBounds
+    {
+        get
+        {
+            if (_contentBounds == null)
+            {
+                var rect = Rect.Empty;
+                if(_renderCommands!=null)
+                    foreach (var cmd in _renderCommands)
+                        rect = rect.Union(cmd.Item.Bounds);
+                _contentBounds = rect;
+            }
+
+            return _contentBounds.Value;
+        }
+    }
+
     protected override void ApplyCore(ChangeSet changes)
     {
         var ch = (DrawListVisualChanges)changes;
@@ -23,6 +42,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
         {
             _renderCommands?.Dispose();
             _renderCommands = ch.AcquireDrawCommands();
+            _contentBounds = null;
         }
         base.ApplyCore(changes);
     }

+ 80 - 3
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@@ -2,7 +2,10 @@ using System;
 using System.Numerics;
 using System.Threading;
 using Avalonia.Media;
+using Avalonia.Media.Immutable;
 using Avalonia.Platform;
+using Avalonia.Rendering.Composition.Transport;
+using Avalonia.Utilities;
 
 namespace Avalonia.Rendering.Composition.Server
 {
@@ -14,6 +17,13 @@ namespace Avalonia.Rendering.Composition.Server
         public long Id { get; }
         private ulong _frame = 1;
         private IRenderTarget? _renderTarget;
+        private FpsCounter _fpsCounter = new FpsCounter(Typeface.Default.GlyphTypeface);
+        private Rect _dirtyRect;
+        private Random _random = new();
+        private Size _layerSize;
+        private IDrawingContextLayerImpl? _layer;
+        private bool _redrawRequested;
+
 
         public ReadbackIndices Readback { get; } = new();
 
@@ -33,6 +43,11 @@ namespace Avalonia.Rendering.Composition.Server
                 _compositor.RemoveCompositionTarget(this);
         }
 
+        partial void ApplyChangesExtra(CompositionTargetChanges c)
+        {
+            _redrawRequested = true;
+        }
+
         public void Render()
         {
             if (Root == null) 
@@ -40,14 +55,76 @@ namespace Avalonia.Rendering.Composition.Server
             _renderTarget ??= _renderTargetFactory();
 
             Compositor.UpdateServerTime();
-            using (var context = _renderTarget.CreateDrawingContext(null))
+            
+            Root.Update(this, Matrix4x4.Identity);
+            
+            if(_dirtyRect.IsEmpty && !_redrawRequested)
+                return;
+            _redrawRequested = false;
+            using (var targetContext = _renderTarget.CreateDrawingContext(null))
             {
-                context.Clear(Colors.Transparent);
-                Root.Render(new CompositorDrawingContextProxy(context), Root.CombinedTransformMatrix);
+                var layerSize = Size * Scaling;
+                if (layerSize != _layerSize || _layer == null)
+                {
+                    _layer?.Dispose();
+                    _layer = null;
+                    _layer = targetContext.CreateLayer(layerSize);
+                    _layerSize = layerSize;
+                }
+
+                if (!_dirtyRect.IsEmpty)
+                {
+                    using (var context = _layer.CreateDrawingContext(null))
+                    {
+                        context.PushClip(_dirtyRect);
+                        context.Clear(Colors.Transparent);
+                        Root.Render(new CompositorDrawingContextProxy(context), Root.CombinedTransformMatrix);
+                        context.PopClip();
+                    }
+                }
+
+                targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1, new Rect(_layerSize),
+                    new Rect(_layerSize));
+                
+                
+                if (DrawDirtyRects)
+                {
+                    targetContext.DrawRectangle(new ImmutableSolidColorBrush(
+                            new Color(30, (byte)_random.Next(255), (byte)_random.Next(255),
+                                (byte)_random.Next(255)))
+                        , null, _dirtyRect);
+                }
+
+                if(DrawFps)
+                    _fpsCounter.RenderFps(targetContext);
+                _dirtyRect = Rect.Empty;
+                
             }
 
             Readback.NextWrite(_frame);
             _frame++;
         }
+
+        private static Rect SnapToDevicePixels(Rect 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));
+        }
+        
+        public void AddDirtyRect(Rect rect)
+        {
+            var snapped = SnapToDevicePixels(rect, Scaling);
+            _dirtyRect = _dirtyRect.Union(snapped);
+        }
+
+        public void Invalidate()
+        {
+            _redrawRequested = true;
+        }
     }
 }

+ 7 - 0
src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs

@@ -21,6 +21,13 @@ namespace Avalonia.Rendering.Composition.Server
             }
         }
 
+        public override void Update(ServerCompositionTarget root, Matrix4x4 transform)
+        {
+            base.Update(root, transform);
+            foreach (var child in Children) 
+                child.Update(root, GlobalTransformMatrix);
+        }
+
         public ServerCompositionContainerVisual(ServerCompositor compositor) : base(compositor)
         {
             Children = new ServerCompositionVisualCollection(compositor);

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Server/ServerSolidColorVisual.cs

@@ -8,7 +8,7 @@ namespace Avalonia.Rendering.Composition.Server
     {
         protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
         {
-            canvas.Transform = canvas.CutTransform(transform);
+            canvas.Transform = MatrixUtils.ToMatrix(transform);
             canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new RoundedRect(new Rect(new Size(Size))));
             base.RenderCore(canvas, transform);
         }

+ 36 - 16
src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs

@@ -6,6 +6,7 @@ namespace Avalonia.Rendering.Composition.Server
 {
     unsafe partial class ServerCompositionVisual : ServerObject
     {
+        private bool _isDirty;
         protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
         {
             
@@ -17,7 +18,7 @@ namespace Avalonia.Rendering.Composition.Server
                 return;
             if(Opacity == 0)
                 return;
-            canvas.PreTransform = canvas.CutTransform(transform);
+            canvas.PreTransform = MatrixUtils.ToMatrix(transform);
             canvas.Transform = Matrix.Identity;
             if (Opacity != 1)
                 canvas.PushOpacity(Opacity);
@@ -25,7 +26,8 @@ namespace Avalonia.Rendering.Composition.Server
                 canvas.PushClip(new Rect(new Size(Size.X, Size.Y)));
             if (Clip != null) 
                 canvas.PushGeometryClip(Clip);
-
+            
+            //TODO: Check clip
             RenderCore(canvas, transform);
             
             if (Clip != null)
@@ -48,22 +50,31 @@ namespace Avalonia.Rendering.Composition.Server
             return ref _readback2;
         }
         
-        public Matrix4x4 CombinedTransformMatrix
+        public Matrix4x4 CombinedTransformMatrix { get; private set; }
+        public Matrix4x4 GlobalTransformMatrix { get; private set; }
+
+        public virtual void Update(ServerCompositionTarget root, Matrix4x4 transform)
         {
-            get
+            var res = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix,
+                Scale, RotationAngle, Orientation, Offset);
+            var i = Root!.Readback;
+            ref var readback = ref GetReadback(i.WriteIndex);
+            readback.Revision = i.WriteRevision;
+            readback.Matrix = res;
+            readback.TargetId = Root.Id;
+            //TODO: check effective opacity too
+            IsVisibleInFrame = Visible && Opacity > 0;
+            CombinedTransformMatrix = res;
+            GlobalTransformMatrix = res * transform;
+            //TODO: Cache
+            TransformedBounds = ContentBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix));
+            
+            if (!IsVisibleInFrame)
+                _isDirty = false;
+            else if (_isDirty)
             {
-                if (Root == null)
-                    return default;
-                
-                var res = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix,
-                    Scale, RotationAngle, Orientation, Offset);
-                var i = Root.Readback;
-                ref var readback = ref GetReadback(i.WriteIndex);
-                readback.Revision = i.WriteRevision;
-                readback.Matrix = res;
-                readback.TargetId = Root.Id;
-
-                return res;
+                Root.AddDirtyRect(TransformedBounds);
+                _isDirty = false;
             }
         }
         
@@ -81,11 +92,20 @@ namespace Avalonia.Rendering.Composition.Server
                 Parent = c.Parent.Value;
             if (c.Root.IsSet)
                 Root = c.Root.Value;
+            _isDirty = true;
+
+            if (IsVisibleInFrame)
+                Root?.AddDirtyRect(TransformedBounds);
+            else
+                Root?.Invalidate();
         }
 
         public ServerCompositionTarget? Root { get; private set; }
 
         public ServerCompositionVisual? Parent { get; private set; }
+        public bool IsVisibleInFrame { get; set; }
+        public Rect TransformedBounds { get; set; }
+        public virtual Rect ContentBounds => new Rect(0, 0, Size.X, Size.Y);
     }
 
 

+ 11 - 0
src/Avalonia.Base/Rendering/Composition/Transport/CompositionTargetChanges.cs

@@ -0,0 +1,11 @@
+namespace Avalonia.Rendering.Composition.Transport;
+
+partial class CompositionTargetChanges
+{
+    public Change<bool> RedrawRequested;
+
+    partial void ResetExtra()
+    {
+        RedrawRequested.Reset();
+    }
+}

+ 1 - 1
src/Avalonia.Base/Rendering/Composition/Visual.cs

@@ -59,6 +59,6 @@ namespace Avalonia.Rendering.Composition
         
         internal object? Tag { get; set; }
 
-        internal virtual bool HitTest(Vector2 point) => true;
+        internal virtual bool HitTest(Point point) => true;
     }
 }

+ 5 - 0
src/Avalonia.Base/Rendering/Composition/VisualCollection.cs

@@ -54,6 +54,11 @@ namespace Avalonia.Rendering.Composition
 
         partial void OnRemoved(CompositionVisual item) => item.Parent = null;
 
+        partial void OnBeforeClear()
+        {
+            foreach (var i in this)
+                i.Parent = null;
+        }
 
         partial void OnBeforeAdded(CompositionVisual item)
         {

+ 2 - 2
src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Threading
 
         public T Get()
         {
-            lock (_lock)
+            lock (_stack)
             {
                 if(_stack.Count == 0)
                     return new T();
@@ -19,7 +19,7 @@ namespace Avalonia.Threading
 
         public void Return(T obj)
         {
-            lock (_lock)
+            lock (_stack)
             {
                 _stack.Push(obj);
             }

+ 4 - 0
src/Avalonia.Base/composition-schema.xml

@@ -25,6 +25,10 @@
     <Object Name="CompositionTarget" CustomServerCtor="true">
         <Property Name="Root" Type="CompositionVisual?"/>
         <Property Name="IsEnabled" Type="bool"/>
+        <Property Name="DrawDirtyRects" Type="bool"/>
+        <Property Name="DrawFps" Type="bool"/>
+        <Property Name="Scaling" Type="double"/>
+        <Property Name="Size" Type="Size" />
     </Object>
     
     <Object Name="CompositionSolidColorVisual" Inherits="CompositionContainerVisual" ChangesBase="CompositionVisual">