Sfoglia il codice sorgente

Make SceneBuilder update dirty rects.

Steven Kirk 9 anni fa
parent
commit
3c189cbe82

+ 1 - 0
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@@ -110,6 +110,7 @@
     <Compile Include="Rendering\IRenderer.cs" />
     <Compile Include="Rendering\IRendererFactory.cs" />
     <Compile Include="Rendering\IRenderLoop.cs" />
+    <Compile Include="Rendering\DirtyRects.cs" />
     <Compile Include="Rendering\Renderer.cs" />
     <Compile Include="Rendering\RendererMixin.cs" />
     <Compile Include="Rendering\DefaultRenderLoop.cs" />

+ 36 - 0
src/Avalonia.Visuals/Rendering/DirtyRects.cs

@@ -0,0 +1,36 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Avalonia.Rendering
+{
+    public class DirtyRects
+    {
+        private List<Rect> _rects = new List<Rect>();
+
+        public void Add(Rect rect)
+        {
+            for (var i = 0; i < _rects.Count; ++i)
+            {
+                var intersection = _rects[i].Intersect(rect);
+
+                if (intersection != Rect.Empty)
+                {
+                    _rects[i] = intersection;
+                    return;
+                }
+            }
+
+            _rects.Add(rect);
+        }
+
+        public IList<Rect> Coalesce()
+        {
+            // TODO: Final coalesce
+            return _rects;
+        }
+    }
+}

