diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 10253ec829..a663beb912 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -412,6 +412,21 @@ namespace Avalonia return new Rect(Position + offset, Size); } + /// + /// Gets the union of two rectangles. + /// + /// The other rectangle. + /// The union. + 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)); + } + /// /// Returns a new with the specified X position. /// diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 8f10b263f9..24493eaa19 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -17,7 +17,9 @@ namespace Avalonia.Rendering private Scene _scene; private IRenderTarget _renderTarget; private List _dirty = new List(); + private ConcurrentQueue _renderQueue = new ConcurrentQueue(); 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); + } } } diff --git a/src/Avalonia.Visuals/Rendering/DirtyRects.cs b/src/Avalonia.Visuals/Rendering/DirtyRects.cs index 4101dd2d8b..99c8fd4897 100644 --- a/src/Avalonia.Visuals/Rendering/DirtyRects.cs +++ b/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 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; } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index b6fddd869d..5dc00d87c2 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/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); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index b62be07dd4..8f561d1455 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/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(); + 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(