From bae15f3ec43fb1ae2d39140753d7f916744dee58 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 10 Dec 2016 16:03:42 +0100 Subject: [PATCH] Update dirty controls in order. --- src/Avalonia.Visuals/Avalonia.Visuals.csproj | 1 + .../Rendering/DeferredRenderer.cs | 4 +- .../Rendering/DirtyVisuals.cs | 62 +++++++++++++++++++ src/Avalonia.Visuals/Rendering/RenderLayer.cs | 23 ------- .../Rendering/RenderLayers.cs | 1 - .../Rendering/SceneGraph/SceneLayer.cs | 22 +------ .../VisualTree/VisualExtensions.cs | 32 ++++++++++ .../Rendering/DeferredRendererTests.cs | 36 ++++++++--- 8 files changed, 125 insertions(+), 56 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/DirtyVisuals.cs diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index f95097d1b1..01b1da43e7 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -108,6 +108,7 @@ + diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index e29eab0d41..8a472a51d7 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -20,7 +20,7 @@ namespace Avalonia.Rendering private Scene _scene; private IRenderTarget _renderTarget; - private HashSet _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(); + _dirty = new DirtyVisuals(); _sceneBuilder.UpdateAll(scene); } else if (_dirty.Count > 0) diff --git a/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs new file mode 100644 index 0000000000..3eddb7e94d --- /dev/null +++ b/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 + { + private SortedDictionary> _inner = new SortedDictionary>(); + private Dictionary _index = new Dictionary(); + + 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 list; + + if (!_inner.TryGetValue(distance, out list)) + { + list = new List(); + _inner.Add(distance, list); + } + + list.Add(visual); + _index.Add(visual, distance); + } + + public void Clear() + { + _inner.Clear(); + _index.Clear(); + } + + public IEnumerator GetEnumerator() + { + foreach (var i in _inner) + { + foreach (var j in i.Value) + { + yield return j; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Avalonia.Visuals/Rendering/RenderLayer.cs b/src/Avalonia.Visuals/Rendering/RenderLayer.cs index 87624864d4..9c2f9d4736 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayer.cs +++ b/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; - } } } diff --git a/src/Avalonia.Visuals/Rendering/RenderLayers.cs b/src/Avalonia.Visuals/Rendering/RenderLayers.cs index e406990cfb..3173247180 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayers.cs +++ b/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; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs index 295fab9791..564879ad9e 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs +++ b/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; - } } } diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index 6eaef3363d..bedabf2292 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -13,6 +13,34 @@ namespace Avalonia.VisualTree /// public static class VisualExtensions { + /// + /// Calculates the distance from a visual's . + /// + /// The visual. + /// The number of steps from the visual to the render root. + /// The visual is not rooted. + public static int CalculateDistanceFromVisualRoot(this IVisual visual) + { + Contract.Requires(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; + } + /// /// Tries to get the first common ancestor of two visuals. /// @@ -21,6 +49,8 @@ namespace Avalonia.VisualTree /// The common ancestor, or null if not found. public static IVisual FindCommonVisualAncestor(this IVisual visual, IVisual target) { + Contract.Requires(visual != null); + return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors()) .FirstOrDefault(); } @@ -50,6 +80,8 @@ namespace Avalonia.VisualTree /// The visual and its ancestors. public static IEnumerable GetSelfAndVisualAncestors(this IVisual visual) { + Contract.Requires(visual != null); + yield return visual; foreach (var ancestor in visual.GetVisualAncestors()) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 86ae7f016b..5a4435238d 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/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(); - 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(); + sceneBuilder.Setup(x => x.Update(It.IsAny(), It.IsAny())) + .Callback((_, v) => result.Add(v)); + RunFrame(loop); - sceneBuilder.Verify(x => x.Update(It.IsAny(), control1)); - sceneBuilder.Verify(x => x.Update(It.IsAny(), control2)); + Assert.Equal(new List { root, decorator, border, canvas }, result); } [Fact]