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); 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> /// <summary>
/// Returns a new <see cref="Rect"/> with the specified X position. /// Returns a new <see cref="Rect"/> with the specified X position.
/// </summary> /// </summary>

61
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -17,7 +17,9 @@ namespace Avalonia.Rendering
private Scene _scene; private Scene _scene;
private IRenderTarget _renderTarget; private IRenderTarget _renderTarget;
private List<IVisual> _dirty = new List<IVisual>(); private List<IVisual> _dirty = new List<IVisual>();
private ConcurrentQueue<Rect> _renderQueue = new ConcurrentQueue<Rect>();
private bool _needsUpdate; private bool _needsUpdate;
private bool _updateQueued;
private bool _needsRender; private bool _needsRender;
private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
@ -121,32 +123,50 @@ namespace Avalonia.Rendering
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
var scene = _scene.Clone(); try
if (_dirty.Count > 0)
{ {
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) private void OnRenderLoopTick(object sender, EventArgs e)
{ {
if (_needsUpdate) if (_needsUpdate && !_updateQueued)
{ {
Dispatcher.UIThread.InvokeAsync(UpdateScene, DispatcherPriority.Render); Dispatcher.UIThread.InvokeAsync(UpdateScene, DispatcherPriority.Render);
_updateQueued = true;
} }
if (_needsRender) if (_needsRender)
@ -162,11 +182,18 @@ namespace Avalonia.Rendering
using (var context = _renderTarget.CreateDrawingContext()) 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) public void Add(Rect rect)
{ {
for (var i = 0; i < _rects.Count; ++i) if (!rect.IsEmpty)
{ {
var intersection = _rects[i].Intersect(rect); for (var i = 0; i < _rects.Count; ++i)
if (intersection != Rect.Empty)
{ {
_rects[i] = intersection; var union = _rects[i].Union(rect);
return;
if (union != Rect.Empty)
{
_rects[i] = union;
return;
}
} }
}
_rects.Add(rect); _rects.Add(rect);
}
} }
public IList<Rect> Coalesce() 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; 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 bounds = new Rect(visual.Bounds.Size);
var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl; var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;
contextImpl.Dirty.Add(node.Bounds);
if (visual.IsVisible) if (visual.IsVisible)
{ {
var m = Matrix.CreateTranslation(visual.Bounds.Position); 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 Avalonia.VisualTree;
using Xunit; using Xunit;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Rendering;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph 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() private IDisposable TestApplication()
{ {
return UnitTestApplication.Start( return UnitTestApplication.Start(

Loading…
Cancel
Save