瀏覽代碼

Initial implementation of dirty rect drawing.

Steven Kirk 9 年之前
父節點
當前提交
52c3daea11

+ 15 - 0
src/Avalonia.Visuals/Rect.cs

@@ -412,6 +412,21 @@ namespace Avalonia
             return new Rect(Position + offset, Size);
         }
 
+        /// <summary>
+        /// Gets the union of two rectangles.
+        /// </summary>
+        /// <param name="rect">The other rectangle.</param>
+        /// <returns>The union.</returns>
+        public Rect Union(Rect rect)
+        {
+            var x1 = Math.Min(this.X, rect.X);
+            var x2 = Math.Max(this.Right, rect.Right);
+            var y1 = Math.Min(this.Y, rect.Y);
+            var y2 = Math.Max(this.Bottom, rect.Bottom);
+
+            return new Rect(new Point(x1, y1), new Point(x2, y2));
+        }
+
         /// <summary>
         /// Returns a new <see cref="Rect"/> with the specified X position.
         /// </summary>

+ 44 - 17
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -17,7 +17,9 @@ namespace Avalonia.Rendering
         private Scene _scene;
         private IRenderTarget _renderTarget;
         private List<IVisual> _dirty = new List<IVisual>();
+        private ConcurrentQueue<Rect> _renderQueue = new ConcurrentQueue<Rect>();
         private bool _needsUpdate;
+        private bool _updateQueued;
         private bool _needsRender;
 
         private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
@@ -121,32 +123,50 @@ namespace Avalonia.Rendering
         {
             Dispatcher.UIThread.VerifyAccess();
 
-            var scene = _scene.Clone();
-
-            if (_dirty.Count > 0)
+            try
             {
-                foreach (var visual in _dirty)
+                var scene = _scene.Clone();
+
+                if (_dirty.Count > 0)
+                {
+                    var dirtyRects = new DirtyRects();
+
+                    foreach (var visual in _dirty)
+                    {
+                        SceneBuilder.Update(scene, visual, dirtyRects);
+                    }
+
+                    foreach (var r in dirtyRects.Coalesce())
+                    {
+                        _renderQueue.Enqueue(r);
+                    }
+
+                    _dirty.Clear();
+                }
+                else
                 {
-                    SceneBuilder.Update(scene, visual);
+                    SceneBuilder.UpdateAll(scene);
+                    _renderQueue.Enqueue(new Rect(_root.ClientSize));
                 }
+
+                _scene = scene;
+
+                _needsUpdate = false;
+                _needsRender = true;
+                _root.Invalidate(new Rect(_root.ClientSize));
             }
-            else
+            finally
             {
-                SceneBuilder.UpdateAll(scene);
+                _updateQueued = false;
             }
-
-            _scene = scene;
-
-            _needsUpdate = false;
-            _needsRender = true;
-            _root.Invalidate(new Rect(_root.ClientSize));
         }
 
         private void OnRenderLoopTick(object sender, EventArgs e)
         {
-            if (_needsUpdate)
+            if (_needsUpdate && !_updateQueued)
             {
                 Dispatcher.UIThread.InvokeAsync(UpdateScene, DispatcherPriority.Render);
+                _updateQueued = true;
             }
 
             if (_needsRender)
@@ -162,11 +182,18 @@ namespace Avalonia.Rendering
 
                     using (var context = _renderTarget.CreateDrawingContext())
                     {
-                        Render(context, _scene.Root, new Rect(_root.ClientSize));
+                        Rect rect;
 
-                        if (DrawFps)
+                        while (_renderQueue.TryDequeue(out rect))
                         {
-                            RenderFps(context);
+                            context.PushClip(rect);
+                            Render(context, _scene.Root, rect);
+                            context.PopClip();
+
+                            if (DrawFps)
+                            {
+                                RenderFps(context);
+                            }
                         }
                     }
 

+ 27 - 9
src/Avalonia.Visuals/Rendering/DirtyRects.cs

@@ -13,23 +13,41 @@ namespace Avalonia.Rendering
 
         public void Add(Rect rect)
         {
-            for (var i = 0; i < _rects.Count; ++i)
+            if (!rect.IsEmpty)
             {
-                var intersection = _rects[i].Intersect(rect);
-
-                if (intersection != Rect.Empty)
+                for (var i = 0; i < _rects.Count; ++i)
                 {
-                    _rects[i] = intersection;
-                    return;
+                    var union = _rects[i].Union(rect);
+
+                    if (union != Rect.Empty)
+                    {
+                        _rects[i] = union;
+                        return;
+                    }
                 }
-            }
 
-            _rects.Add(rect);
+                _rects.Add(rect);
+            }
         }
 
         public IList<Rect> Coalesce()
         {
-            // TODO: Final coalesce
+            for (var i = _rects.Count - 1; i >= 0; --i)
+            {
+                var a = _rects[i].Inflate(1);
+
+                for (var j = 0; j < i; ++j)
+                {
+                    var b = _rects[j];
+
+                    if (a.Intersects(b))
+                    {
+                        _rects[i] = _rects[i].Union(b);
+                        _rects.RemoveAt(i);
+                    }
+                }
+            }
+
             return _rects;
         }
     }

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

@@ -128,6 +128,8 @@ namespace Avalonia.Rendering.SceneGraph
             var bounds = new Rect(visual.Bounds.Size);
             var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;
 
+            contextImpl.Dirty.Add(node.Bounds);
+
             if (visual.IsVisible)
             {
                 var m = Matrix.CreateTranslation(visual.Bounds.Position);

+ 48 - 0
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@@ -7,6 +7,7 @@ using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Xunit;
 using Avalonia.Layout;
+using Avalonia.Rendering;
 
 namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
 {
@@ -400,6 +401,53 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
             }
         }
 
+        [Fact]
+        public void DirtyRects_Should_Contain_Old_And_New_Bounds_When_Margin_Changed()
+        {
+            using (TestApplication())
+            {
+                Decorator decorator;
+                Border border;
+                Canvas canvas;
+                var tree = new TestRoot
+                {
+                    Width = 100,
+                    Height = 100,
+                    Child = decorator = new Decorator
+                    {
+                        Margin = new Thickness(0, 10, 0, 0),
+                        Child = border = new Border
+                        {
+                            Background = Brushes.Red,
+                            Child = canvas = new Canvas(),
+                        }
+                    }
+                };
+
+                var layout = AvaloniaLocator.Current.GetService<ILayoutManager>();
+                layout.ExecuteInitialLayoutPass(tree);
+
+                var scene = new Scene(tree);
+                SceneBuilder.UpdateAll(scene);
+
+                var borderNode = scene.FindNode(border);
+                var canvasNode = scene.FindNode(canvas);
+                Assert.Equal(Matrix.CreateTranslation(0, 10), borderNode.Transform);
+                Assert.Equal(Matrix.CreateTranslation(0, 10), canvasNode.Transform);
+
+                decorator.Margin = new Thickness(0, 20, 0, 0);
+                layout.ExecuteLayoutPass();
+
+                scene = scene.Clone();
+
+                var dirty = new DirtyRects();
+                SceneBuilder.Update(scene, decorator, dirty);
+
+                var rects = dirty.Coalesce().ToArray();
+                Assert.Equal(new[] { new Rect(0, 10, 100, 90) }, rects);
+            }
+        }
+
         private IDisposable TestApplication()
         {
             return UnitTestApplication.Start(