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(