Browse Source

Initial implementation of dirty rect drawing.

scenegraph-after-breakage
Steven Kirk 10 years ago
parent
commit
52c3daea11
  1. 15
      src/Avalonia.Visuals/Rect.cs
  2. 61
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  3. 36
      src/Avalonia.Visuals/Rendering/DirtyRects.cs
  4. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  5. 48
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

15
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>

61
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);
}
}
}

36
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
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
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(

Loading…
Cancel
Save