diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index c7c61e2b4e..413794dfa2 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -10,6 +10,7 @@ namespace ControlCatalog { this.InitializeComponent(); this.AttachDevTools(); + Renderer.DrawDirtyRects = Renderer.DrawFps = true; } private void InitializeComponent() diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 409abfe8fa..7359dceae1 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -181,6 +181,7 @@ namespace Avalonia public void ClearValue(AvaloniaProperty property) { Contract.Requires(property != null); + VerifyAccess(); SetValue(property, AvaloniaProperty.UnsetValue); } @@ -193,6 +194,7 @@ namespace Avalonia public object GetValue(AvaloniaProperty property) { Contract.Requires(property != null); + VerifyAccess(); if (property.IsDirect) { @@ -234,7 +236,8 @@ namespace Avalonia public bool IsSet(AvaloniaProperty property) { Contract.Requires(property != null); - + VerifyAccess(); + PriorityValue value; if (_values.TryGetValue(property, out value)) diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index 969bba3475..f95097d1b1 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -114,7 +114,6 @@ - @@ -131,6 +130,8 @@ + + diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 201526a32b..284dbde00e 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -20,11 +20,11 @@ namespace Avalonia.Rendering private Scene _scene; private IRenderTarget _renderTarget; - private List _dirty; - private LayerDirtyRects _dirtyRects; + private HashSet _dirty; private IRenderTargetBitmapImpl _overlay; private bool _updateQueued; private bool _rendering; + private int _lastSceneId = -1; private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private int _totalFrames; @@ -112,21 +112,22 @@ namespace Avalonia.Rendering } } - private void RenderToLayers(Scene scene, LayerDirtyRects dirtyRects) + private void RenderToLayers(Scene scene) { - if (dirtyRects != null) + if (scene.Layers.HasDirty) { - foreach (var layer in dirtyRects) + foreach (var layer in scene.Layers) { - var renderTarget = GetRenderTargetForLayer(layer.Key); - var node = (VisualNode)scene.FindNode(layer.Key); + var renderTarget = GetRenderTargetForLayer(layer.LayerRoot); + var node = (VisualNode)scene.FindNode(layer.LayerRoot); using (var context = renderTarget.CreateDrawingContext()) { - foreach (var rect in layer.Value) + foreach (var rect in layer.Dirty) { + context.Transform = Matrix.Identity; context.PushClip(rect); - Render(context, node, layer.Key, rect); + Render(context, node, layer.LayerRoot, rect); context.PopClip(); if (DrawDirtyRects) @@ -136,8 +137,6 @@ namespace Avalonia.Rendering } } } - - _layers.RemoveUnused(scene); } } @@ -204,17 +203,7 @@ namespace Avalonia.Rendering } } - //private void SaveLayers() - //{ - // int i = 0; - // foreach (var layer in _layers) - // { - // layer.Bitmap.Save($"C:\\Users\\Grokys\\Desktop\\layer{i}.png"); - // ++i; - // } - //} - - private void RenderComposite(Scene scene, LayerDirtyRects dirtyRects) + private void RenderComposite(Scene scene) { try { @@ -227,9 +216,10 @@ namespace Avalonia.Rendering { var clientRect = new Rect(_root.ClientSize); - foreach (var layer in _layers) + foreach (var layer in scene.Layers) { - context.DrawImage(layer.Bitmap, layer.LayerRoot.Opacity, clientRect, clientRect); + var renderLayer = _layers.Get(layer.LayerRoot); + context.DrawImage(renderLayer.Bitmap, layer.Opacity, clientRect, clientRect); } if (_overlay != null) @@ -253,27 +243,23 @@ namespace Avalonia.Rendering try { var scene = _scene.Clone(); - var dirtyRects = new LayerDirtyRects(); if (_dirty == null) { - _dirty = new List(); - _sceneBuilder.UpdateAll(scene, dirtyRects); + _dirty = new HashSet(); + _sceneBuilder.UpdateAll(scene); } else if (_dirty.Count > 0) { foreach (var visual in _dirty) { - _sceneBuilder.Update(scene, visual, dirtyRects); + _sceneBuilder.Update(scene, visual); } - - dirtyRects.Coalesce(); } lock (_scene) { _scene = scene; - _dirtyRects = dirtyRects.IsEmpty ? null : dirtyRects; } _dirty.Clear(); @@ -292,7 +278,7 @@ namespace Avalonia.Rendering return; } - if (!_updateQueued && (_dirty == null || _dirty.Count > 0 || _dirtyRects != null)) + if (!_updateQueued && (_dirty == null || _dirty.Count > 0)) { _updateQueued = true; _dispatcher.InvokeAsync(UpdateScene, DispatcherPriority.Render); @@ -303,17 +289,21 @@ namespace Avalonia.Rendering _dirtyRectsDisplay.Tick(); Scene scene; - LayerDirtyRects dirtyRects; lock (_scene) { scene = _scene; - dirtyRects = _dirtyRects; } - RenderToLayers(scene, dirtyRects); + if (scene.Id != _lastSceneId) + { + _layers.RemoveUnused(scene); + RenderToLayers(scene); + _lastSceneId = scene.Id; + } + RenderOverlay(); - RenderComposite(scene, dirtyRects); + RenderComposite(scene); _rendering = false; } diff --git a/src/Avalonia.Visuals/Rendering/LayerDirtyRects.cs b/src/Avalonia.Visuals/Rendering/LayerDirtyRects.cs deleted file mode 100644 index acd1e3a937..0000000000 --- a/src/Avalonia.Visuals/Rendering/LayerDirtyRects.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.VisualTree; - -namespace Avalonia.Rendering -{ - public class LayerDirtyRects : Dictionary - { - public bool IsEmpty - { - get - { - foreach (var i in Values) - { - if (!i.IsEmpty) - { - return false; - } - } - - return true; - } - } - - public void Add(IVisual layerRoot, Rect rect) - { - DirtyRects rects; - - if (!TryGetValue(layerRoot, out rects)) - { - Add(layerRoot, rects = new DirtyRects()); - } - - rects.Add(rect); - } - - public void Coalesce() - { - foreach (var i in Values) - { - i.Coalesce(); - } - } - } -} diff --git a/src/Avalonia.Visuals/Rendering/RenderLayers.cs b/src/Avalonia.Visuals/Rendering/RenderLayers.cs index 6cc106ba30..d7cd137496 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayers.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayers.cs @@ -45,9 +45,8 @@ namespace Avalonia.Rendering for (var i = _inner.Count - 1; i >= 0; --i) { var layer = _inner[i]; - var node = (VisualNode)scene.FindNode(layer.LayerRoot); - if (node == null || node.LayerRoot.VisualRoot == null || node.LayerRoot != layer.LayerRoot) + if (!scene.Layers.Exists(layer.LayerRoot)) { layer.Bitmap.Dispose(); _inner.RemoveAt(i); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index a6a4cc1f1a..196535a32d 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -13,19 +13,14 @@ namespace Avalonia.Rendering.SceneGraph private int _childIndex; private int _drawOperationindex; - public DeferredDrawingContextImpl() - : this(new LayerDirtyRects()) + public DeferredDrawingContextImpl(SceneLayers layers) { - } - - public DeferredDrawingContextImpl(LayerDirtyRects dirty) - { - Dirty = dirty; + Layers = layers; } public Matrix Transform { get; set; } = Matrix.Identity; - public LayerDirtyRects Dirty { get; } + public SceneLayers Layers { get; } public UpdateState BeginUpdate(VisualNode node) { @@ -208,9 +203,11 @@ namespace Avalonia.Rendering.SceneGraph { Owner._node.TrimDrawOperations(Owner._drawOperationindex); + var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot).Dirty; + foreach (var operation in Owner._node.DrawOperations) { - Owner.Dirty.Add(Owner._node.LayerRoot, operation.Bounds); + dirty.Add(operation.Bounds); } Owner._node = Node; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs index 43c359d59a..a62e9ade93 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs @@ -4,7 +4,7 @@ namespace Avalonia.Rendering.SceneGraph { public interface ISceneBuilder { - bool Update(Scene scene, IVisual visual, LayerDirtyRects dirty); - void UpdateAll(Scene scene, LayerDirtyRects dirty); + bool Update(Scene scene, IVisual visual); + void UpdateAll(Scene scene); } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 116a26bf8d..42836d56ea 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -12,20 +12,24 @@ namespace Avalonia.Rendering.SceneGraph private Dictionary _index; public Scene(IVisual rootVisual) - : this(new VisualNode(rootVisual, null), new Dictionary()) + : this(new VisualNode(rootVisual, null), new Dictionary(), new SceneLayers(), 0) { _index.Add(rootVisual, Root); } - internal Scene(VisualNode root, Dictionary index) + internal Scene(VisualNode root, Dictionary index, SceneLayers layers, int id) { Contract.Requires(root != null); _index = index; Root = root; + Layers = layers; + Id = id; root.LayerRoot = root.Visual; } + public int Id { get; } + public SceneLayers Layers { get; } public IVisualNode Root { get; } public void Add(IVisualNode node) @@ -39,7 +43,7 @@ namespace Avalonia.Rendering.SceneGraph { var index = new Dictionary(); var root = (VisualNode)Clone((VisualNode)Root, null, index); - var result = new Scene(root, index); + var result = new Scene(root, index, Layers.Clone(), Id + 1); return result; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 53d0ebfaa7..ad92fc513a 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -11,23 +11,24 @@ namespace Avalonia.Rendering.SceneGraph { public class SceneBuilder : ISceneBuilder { - public void UpdateAll(Scene scene, LayerDirtyRects dirty) + public void UpdateAll(Scene scene) { Contract.Requires(scene != null); Dispatcher.UIThread.VerifyAccess(); - using (var impl = new DeferredDrawingContextImpl(dirty)) + scene.Layers.GetOrAdd(scene.Root.Visual); + + using (var impl = new DeferredDrawingContextImpl(scene.Layers)) using (var context = new DrawingContext(impl)) { Update(context, scene, (VisualNode)scene.Root, scene.Root.Visual.Bounds, true); } } - public bool Update(Scene scene, IVisual visual, LayerDirtyRects dirty) + public bool Update(Scene scene, IVisual visual) { Contract.Requires(scene != null); Contract.Requires(visual != null); - Contract.Requires(dirty != null); Dispatcher.UIThread.VerifyAccess(); var node = (VisualNode)scene.FindNode(visual); @@ -48,7 +49,7 @@ namespace Avalonia.Rendering.SceneGraph // descendents too. var recurse = node.Visual != visual; - using (var impl = new DeferredDrawingContextImpl(dirty)) + using (var impl = new DeferredDrawingContextImpl(scene.Layers)) using (var context = new DrawingContext(impl)) { var clip = scene.Root.Visual.Bounds; @@ -69,10 +70,10 @@ namespace Avalonia.Rendering.SceneGraph { if (node != null) { - // The control has been removed so remove it from its parent and deindex the + // The control has been hidden so remove it from its parent and deindex the // node and its descendents. ((VisualNode)node.Parent)?.RemoveChild(node); - Deindex(scene, node, dirty); + Deindex(scene, node); return true; } } @@ -83,7 +84,7 @@ namespace Avalonia.Rendering.SceneGraph // node and its descendents. var trim = FindFirstDeadAncestor(scene, node); ((VisualNode)trim.Parent).RemoveChild(trim); - Deindex(scene, trim, dirty); + Deindex(scene, trim); return true; } @@ -124,7 +125,7 @@ namespace Avalonia.Rendering.SceneGraph var bounds = new Rect(visual.Bounds.Size); var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl; - contextImpl.Dirty.Add(node.LayerRoot, node.Bounds); + contextImpl.Layers[node.LayerRoot].Dirty.Add(node.Bounds); if (visual.IsVisible) { @@ -156,11 +157,16 @@ namespace Avalonia.Rendering.SceneGraph if (opacity < 1 && node.LayerRoot != visual) { - SetLayer(node, node.Visual, contextImpl.Dirty); + SetLayer(scene, node, node.Visual); } else if (opacity >= 1 && node.LayerRoot == node.Visual && node.Parent != null) { - ClearLayer(node, contextImpl.Dirty); + ClearLayer(scene, node); + } + + if (node.LayerRoot == visual) + { + scene.Layers[visual].Opacity = visual.Opacity; } if (node.ClipToBounds) @@ -197,7 +203,7 @@ namespace Avalonia.Rendering.SceneGraph return node; } - private static void Deindex(Scene scene, VisualNode node, LayerDirtyRects dirty) + private static void Deindex(Scene scene, VisualNode node) { scene.Remove(node); node.SubTreeUpdated = true; @@ -209,48 +215,41 @@ namespace Avalonia.Rendering.SceneGraph if (geometry != null) { - dirty.Add(child.LayerRoot, geometry.Bounds); + scene.Layers[child.LayerRoot].Dirty.Add(geometry.Bounds); } if (visual != null) { - Deindex(scene, visual, dirty); + Deindex(scene, visual); } } - } - - private static void AddSubtreeBounds(VisualNode node, LayerDirtyRects dirty) - { - dirty.Add(node.LayerRoot, node.Bounds); - foreach (VisualNode child in node.Children) + if (node.LayerRoot == node.Visual) { - if (child.LayerRoot == node.LayerRoot) - { - AddSubtreeBounds(child, dirty); - } + scene.Layers.Remove(node.LayerRoot); } } - private static void ClearLayer(VisualNode node, LayerDirtyRects dirty) + private static void ClearLayer(Scene scene, VisualNode node) { var parent = (VisualNode)node.Parent; + var oldLayerRoot = node.LayerRoot; var newLayerRoot = parent.LayerRoot; - var existingDirtyRects = dirty[node.LayerRoot]; + var existingDirtyRects = scene.Layers[node.LayerRoot].Dirty; + var newDirtyRects = scene.Layers[newLayerRoot].Dirty; existingDirtyRects.Coalesce(); foreach (var r in existingDirtyRects) { - dirty.Add(newLayerRoot, r); + newDirtyRects.Add(r); } - dirty.Remove(node.LayerRoot); - - SetLayer(node, newLayerRoot, dirty); + SetLayer(scene, node, newLayerRoot); + scene.Layers.Remove(oldLayerRoot); } - private static void SetLayer(VisualNode node, IVisual layerRoot, LayerDirtyRects dirty) + private static void SetLayer(Scene scene, VisualNode node, IVisual layerRoot) { if (node.LayerRoot == layerRoot) { @@ -260,15 +259,17 @@ namespace Avalonia.Rendering.SceneGraph var oldLayerRoot = node.LayerRoot; node.LayerRoot = layerRoot; - dirty.Add(oldLayerRoot, node.Bounds); - dirty.Add(layerRoot, node.Bounds); + + var layer = scene.Layers.GetOrAdd(layerRoot); + layer.Dirty.Add(node.Bounds); + scene.Layers[oldLayerRoot].Dirty.Add(node.Bounds); foreach (VisualNode child in node.Children) { // If the child is not the start of a new layer, recurse. if (child.LayerRoot != child.Visual) { - SetLayer(child, layerRoot, dirty); + SetLayer(scene, child, layerRoot); } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs new file mode 100644 index 0000000000..295fab9791 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs @@ -0,0 +1,55 @@ +using System; +using Avalonia.VisualTree; + +namespace Avalonia.Rendering.SceneGraph +{ + public class SceneLayer + { + public SceneLayer(IVisual layerRoot) + { + LayerRoot = layerRoot; + Dirty = new DirtyRects(); + DistanceFromRoot = CalculateDistanceFromRoot(layerRoot); + } + + internal SceneLayer(IVisual layerRoot, int distanceFromRoot) + { + LayerRoot = layerRoot; + Dirty = new DirtyRects(); + DistanceFromRoot = distanceFromRoot; + } + + public SceneLayer Clone() + { + return new SceneLayer(LayerRoot, DistanceFromRoot) + { + Opacity = Opacity + }; + } + + public IVisual LayerRoot { get; } + 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/Rendering/SceneGraph/SceneLayers.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs new file mode 100644 index 0000000000..e190a1e788 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Avalonia.VisualTree; + +namespace Avalonia.Rendering.SceneGraph +{ + public class SceneLayers : IEnumerable + { + private List _inner = new List(); + private Dictionary _index = new Dictionary(); + + public SceneLayers() + { + } + + public int Count => _inner.Count; + + public bool HasDirty + { + get + { + foreach (var layer in _inner) + { + if (!layer.Dirty.IsEmpty) + { + return true; + } + } + + return false; + } + } + + public SceneLayer this[int index] => _inner[index]; + public SceneLayer this[IVisual visual] => _index[visual]; + + public SceneLayer Add(IVisual layerRoot) + { + Contract.Requires(layerRoot != null); + + var layer = new SceneLayer(layerRoot); + var insert = FindInsertIndex(layer); + _index.Add(layerRoot, layer); + _inner.Insert(insert, layer); + return layer; + } + + public SceneLayers Clone() + { + var result = new SceneLayers(); + + foreach (var src in _inner) + { + var dest = src.Clone(); + result._index.Add(dest.LayerRoot, dest); + result._inner.Add(dest); + } + + return result; + } + + public bool Exists(IVisual layerRoot) + { + Contract.Requires(layerRoot != null); + + return _index.ContainsKey(layerRoot); + } + + public SceneLayer GetOrAdd(IVisual layerRoot) + { + Contract.Requires(layerRoot != null); + + SceneLayer result; + + if (!_index.TryGetValue(layerRoot, out result)) + { + result = Add(layerRoot); + } + + return result; + } + + public bool Remove(IVisual layerRoot) + { + Contract.Requires(layerRoot != null); + + SceneLayer layer; + + if (_index.TryGetValue(layerRoot, out layer)) + { + _index.Remove(layerRoot); + _inner.Remove(layer); + } + + return layer != null; + } + + public IEnumerator GetEnumerator() => _inner.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private int FindInsertIndex(SceneLayer insert) + { + var index = 0; + + foreach (var layer in _inner) + { + if (layer.DistanceFromRoot > insert.DistanceFromRoot) + { + break; + } + + ++index; + } + + return index; + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj index b0416f74d9..c7a8739404 100644 --- a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj +++ b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj @@ -83,7 +83,10 @@ + + + diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index a89915d08d..26769b37a7 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Platform; @@ -20,11 +21,16 @@ namespace Avalonia.Visuals.UnitTests.Rendering { var loop = new Mock(); var root = new TestRoot(); + var dispatcher = new Mock(); + dispatcher.Setup(x => x.InvokeAsync(It.IsAny(), DispatcherPriority.Render)) + .Callback((a, p) => a()); + var target = new DeferredRenderer( root, loop.Object, - sceneBuilder: null, + sceneBuilder: MockSceneBuilder(root).Object, + layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher.Object); RunFrame(loop); @@ -40,17 +46,18 @@ namespace Avalonia.Visuals.UnitTests.Rendering { var loop = new Mock(); var root = new TestRoot(); - var sceneBuilder = new Mock(); + var sceneBuilder = MockSceneBuilder(root); var dispatcher = new ImmediateDispatcher(); var target = new DeferredRenderer( root, loop.Object, sceneBuilder: sceneBuilder.Object, + layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); RunFrame(loop); - sceneBuilder.Verify(x => x.UpdateAll(It.IsAny(), It.IsAny())); + sceneBuilder.Verify(x => x.UpdateAll(It.IsAny())); } [Fact] @@ -58,19 +65,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering { var loop = new Mock(); var root = new TestRoot(); - var sceneBuilder = new Mock(); + var sceneBuilder = MockSceneBuilder(root); var dispatcher = new ImmediateDispatcher(); var target = new DeferredRenderer( root, loop.Object, sceneBuilder: sceneBuilder.Object, + layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); IgnoreFirstFrame(loop, sceneBuilder); RunFrame(loop); - sceneBuilder.Verify(x => x.UpdateAll(It.IsAny(), It.IsAny()), Times.Never); - sceneBuilder.Verify(x => x.Update(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + sceneBuilder.Verify(x => x.UpdateAll(It.IsAny()), Times.Never); + sceneBuilder.Verify(x => x.Update(It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -78,7 +86,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering { var loop = new Mock(); var root = new TestRoot(); - var sceneBuilder = new Mock(); + var sceneBuilder = MockSceneBuilder(root); var dispatcher = new ImmediateDispatcher(); var control1 = new Border(); var control2 = new Canvas(); @@ -86,6 +94,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering root, loop.Object, sceneBuilder: sceneBuilder.Object, + layerFactory: MockLayerFactory(root).Object, dispatcher: dispatcher); IgnoreFirstFrame(loop, sceneBuilder); @@ -93,8 +102,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering target.AddDirty(control2); RunFrame(loop); - sceneBuilder.Verify(x => x.Update(It.IsAny(), control1, It.IsAny())); - sceneBuilder.Verify(x => x.Update(It.IsAny(), control2, It.IsAny())); + sceneBuilder.Verify(x => x.Update(It.IsAny(), control1)); + sceneBuilder.Verify(x => x.Update(It.IsAny(), control2)); } [Fact] @@ -106,12 +115,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering var dispatcher = new ImmediateDispatcher(); var sceneBuilder = new Mock(); - sceneBuilder.Setup(x => x.UpdateAll(It.IsAny(), It.IsAny())) - .Callback((scene, dirty) => + sceneBuilder.Setup(x => x.UpdateAll(It.IsAny())) + .Callback(scene => { - var rects = new DirtyRects(); - rects.Add(new Rect(root.ClientSize)); - dirty.Add(root, rects); + scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); }); var layers = new Mock(); @@ -212,6 +219,21 @@ namespace Avalonia.Visuals.UnitTests.Rendering x.CreateDrawingContext() == Mock.Of()); } + private Mock MockLayerFactory(IRenderRoot root) + { + var result = new Mock(); + result.Setup(x => x.CreateLayer(root, root.ClientSize)).Returns(CreateLayer()); + return result; + } + + private Mock MockSceneBuilder(IRenderRoot root) + { + var result = new Mock(); + result.Setup(x => x.UpdateAll(It.IsAny())) + .Callback(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize))); + return result; + } + private class MockRenderLayerFactory : IRenderLayerFactory { private IDictionary _layers; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs index c164b26ce0..587e38d73d 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs @@ -1,8 +1,8 @@ using System; using System.Linq; using Avalonia.Media; -using Avalonia.Rendering; using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; using Avalonia.VisualTree; using Moq; using Xunit; @@ -14,9 +14,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph [Fact] public void Should_Add_VisualNode() { - var parent = new VisualNode(Mock.Of(), null); + var parent = new VisualNode(new TestRoot(), null); var child = new VisualNode(Mock.Of(), null); - var target = new DeferredDrawingContextImpl(); + var layers = new SceneLayers(); + var target = new DeferredDrawingContextImpl(layers); target.BeginUpdate(parent); target.BeginUpdate(child); @@ -28,12 +29,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph [Fact] public void Should_Not_Replace_Identical_VisualNode() { - var parent = new VisualNode(Mock.Of(), null); + var parent = new VisualNode(new TestRoot(), null); var child = new VisualNode(Mock.Of(), null); + var layers = new SceneLayers(); parent.AddChild(child); - var target = new DeferredDrawingContextImpl(); + var target = new DeferredDrawingContextImpl(layers); target.BeginUpdate(parent); target.BeginUpdate(child); @@ -45,13 +47,14 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph [Fact] public void Should_Replace_Different_VisualNode() { - var parent = new VisualNode(Mock.Of(), null); + 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(); parent.AddChild(child1); - var target = new DeferredDrawingContextImpl(); + var target = new DeferredDrawingContextImpl(layers); target.BeginUpdate(parent); target.BeginUpdate(child2); @@ -63,16 +66,18 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph [Fact] public void TrimChildren_Should_Trim_Children() { - var node = new VisualNode(Mock.Of(), null); + var root = new TestRoot(); + var node = new VisualNode(root, null) { LayerRoot = root }; - node.AddChild(new VisualNode(Mock.Of(), node)); - node.AddChild(new VisualNode(Mock.Of(), node)); - node.AddChild(new VisualNode(Mock.Of(), node)); - node.AddChild(new VisualNode(Mock.Of(), node)); + node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); + node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); + node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); + node.AddChild(new VisualNode(Mock.Of(), node) { LayerRoot = root }); - var target = new DeferredDrawingContextImpl(); - var child1 = new VisualNode(Mock.Of(), null); - var child2 = new VisualNode(Mock.Of(), null); + var layers = new SceneLayers(); + var target = new DeferredDrawingContextImpl(layers); + var child1 = new VisualNode(Mock.Of(), null) { LayerRoot = root }; + var child2 = new VisualNode(Mock.Of(), null) { LayerRoot = root }; target.BeginUpdate(node); using (target.BeginUpdate(child1)) { } @@ -85,8 +90,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph [Fact] public void Should_Add_DrawOperations() { - var node = new VisualNode(Mock.Of(), null); - var target = new DeferredDrawingContextImpl(); + var node = new VisualNode(new TestRoot(), null); + var layers = new SceneLayers(); + var target = new DeferredDrawingContextImpl(layers); node.LayerRoot = node.Visual; @@ -104,9 +110,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph [Fact] public void Should_Not_Replace_Identical_DrawOperation() { - var node = new VisualNode(Mock.Of(), null); + var node = new VisualNode(new TestRoot(), null); var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); - var target = new DeferredDrawingContextImpl(); + var layers = new SceneLayers(); + var target = new DeferredDrawingContextImpl(layers); node.LayerRoot = node.Visual; node.AddDrawOperation(operation); @@ -125,9 +132,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph [Fact] public void Should_Replace_Different_DrawOperation() { - var node = new VisualNode(Mock.Of(), null); + var node = new VisualNode(new TestRoot(), null); var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); - var target = new DeferredDrawingContextImpl(); + var layers = new SceneLayers(); + var target = new DeferredDrawingContextImpl(layers); node.LayerRoot = node.Visual; node.AddDrawOperation(operation); @@ -146,10 +154,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph [Fact] public void Should_Update_DirtyRects() { - var node = new VisualNode(Mock.Of(), null); + var node = new VisualNode(new TestRoot(), null); var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0); - var dirtyRects = new LayerDirtyRects(); - var target = new DeferredDrawingContextImpl(dirtyRects); + var layers = new SceneLayers(); + var target = new DeferredDrawingContextImpl(layers); node.LayerRoot = node.Visual; @@ -158,13 +166,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph target.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100)); } - Assert.Equal(new Rect(0, 0, 100, 100), dirtyRects.Single().Value.Single()); + Assert.Equal(new Rect(0, 0, 100, 100), layers.Single().Dirty.Single()); } [Fact] public void Should_Trim_DrawOperations() { - var node = new VisualNode(Mock.Of(), null); + var node = new VisualNode(new TestRoot(), null); node.LayerRoot = node.Visual; node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 10, 100), 0)); @@ -172,7 +180,8 @@ 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 target = new DeferredDrawingContextImpl(); + var layers = new SceneLayers(); + var target = new DeferredDrawingContextImpl(layers); using (target.BeginUpdate(node)) { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index 220b77e8b7..1bc89b5112 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -7,11 +7,10 @@ using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; using Avalonia.Layout; -using Avalonia.Rendering; namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { - public class SceneBuilderTests + public partial class SceneBuilderTests { [Fact] public void Should_Build_Initial_Scene() @@ -39,7 +38,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var result = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result, new LayerDirtyRects()); + sceneBuilder.UpdateAll(result); Assert.Same(tree, ((VisualNode)result.Root).LayerRoot); Assert.Equal(1, result.Root.Children.Count); @@ -88,14 +87,14 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var result = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result, new LayerDirtyRects()); + sceneBuilder.UpdateAll(result); var canvasNode = result.FindNode(canvas); Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds); // Initial ClipBounds are correct, make sure they're still correct after updating canvas. result = result.Clone(); - Assert.True(sceneBuilder.Update(result, canvas, new LayerDirtyRects())); + Assert.True(sceneBuilder.Update(result, canvas)); canvasNode = result.FindNode(canvas); Assert.Equal(new Rect(10, 20, 160, 240), canvasNode.ClipBounds); @@ -138,14 +137,14 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene, new LayerDirtyRects()); + sceneBuilder.UpdateAll(scene); var borderNode = scene.FindNode(border); Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); // Initial ClipBounds are correct, make sure they're still correct after updating border. scene = scene.Clone(); - Assert.True(sceneBuilder.Update(scene, border, new LayerDirtyRects())); + Assert.True(sceneBuilder.Update(scene, border)); borderNode = scene.FindNode(border); Assert.Equal(new Rect(50, 50, 50, 50), borderNode.ClipBounds); @@ -179,7 +178,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var result = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result, new LayerDirtyRects()); + sceneBuilder.UpdateAll(result); var panelNode = result.FindNode(tree.Child); var expected = new IVisual[] { back, front }; @@ -213,7 +212,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var result = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(result, new LayerDirtyRects()); + sceneBuilder.UpdateAll(result); var targetNode = result.FindNode(target); @@ -248,7 +247,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var initial = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(initial, new LayerDirtyRects()); + sceneBuilder.UpdateAll(initial); var initialBackgroundNode = initial.FindNode(border).Children[0]; var initialTextNode = initial.FindNode(textBlock).DrawOperations[0]; @@ -259,7 +258,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph border.Background = Brushes.Green; var result = initial.Clone(); - sceneBuilder.Update(result, border, new LayerDirtyRects()); + sceneBuilder.Update(result, border); var borderNode = (VisualNode)result.Root.Children[0]; Assert.Same(border, borderNode.Visual); @@ -303,16 +302,16 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var initial = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(initial, new LayerDirtyRects()); + sceneBuilder.UpdateAll(initial); border.Child = decorator; var result = initial.Clone(); - Assert.True(sceneBuilder.Update(result, decorator, new LayerDirtyRects())); + Assert.True(sceneBuilder.Update(result, decorator)); // Updating canvas should result in no-op as it should have been updated along // with decorator as part of the add opeation. - Assert.False(sceneBuilder.Update(result, canvas, new LayerDirtyRects())); + Assert.False(sceneBuilder.Update(result, canvas)); var borderNode = (VisualNode)result.Root.Children[0]; Assert.Equal(1, borderNode.Children.Count); @@ -358,13 +357,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var initial = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(initial, new LayerDirtyRects()); + sceneBuilder.UpdateAll(initial); border.Child = null; var result = initial.Clone(); - Assert.True(sceneBuilder.Update(result, decorator, new LayerDirtyRects())); - Assert.False(sceneBuilder.Update(result, canvas, new LayerDirtyRects())); + Assert.True(sceneBuilder.Update(result, decorator)); + Assert.False(sceneBuilder.Update(result, canvas)); var borderNode = (VisualNode)result.Root.Children[0]; Assert.Equal(0, borderNode.Children.Count); @@ -401,13 +400,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var initial = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(initial, new LayerDirtyRects()); + sceneBuilder.UpdateAll(initial); border.IsVisible = false; var result = initial.Clone(); - Assert.True(sceneBuilder.Update(result, border, new LayerDirtyRects())); - Assert.False(sceneBuilder.Update(result, canvas, new LayerDirtyRects())); + Assert.True(sceneBuilder.Update(result, border)); + Assert.False(sceneBuilder.Update(result, canvas)); var decoratorNode = (VisualNode)result.Root.Children[0]; Assert.Equal(0, decoratorNode.Children.Count); @@ -444,7 +443,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene, new LayerDirtyRects()); + sceneBuilder.UpdateAll(scene); var borderNode = scene.FindNode(border); var canvasNode = scene.FindNode(canvas); @@ -455,7 +454,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph layout.ExecuteLayoutPass(); scene = scene.Clone(); - sceneBuilder.Update(scene, decorator, new LayerDirtyRects()); + sceneBuilder.Update(scene, decorator); borderNode = scene.FindNode(border); canvasNode = scene.FindNode(canvas); @@ -492,7 +491,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene, new LayerDirtyRects()); + sceneBuilder.UpdateAll(scene); var borderNode = scene.FindNode(border); var canvasNode = scene.FindNode(canvas); @@ -504,83 +503,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph scene = scene.Clone(); - var dirty = new LayerDirtyRects(); - sceneBuilder.Update(scene, decorator, dirty); + sceneBuilder.Update(scene, decorator); - var rects = dirty.Single().Value.ToArray(); + var rects = scene.Layers.Single().Dirty.ToArray(); Assert.Equal(new[] { new Rect(0, 10, 100, 90) }, rects); } } - [Fact] - public void Control_With_Transparency_Should_Start_New_Layer() - { - using (TestApplication()) - { - Decorator decorator; - Border border; - Canvas canvas; - var tree = new TestRoot - { - Padding = new Thickness(10), - Width = 100, - Height = 120, - Child = decorator = new Decorator - { - Padding = new Thickness(11), - Child = border = new Border - { - Opacity = 0.5, - Background = Brushes.Red, - Padding = new Thickness(12), - Child = canvas = new Canvas(), - } - } - }; - - var layout = AvaloniaLocator.Current.GetService(); - layout.ExecuteInitialLayoutPass(tree); - - var dirty = new LayerDirtyRects(); - var scene = new Scene(tree); - var sceneBuilder = new SceneBuilder(); - sceneBuilder.UpdateAll(scene, dirty); - - var rootNode = (VisualNode)scene.Root; - var borderNode = (VisualNode)scene.FindNode(border); - var canvasNode = (VisualNode)scene.FindNode(canvas); - - Assert.Same(tree, rootNode.LayerRoot); - Assert.Same(border, borderNode.LayerRoot); - Assert.Same(border, canvasNode.LayerRoot); - - Assert.Equal(2, dirty.Count()); - Assert.Empty(dirty.Select(x => x.Key).Except(new IVisual[] { tree, border })); - - border.Opacity = 1; - scene = scene.Clone(); - - dirty = new LayerDirtyRects(); - sceneBuilder.Update(scene, border, dirty); - - rootNode = (VisualNode)scene.Root; - borderNode = (VisualNode)scene.FindNode(border); - canvasNode = (VisualNode)scene.FindNode(canvas); - - Assert.Same(tree, rootNode.LayerRoot); - Assert.Same(tree, borderNode.LayerRoot); - Assert.Same(tree, canvasNode.LayerRoot); - - var rootDirty = dirty[tree]; - var borderDirty = dirty[border]; - - Assert.Equal(1, rootDirty.Count()); - Assert.Equal(1, borderDirty.Count()); - Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single()); - Assert.Equal(new Rect(21, 21, 58, 78), borderDirty.Single()); - } - } - private IDisposable TestApplication() { return UnitTestApplication.Start( diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs new file mode 100644 index 0000000000..571728d5e7 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs @@ -0,0 +1,176 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; +using Avalonia.Layout; +using Avalonia.Rendering; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public partial class SceneBuilderTests + { + [Fact] + public void Control_With_Transparency_Should_Start_New_Layer() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Padding = new Thickness(10), + Width = 100, + Height = 120, + Child = decorator = new Decorator + { + Padding = new Thickness(11), + Child = border = new Border + { + Opacity = 0.5, + Background = Brushes.Red, + Padding = new Thickness(12), + Child = canvas = new Canvas(), + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + var rootNode = (VisualNode)scene.Root; + var borderNode = (VisualNode)scene.FindNode(border); + var canvasNode = (VisualNode)scene.FindNode(canvas); + + Assert.Same(tree, rootNode.LayerRoot); + Assert.Same(border, borderNode.LayerRoot); + Assert.Same(border, canvasNode.LayerRoot); + Assert.Equal(0.5, scene.Layers[border].Opacity); + + Assert.Equal(2, scene.Layers.Count()); + Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border })); + + border.Opacity = 1; + scene = scene.Clone(); + + sceneBuilder.Update(scene, border); + + rootNode = (VisualNode)scene.Root; + borderNode = (VisualNode)scene.FindNode(border); + canvasNode = (VisualNode)scene.FindNode(canvas); + + Assert.Same(tree, rootNode.LayerRoot); + Assert.Same(tree, borderNode.LayerRoot); + Assert.Same(tree, canvasNode.LayerRoot); + Assert.Equal(1, scene.Layers.Count()); + + var rootDirty = scene.Layers[tree].Dirty; + + Assert.Equal(1, rootDirty.Count()); + Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single()); + } + } + + [Fact] + public void Removing_Transparent_Control_Should_Remove_Layers() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Padding = new Thickness(10), + Width = 100, + Height = 120, + Child = decorator = new Decorator + { + Padding = new Thickness(11), + Child = border = new Border + { + Opacity = 0.5, + Background = Brushes.Red, + Padding = new Thickness(12), + Child = canvas = new Canvas + { + Opacity = 0.75, + }, + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + Assert.Equal(3, scene.Layers.Count); + + decorator.Child = null; + scene = scene.Clone(); + + sceneBuilder.Update(scene, border); + + Assert.Equal(1, scene.Layers.Count); + } + } + + [Fact] + public void Hiding_Transparent_Control_Should_Remove_Layers() + { + using (TestApplication()) + { + Decorator decorator; + Border border; + Canvas canvas; + var tree = new TestRoot + { + Padding = new Thickness(10), + Width = 100, + Height = 120, + Child = decorator = new Decorator + { + Padding = new Thickness(11), + Child = border = new Border + { + Opacity = 0.5, + Background = Brushes.Red, + Padding = new Thickness(12), + Child = canvas = new Canvas + { + Opacity = 0.75, + }, + } + } + }; + + var layout = AvaloniaLocator.Current.GetService(); + layout.ExecuteInitialLayoutPass(tree); + + var scene = new Scene(tree); + var sceneBuilder = new SceneBuilder(); + sceneBuilder.UpdateAll(scene); + + Assert.Equal(3, scene.Layers.Count); + + border.IsVisible = false; + scene = scene.Clone(); + + sceneBuilder.Update(scene, border); + + Assert.Equal(1, scene.Layers.Count); + } + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs new file mode 100644 index 0000000000..bea820beef --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class SceneLayersTests + { + [Fact] + public void Layers_Should_Be_Ordered() + { + Border border; + Decorator decorator; + var root = new TestRoot + { + Child = border = new Border + { + Child = decorator = new Decorator(), + } + }; + + var target = new SceneLayers(); + target.Add(root); + target.Add(decorator); + target.Add(border); + + var result = target.Select(x => x.LayerRoot).ToArray(); + + Assert.Equal(new IVisual[] { root, border, decorator }, result); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneTests.cs new file mode 100644 index 0000000000..9c0adc432d --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneTests.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Rendering.SceneGraph; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class SceneTests + { + [Fact] + public void Cloning_Scene_Should_Retain_Layers_But_Not_DirtyRects() + { + Decorator decorator; + var tree = new TestRoot + { + Child = decorator = new Decorator(), + }; + + var scene = new Scene(tree); + scene.Layers.Add(tree); + scene.Layers.Add(decorator); + + scene.Layers[tree].Dirty.Add(new Rect(0, 0, 100, 100)); + scene.Layers[decorator].Dirty.Add(new Rect(0, 0, 50, 100)); + + scene = scene.Clone(); + Assert.Equal(2, scene.Layers.Count()); + Assert.Empty(scene.Layers[0].Dirty); + Assert.Empty(scene.Layers[1].Dirty); + } + } +}