From 0a36466021eaff5f5e678280ad4fe64c590681a4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 27 Dec 2016 16:48:29 +0000 Subject: [PATCH] Run render tests with deferred renderer. Make render tests run all tests with both the deferred and immediate renderer. --- .../Rendering/DeferredRenderer.cs | 25 ++++++++++-- .../Rendering/DirtyVisuals.cs | 2 +- .../Rendering/SceneGraph/Scene.cs | 6 ++- .../Rendering/SceneGraph/SceneLayer.cs | 9 +---- .../Rendering/SceneGraph/SceneLayers.cs | 13 +++--- .../VisualTree/VisualExtensions.cs | 20 ++++------ tests/Avalonia.RenderTests/TestBase.cs | 40 ++++++++++++++----- .../DeferredDrawingContextImplTests.cs | 18 ++++----- .../Rendering/SceneGraph/SceneLayersTests.cs | 2 +- 9 files changed, 86 insertions(+), 49 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 1bbf752b56..7ec841bba9 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -14,7 +14,7 @@ namespace Avalonia.Rendering { private readonly IDispatcher _dispatcher; private readonly IRenderLoop _renderLoop; - private readonly IRenderRoot _root; + private readonly IVisual _root; private readonly ISceneBuilder _sceneBuilder; private readonly RenderLayers _layers; private readonly IRenderLayerFactory _layerFactory; @@ -51,6 +51,23 @@ namespace Avalonia.Rendering } } + public DeferredRenderer( + IVisual root, + IRenderTarget renderTarget, + ISceneBuilder sceneBuilder = null, + IRenderLayerFactory layerFactory = null) + { + Contract.Requires(root != null); + Contract.Requires(renderTarget != null); + + _root = root; + _renderTarget = renderTarget; + _sceneBuilder = sceneBuilder ?? new SceneBuilder(); + _scene = new Scene(root); + _layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); + _layers = new RenderLayers(_layerFactory); + } + public bool DrawFps { get; set; } public bool DrawDirtyRects { get; set; } public string DebugFramesPath { get; set; } @@ -81,6 +98,8 @@ namespace Avalonia.Rendering public void Render(Rect rect) { + UpdateScene(); + Render(_scene); } private void Render(Scene scene) @@ -198,7 +217,7 @@ namespace Avalonia.Rendering { if (_renderTarget == null) { - _renderTarget = _root.CreateRenderTarget(); + _renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); } using (var context = _renderTarget.CreateDrawingContext()) @@ -259,7 +278,7 @@ namespace Avalonia.Rendering } _dirty.Clear(); - _root.Invalidate(new Rect(scene.Size)); + (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size)); } finally { diff --git a/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs index 8e00edf1d0..ab320e62a5 100644 --- a/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs +++ b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs @@ -14,7 +14,7 @@ namespace Avalonia.Rendering public void Add(IVisual visual) { - var distance = visual.IsAttachedToVisualTree ? visual.CalculateDistanceFromVisualRoot() : -1; + var distance = visual.CalculateDistanceFromAncestor(visual.VisualRoot); int existingDistance; if (_index.TryGetValue(visual, out existingDistance)) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 3874038621..ee9ba0c612 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -12,7 +12,11 @@ namespace Avalonia.Rendering.SceneGraph private Dictionary _index; public Scene(IVisual rootVisual) - : this(new VisualNode(rootVisual, null), new Dictionary(), new SceneLayers(), 0) + : this( + new VisualNode(rootVisual, null), + new Dictionary(), + new SceneLayers(rootVisual), + 0) { _index.Add(rootVisual, Root); } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs index 564879ad9e..2dc12f0182 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs @@ -5,14 +5,7 @@ namespace Avalonia.Rendering.SceneGraph { public class SceneLayer { - public SceneLayer(IVisual layerRoot) - { - LayerRoot = layerRoot; - Dirty = new DirtyRects(); - DistanceFromRoot = layerRoot.CalculateDistanceFromVisualRoot(); - } - - internal SceneLayer(IVisual layerRoot, int distanceFromRoot) + public SceneLayer(IVisual layerRoot, int distanceFromRoot) { LayerRoot = layerRoot; Dirty = new DirtyRects(); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs index 18f20767a5..2003225097 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs @@ -7,11 +7,13 @@ namespace Avalonia.Rendering.SceneGraph { public class SceneLayers : IEnumerable { - private List _inner = new List(); - private Dictionary _index = new Dictionary(); + private readonly IVisual _root; + private readonly List _inner = new List(); + private readonly Dictionary _index = new Dictionary(); - public SceneLayers() + public SceneLayers(IVisual root) { + _root = root; } public int Count => _inner.Count; @@ -39,7 +41,8 @@ namespace Avalonia.Rendering.SceneGraph { Contract.Requires(layerRoot != null); - var layer = new SceneLayer(layerRoot); + var distance = layerRoot.CalculateDistanceFromAncestor(_root); + var layer = new SceneLayer(layerRoot, distance); var insert = FindInsertIndex(layer); _index.Add(layerRoot, layer); _inner.Insert(insert, layer); @@ -48,7 +51,7 @@ namespace Avalonia.Rendering.SceneGraph public SceneLayers Clone() { - var result = new SceneLayers(); + var result = new SceneLayers(_root); foreach (var src in _inner) { diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index bedabf2292..efd273d11b 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -17,28 +17,24 @@ namespace Avalonia.VisualTree /// 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) + /// The ancestor visual. + /// + /// The number of steps from the visual to the ancestor or -1 if + /// is not a descendent of . + /// + public static int CalculateDistanceFromAncestor(this IVisual visual, IVisual ancestor) { Contract.Requires(visual != null); - var root = visual.VisualRoot; - - if (root == null) - { - throw new ArgumentException("Visual is not rooted."); - } - var result = 0; - while (visual != root) + while (visual != null && visual != ancestor) { ++result; visual = visual.VisualParent; } - return result; + return visual != null ? result : -1; } /// diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 385cb052da..a7ecc35717 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -9,6 +9,7 @@ using Avalonia.Media.Imaging; using Avalonia.Rendering; using Xunit; +using Avalonia.Platform; #if AVALONIA_CAIRO using Avalonia.Cairo; @@ -63,7 +64,9 @@ namespace Avalonia.Direct2D1.RenderTests Directory.CreateDirectory(OutputPath); } - string path = Path.Combine(OutputPath, testName + ".out.png"); + var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); + var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); + var factory = AvaloniaLocator.Current.GetService(); using (RenderTargetBitmap bitmap = new RenderTargetBitmap( (int)target.Width, @@ -73,22 +76,41 @@ namespace Avalonia.Direct2D1.RenderTests target.Measure(size); target.Arrange(new Rect(size)); bitmap.Render(target); - bitmap.Save(path); + bitmap.Save(immediatePath); + } + + using (var rtb = factory.CreateRenderTargetBitmap((int)target.Width, (int)target.Height, 96, 96)) + using (var renderer = new DeferredRenderer(target, rtb)) + { + Size size = new Size(target.Width, target.Height); + target.Measure(size); + target.Arrange(new Rect(size)); + renderer.Render(target.Bounds); + rtb.Save(deferredPath); } } protected void CompareImages([CallerMemberName] string testName = "") { - string expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); - string actualPath = Path.Combine(OutputPath, testName + ".out.png"); - using (MagickImage expected = new MagickImage(expectedPath)) - using (MagickImage actual = new MagickImage(actualPath)) + var expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); + var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); + var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); + + using (var expected = new MagickImage(expectedPath)) + using (var immediate = new MagickImage(immediatePath)) + using (var deferred = new MagickImage(deferredPath)) { - double error = expected.Compare(actual, ErrorMetric.RootMeanSquared); + double immediateError = expected.Compare(immediate, ErrorMetric.RootMeanSquared); + double deferredError = expected.Compare(deferred, ErrorMetric.RootMeanSquared); + + if (immediateError > 0.022) + { + Assert.True(false, immediatePath + ": Error = " + immediateError); + } - if (error > 0.022) + if (deferredError > 0.022) { - Assert.True(false, actualPath + ": Error = " + error); + Assert.True(false, deferredPath + ": Error = " + deferredError); } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs index 587e38d73d..373bd22459 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs @@ -16,7 +16,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { var parent = new VisualNode(new TestRoot(), null); var child = new VisualNode(Mock.Of(), null); - var layers = new SceneLayers(); + var layers = new SceneLayers(parent.Visual); var target = new DeferredDrawingContextImpl(layers); target.BeginUpdate(parent); @@ -31,7 +31,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { var parent = new VisualNode(new TestRoot(), null); var child = new VisualNode(Mock.Of(), null); - var layers = new SceneLayers(); + var layers = new SceneLayers(parent.Visual); parent.AddChild(child); @@ -50,7 +50,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var parent = new VisualNode(new TestRoot(), null); var child1 = new VisualNode(Mock.Of(), null); var child2 = new VisualNode(Mock.Of(), null); - var layers = new SceneLayers(); + var layers = new SceneLayers(parent.Visual); parent.AddChild(child1); @@ -74,7 +74,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); - var layers = new SceneLayers(); + var layers = new SceneLayers(root); var target = new DeferredDrawingContextImpl(layers); var child1 = new VisualNode(Mock.Of(), null) { LayerRoot = root }; var child2 = new VisualNode(Mock.Of(), null) { LayerRoot = root }; @@ -91,7 +91,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph public void Should_Add_DrawOperations() { var node = new VisualNode(new TestRoot(), null); - var layers = new SceneLayers(); + var layers = new SceneLayers(node.Visual); var target = new DeferredDrawingContextImpl(layers); node.LayerRoot = node.Visual; @@ -112,7 +112,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { var node = new VisualNode(new TestRoot(), null); var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); - var layers = new SceneLayers(); + var layers = new SceneLayers(node.Visual); var target = new DeferredDrawingContextImpl(layers); node.LayerRoot = node.Visual; @@ -134,7 +134,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { var node = new VisualNode(new TestRoot(), null); var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); - var layers = new SceneLayers(); + var layers = new SceneLayers(node.Visual); var target = new DeferredDrawingContextImpl(layers); node.LayerRoot = node.Visual; @@ -156,7 +156,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { var node = new VisualNode(new TestRoot(), null); var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); - var layers = new SceneLayers(); + var layers = new SceneLayers(node.Visual); var target = new DeferredDrawingContextImpl(layers); node.LayerRoot = node.Visual; @@ -180,7 +180,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 30, 100), 0)); node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 40, 100), 0)); - var layers = new SceneLayers(); + var layers = new SceneLayers(node.Visual); var target = new DeferredDrawingContextImpl(layers); using (target.BeginUpdate(node)) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs index bea820beef..fac6e1ceb6 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs @@ -23,7 +23,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph } }; - var target = new SceneLayers(); + var target = new SceneLayers(root); target.Add(root); target.Add(decorator); target.Add(border);