Browse Source

Update dirty controls in order.

scenegraph-after-breakage
Steven Kirk 10 years ago
parent
commit
bae15f3ec4
  1. 1
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  2. 4
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  3. 62
      src/Avalonia.Visuals/Rendering/DirtyVisuals.cs
  4. 23
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  5. 1
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  6. 22
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
  7. 32
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  8. 36
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

1
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -108,6 +108,7 @@
<Compile Include="RelativeRect.cs" />
<Compile Include="Rendering\DefaultRenderLayerFactory.cs" />
<Compile Include="Rendering\DeferredRenderer.cs" />
<Compile Include="Rendering\DirtyVisuals.cs" />
<Compile Include="Rendering\DisplayDirtyRect.cs" />
<Compile Include="Rendering\DisplayDirtyRects.cs" />
<Compile Include="Rendering\IRenderer.cs" />

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

@ -20,7 +20,7 @@ namespace Avalonia.Rendering
private Scene _scene;
private IRenderTarget _renderTarget;
private HashSet<IVisual> _dirty;
private DirtyVisuals _dirty;
private IRenderTargetBitmapImpl _overlay;
private bool _updateQueued;
private bool _rendering;
@ -268,7 +268,7 @@ namespace Avalonia.Rendering
if (_dirty == null)
{
_dirty = new HashSet<IVisual>();
_dirty = new DirtyVisuals();
_sceneBuilder.UpdateAll(scene);
}
else if (_dirty.Count > 0)

62
src/Avalonia.Visuals/Rendering/DirtyVisuals.cs

@ -0,0 +1,62 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
internal class DirtyVisuals : IEnumerable<IVisual>
{
private SortedDictionary<int, List<IVisual>> _inner = new SortedDictionary<int, List<IVisual>>();
private Dictionary<IVisual, int> _index = new Dictionary<IVisual, int>();
public int Count => _index.Count;
public void Add(IVisual visual)
{
var distance = visual.CalculateDistanceFromVisualRoot();
int existingDistance;
if (_index.TryGetValue(visual, out existingDistance))
{
if (distance == existingDistance)
{
return;
}
_inner[existingDistance].Remove(visual);
_index.Remove(visual);
}
List<IVisual> list;
if (!_inner.TryGetValue(distance, out list))
{
list = new List<IVisual>();
_inner.Add(distance, list);
}
list.Add(visual);
_index.Add(visual, distance);
}
public void Clear()
{
_inner.Clear();
_index.Clear();
}
public IEnumerator<IVisual> GetEnumerator()
{
foreach (var i in _inner)
{
foreach (var j in i.Value)
{
yield return j;
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

23
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -18,13 +18,11 @@ namespace Avalonia.Rendering
Bitmap = factory.CreateLayer(layerRoot, size);
Size = size;
LayerRoot = layerRoot;
Order = GetDistanceFromRenderRoot(layerRoot);
}
public IRenderTargetBitmapImpl Bitmap { get; private set; }
public Size Size { get; private set; }
public IVisual LayerRoot { get; }
public int Order { get; }
public void ResizeBitmap(Size size)
{
@ -42,26 +40,5 @@ namespace Avalonia.Rendering
}
}
}
private static int GetDistanceFromRenderRoot(IVisual visual)
{
var root = visual as IRenderRoot;
var result = 0;
while (root == null)
{
++result;
visual = visual.VisualParent;
if (visual == null)
{
throw new AvaloniaInternalException("Visual is not rooted.");
}
root = visual as IRenderRoot;
}
return result;
}
}
}

1
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree;

22
src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs

@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph
{
LayerRoot = layerRoot;
Dirty = new DirtyRects();
DistanceFromRoot = CalculateDistanceFromRoot(layerRoot);
DistanceFromRoot = layerRoot.CalculateDistanceFromVisualRoot();
}
internal SceneLayer(IVisual layerRoot, int distanceFromRoot)
@ -31,25 +31,5 @@ namespace Avalonia.Rendering.SceneGraph
public DirtyRects Dirty { get; }
public int DistanceFromRoot { get; }
public double Opacity { get; set; } = 1;
private int CalculateDistanceFromRoot(IVisual visual)
{
var result = 0;
while (!(visual is IRenderRoot))
{
visual = visual.VisualParent;
if (visual == null)
{
throw new AvaloniaInternalException(
"Attempted to create a SceneLayer for an unrooted visual.");
}
++result;
}
return result;
}
}
}

32
src/Avalonia.Visuals/VisualTree/VisualExtensions.cs

@ -13,6 +13,34 @@ namespace Avalonia.VisualTree
/// </summary>
public static class VisualExtensions
{
/// <summary>
/// Calculates the distance from a visual's <see cref="IRenderRoot"/>.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>The number of steps from the visual to the render root.</returns>
/// <exception cref="ArgumentException">The visual is not rooted.</exception>
public static int CalculateDistanceFromVisualRoot(this IVisual visual)
{
Contract.Requires<ArgumentNullException>(visual != null);
var root = visual.VisualRoot;
if (root == null)
{
throw new ArgumentException("Visual is not rooted.");
}
var result = 0;
while (visual != root)
{
++result;
visual = visual.VisualParent;
}
return result;
}
/// <summary>
/// Tries to get the first common ancestor of two visuals.
/// </summary>
@ -21,6 +49,8 @@ namespace Avalonia.VisualTree
/// <returns>The common ancestor, or null if not found.</returns>
public static IVisual FindCommonVisualAncestor(this IVisual visual, IVisual target)
{
Contract.Requires<ArgumentNullException>(visual != null);
return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors())
.FirstOrDefault();
}
@ -50,6 +80,8 @@ namespace Avalonia.VisualTree
/// <returns>The visual and its ancestors.</returns>
public static IEnumerable<IVisual> GetSelfAndVisualAncestors(this IVisual visual)
{
Contract.Requires<ArgumentNullException>(visual != null);
yield return visual;
foreach (var ancestor in visual.GetVisualAncestors())

36
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -82,14 +82,26 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
[Fact]
public void Frame_Should_Call_SceneBuilder_Update_With_Dirty_Controls()
public void Should_Update_Dirty_Controls_In_Order()
{
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var sceneBuilder = MockSceneBuilder(root);
var dispatcher = new ImmediateDispatcher();
var control1 = new Border();
var control2 = new Canvas();
Border border;
Decorator decorator;
Canvas canvas;
var root = new TestRoot
{
Child = decorator = new Decorator
{
Child = border = new Border
{
Child = canvas = new Canvas()
}
}
};
var sceneBuilder = MockSceneBuilder(root);
var target = new DeferredRenderer(
root,
loop.Object,
@ -98,12 +110,18 @@ namespace Avalonia.Visuals.UnitTests.Rendering
dispatcher: dispatcher);
IgnoreFirstFrame(loop, sceneBuilder);
target.AddDirty(control1);
target.AddDirty(control2);
target.AddDirty(border);
target.AddDirty(canvas);
target.AddDirty(root);
target.AddDirty(decorator);
var result = new List<IVisual>();
sceneBuilder.Setup(x => x.Update(It.IsAny<Scene>(), It.IsAny<IVisual>()))
.Callback<Scene, IVisual>((_, v) => result.Add(v));
RunFrame(loop);
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), control1));
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), control2));
Assert.Equal(new List<IVisual> { root, decorator, border, canvas }, result);
}
[Fact]

Loading…
Cancel
Save