+ 26 - 3
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -3,11 +3,9 @@
 
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Reactive.Disposables;
 using Avalonia.Media;
 using Avalonia.Platform;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering.SceneGraph
 {
@@ -15,10 +13,22 @@ namespace Avalonia.Rendering.SceneGraph
     {
         private Stack<Frame> _stack = new Stack<Frame>();
 
+        public DeferredDrawingContextImpl()
+            : this(new DirtyRects())
+        {
+        }
+
+        public DeferredDrawingContextImpl(DirtyRects dirty)
+        {
+            Dirty = dirty;
+        }
+
         public Matrix Transform { get; set; }
 
         private VisualNode Node => _stack.Peek().Node;
 
+        public DirtyRects Dirty { get; }
+
         private int Index
         {
             get { return _stack.Peek().Index; }
@@ -206,7 +216,20 @@ namespace Avalonia.Rendering.SceneGraph
             return Index < Node.Children.Count ? Node.Children[Index] as T : null;
         }
 
-        private void Pop() => _stack.Pop();
+        private void Pop()
+        {
+            foreach (var child in Node.Children)
+            {
+                var geometry = child as IGeometryNode;
+
+                if (geometry != null)
+                {
+                    Dirty.Add(geometry.Bounds);
+                }
+            }
+
+            _stack.Pop();
+        }
 
         class Frame
         {

+ 2 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@@ -11,12 +11,14 @@ namespace Avalonia.Rendering.SceneGraph
     {
         public GeometryNode(Matrix transform, IBrush brush, Pen pen, IGeometryImpl geometry)
         {
+            Bounds = geometry.Bounds * transform;
             Transform = transform;
             Brush = brush;
             Pen = pen;
             Geometry = geometry;
         }
 
+        public Rect Bounds { get; }
         public Matrix Transform { get; }
         public IBrush Brush { get; }
         public Pen Pen { get; }

+ 5 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/IGeometryNode.cs

@@ -10,6 +10,11 @@ namespace Avalonia.Rendering.SceneGraph
     /// </summary>
     public interface IGeometryNode : ISceneNode
     {
+        /// <summary>
+        /// Gets the bounds of the node in global coordinates.
+        /// </summary>
+        Rect Bounds { get; }
+
         /// <summary>
         /// Hit test the geometry in this node.
         /// </summary>

+ 3 - 4
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@@ -11,6 +11,7 @@ namespace Avalonia.Rendering.SceneGraph
     {
         public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
         {
+            Bounds = destRect * transform;
             Transform = transform;
             Source = source;
             Opacity = opacity;
@@ -18,6 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
             DestRect = destRect;
         }
 
+        public Rect Bounds { get; }
         public Matrix Transform { get; }
         public IBitmapImpl Source { get; }
         public double Opacity { get; }
@@ -39,9 +41,6 @@ namespace Avalonia.Rendering.SceneGraph
             context.DrawImage(Source, Opacity, SourceRect, DestRect);
         }
 
-        public bool HitTest(Point p)
-        {
-            return (DestRect * Transform).Contains(p);
-        }
+        public bool HitTest(Point p) => Bounds.Contains(p);
     }
 }

+ 2 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs

@@ -10,12 +10,14 @@ namespace Avalonia.Rendering.SceneGraph
     {
         public LineNode(Matrix transform, Pen pen, Point p1, Point p2)
         {
+            Bounds = new Rect(P1, P2);
             Transform = transform;
             Pen = pen;
             P1 = p1;
             P2 = p2;
         }
 
+        public Rect Bounds { get; }
         public Matrix Transform { get; }
         public Pen Pen { get; }
         public Point P1 { get; }

+ 3 - 5
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@@ -10,6 +10,7 @@ namespace Avalonia.Rendering.SceneGraph
     {
         public RectangleNode(Matrix transform, IBrush brush, Pen pen, Rect rect, float cornerRadius)
         {
+            Bounds = rect * transform;
             Transform = transform;
             Brush = brush;
             Pen = pen;
@@ -17,6 +18,7 @@ namespace Avalonia.Rendering.SceneGraph
             CornerRadius = cornerRadius;
         }
 
+        public Rect Bounds { get; }
         public Matrix Transform { get; }
         public IBrush Brush { get; }
         public Pen Pen { get; }
@@ -47,10 +49,6 @@ namespace Avalonia.Rendering.SceneGraph
             }
         }
 
-        public bool HitTest(Point p)
-        {
-            // TODO: Only test interior when Brush != null.
-            return (Rect * Transform).Contains(p);
-        }
+        public bool HitTest(Point p) => Bounds.Contains(p);
     }
 }

+ 31 - 7
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using Avalonia.Media;
 using Avalonia.Threading;
 using Avalonia.VisualTree;
+using System.Collections.Generic;
 
 namespace Avalonia.Rendering.SceneGraph
 {
@@ -13,6 +14,7 @@ namespace Avalonia.Rendering.SceneGraph
     {
         public static void UpdateAll(Scene scene)
         {
+            Contract.Requires<ArgumentNullException>(scene != null);
             Dispatcher.UIThread.VerifyAccess();
 
             using (var impl = new DeferredDrawingContextImpl())
@@ -24,6 +26,15 @@ namespace Avalonia.Rendering.SceneGraph
 
         public static bool Update(Scene scene, IVisual visual)
         {
+            var dirty = new DirtyRects();
+            return Update(scene, visual, dirty);
+        }
+
+        public static bool Update(Scene scene, IVisual visual, DirtyRects dirty)
+        {
+            Contract.Requires<ArgumentNullException>(scene != null);
+            Contract.Requires<ArgumentNullException>(visual != null);
+            Contract.Requires<ArgumentNullException>(dirty != null);
             Dispatcher.UIThread.VerifyAccess();
 
             var node = (VisualNode)scene.FindNode(visual);
@@ -44,7 +55,7 @@ namespace Avalonia.Rendering.SceneGraph
                         // descendents too.
                         var recurse = node.Visual != visual;
 
-                        using (var impl = new DeferredDrawingContextImpl())
+                        using (var impl = new DeferredDrawingContextImpl(dirty))
                         using (var context = new DrawingContext(impl))
                         {
                             if (node.Parent != null)
@@ -65,7 +76,7 @@ namespace Avalonia.Rendering.SceneGraph
                         // The control has been removed so remove it from its parent and deindex the
                         // node and its descendents.
                         ((VisualNode)node.Parent)?.Children.Remove(node);
-                        Deindex(scene, node);
+                        Deindex(scene, node, dirty);
                         return true;
                     }
                 }
@@ -76,7 +87,7 @@ namespace Avalonia.Rendering.SceneGraph
                 // node and its descendents.
                 var trim = FindFirstDeadAncestor(scene, node);
                 ((VisualNode)trim.Parent).Children.Remove(trim);
-                Deindex(scene, trim);
+                Deindex(scene, trim, dirty);
                 return true;
             }
 
@@ -170,18 +181,31 @@ namespace Avalonia.Rendering.SceneGraph
             return node;
         }
 
-        private static void Deindex(Scene scene, VisualNode node)
+        private static IList<Rect> Deindex(Scene scene, VisualNode node)
+        {
+            var dirty = new DirtyRects();
+            Deindex(scene, node, dirty);
+            return dirty.Coalesce();
+        }
+
+        private static void Deindex(Scene scene, VisualNode node, DirtyRects dirty)
         {
             scene.Remove(node);
             node.SubTreeUpdated = true;
 
             foreach (var child in node.Children)
             {
-                var visualChild = child as VisualNode;
+                var geometry = child as IGeometryNode;
+                var visual = child as VisualNode;
+
+                if (geometry != null)
+                {
+                    dirty.Add(geometry.Bounds);
+                }
 
-                if (visualChild != null)
+                if (visual != null)
                 {
-                    Deindex(scene, visualChild);
+                    Deindex(scene, visual, dirty);
                 }
             }
         }

+ 3 - 4
src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs

@@ -11,12 +11,14 @@ namespace Avalonia.Rendering.SceneGraph
     {
         public TextNode(Matrix transform, IBrush foreground, Point origin, IFormattedTextImpl text)
         {
+            Bounds = new Rect(origin, text.Measure()) * transform;
             Transform = transform;
             Foreground = foreground;
             Origin = origin;
             Text = text;
         }
 
+        public Rect Bounds { get; }
         public Matrix Transform { get; }
         public IBrush Foreground { get; }
         public Point Origin { get; }
@@ -36,9 +38,6 @@ namespace Avalonia.Rendering.SceneGraph
                 Equals(text, Text);
         }
 
-        public bool HitTest(Point p)
-        {
-            return (new Rect(Origin, Text.Measure()) * Transform).Contains(p);
-        }
+        public bool HitTest(Point p) => Bounds.Contains(p);
     }
 }

+ 8 - 2
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@@ -72,7 +72,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                     Child = new Border
                     {
                         Margin = new Thickness(10, 20, 30, 40),
-                        Child = canvas = new Canvas(),
+                        Child = canvas = new Canvas
+                        {
+                            Background = Brushes.AliceBlue,
+                        }
                     }
                 };
 
@@ -282,7 +285,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
                         Background = Brushes.Red,
                         Child = decorator = new Decorator
                         {
-                            Child = canvas = new Canvas()
+                            Child = canvas = new Canvas
+                            {
+                                Background = Brushes.AliceBlue,
+                            }
                         }
                     }
                 };