Browse Source

Store layers in Scene.

Layers are now calculated by SceneBuilder and stored in Scene. This
means we are no longer accessing `visual.Opacity` from the render thread
(and `VerifyAccess` calls have been placed in AvaloniaObject.GetValue
and friends to ensure this cannot happen). ControlCatalog is now
rendering mostly fine.
scenegraph-after-breakage
Steven Kirk 10 years ago
parent
commit
ec2998728e
  1. 1
      samples/ControlCatalog/MainWindow.xaml.cs
  2. 5
      src/Avalonia.Base/AvaloniaObject.cs
  3. 3
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  4. 62
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  5. 45
      src/Avalonia.Visuals/Rendering/LayerDirtyRects.cs
  6. 3
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  7. 15
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  8. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs
  9. 10
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  10. 69
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  11. 55
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
  12. 119
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs
  13. 3
      tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj
  14. 50
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  15. 63
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
  16. 119
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  17. 176
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
  18. 36
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneLayersTests.cs
  19. 34
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneTests.cs

1
samples/ControlCatalog/MainWindow.xaml.cs

@ -10,6 +10,7 @@ namespace ControlCatalog
{
this.InitializeComponent();
this.AttachDevTools();
Renderer.DrawDirtyRects = Renderer.DrawFps = true;
}
private void InitializeComponent()

5
src/Avalonia.Base/AvaloniaObject.cs

@ -181,6 +181,7 @@ namespace Avalonia
public void ClearValue(AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
SetValue(property, AvaloniaProperty.UnsetValue);
}
@ -193,6 +194,7 @@ namespace Avalonia
public object GetValue(AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
if (property.IsDirect)
{
@ -234,7 +236,8 @@ namespace Avalonia
public bool IsSet(AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
PriorityValue value;
if (_values.TryGetValue(property, out value))

3
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -114,7 +114,6 @@
<Compile Include="Rendering\IRendererFactory.cs" />
<Compile Include="Rendering\IRenderLoop.cs" />
<Compile Include="Rendering\DirtyRects.cs" />
<Compile Include="Rendering\LayerDirtyRects.cs" />
<Compile Include="Rendering\Renderer.cs" />
<Compile Include="Rendering\RendererMixin.cs" />
<Compile Include="Rendering\DefaultRenderLoop.cs" />
@ -131,6 +130,8 @@
<Compile Include="Rendering\SceneGraph\RectangleNode.cs" />
<Compile Include="Rendering\SceneGraph\Scene.cs" />
<Compile Include="Rendering\SceneGraph\SceneBuilder.cs" />
<Compile Include="Rendering\SceneGraph\SceneLayer.cs" />
<Compile Include="Rendering\SceneGraph\SceneLayers.cs" />
<Compile Include="Rendering\SceneGraph\TextNode.cs" />
<Compile Include="Rendering\SceneGraph\VisualNode.cs" />
<Compile Include="Rendering\ZIndexComparer.cs" />

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

@ -20,11 +20,11 @@ namespace Avalonia.Rendering
private Scene _scene;
private IRenderTarget _renderTarget;
private List<IVisual> _dirty;
private LayerDirtyRects _dirtyRects;
private HashSet<IVisual> _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<IVisual>();
_sceneBuilder.UpdateAll(scene, dirtyRects);
_dirty = new HashSet<IVisual>();
_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;
}

45
src/Avalonia.Visuals/Rendering/LayerDirtyRects.cs

@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class LayerDirtyRects : Dictionary<IVisual, DirtyRects>
{
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();
}
}
}
}

3
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);

15
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;

4
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);
}
}

10
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@ -12,20 +12,24 @@ namespace Avalonia.Rendering.SceneGraph
private Dictionary<IVisual, IVisualNode> _index;
public Scene(IVisual rootVisual)
: this(new VisualNode(rootVisual, null), new Dictionary<IVisual, IVisualNode>())
: this(new VisualNode(rootVisual, null), new Dictionary<IVisual, IVisualNode>(), new SceneLayers(), 0)
{
_index.Add(rootVisual, Root);
}
internal Scene(VisualNode root, Dictionary<IVisual, IVisualNode> index)
internal Scene(VisualNode root, Dictionary<IVisual, IVisualNode> index, SceneLayers layers, int id)
{
Contract.Requires<ArgumentNullException>(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<IVisual, IVisualNode>();
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;
}

69
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<ArgumentNullException>(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<ArgumentNullException>(scene != null);
Contract.Requires<ArgumentNullException>(visual != null);
Contract.Requires<ArgumentNullException>(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);
}
}
}

55
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;
}
}
}

119
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<SceneLayer>
{
private List<SceneLayer> _inner = new List<SceneLayer>();
private Dictionary<IVisual, SceneLayer> _index = new Dictionary<IVisual, SceneLayer>();
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<ArgumentNullException>(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<ArgumentNullException>(layerRoot != null);
return _index.ContainsKey(layerRoot);
}
public SceneLayer GetOrAdd(IVisual layerRoot)
{
Contract.Requires<ArgumentNullException>(layerRoot != null);
SceneLayer result;
if (!_index.TryGetValue(layerRoot, out result))
{
result = Add(layerRoot);
}
return result;
}
public bool Remove(IVisual layerRoot)
{
Contract.Requires<ArgumentNullException>(layerRoot != null);
SceneLayer layer;
if (_index.TryGetValue(layerRoot, out layer))
{
_index.Remove(layerRoot);
_inner.Remove(layer);
}
return layer != null;
}
public IEnumerator<SceneLayer> 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;
}
}
}

3
tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj

@ -83,7 +83,10 @@
<Compile Include="RelativeRectTests.cs" />
<Compile Include="Rendering\DeferredRendererTests.cs" />
<Compile Include="Rendering\SceneGraph\DeferredDrawingContextImplTests.cs" />
<Compile Include="Rendering\SceneGraph\SceneBuilderTests_Layers.cs" />
<Compile Include="Rendering\SceneGraph\SceneBuilderTests.cs" />
<Compile Include="Rendering\SceneGraph\SceneLayersTests.cs" />
<Compile Include="Rendering\SceneGraph\SceneTests.cs" />
<Compile Include="Rendering\SceneGraph\VisualNodeTests.cs" />
<Compile Include="ThicknessTests.cs" />
<Compile Include="Media\BrushTests.cs" />

50
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<IRenderLoop>();
var root = new TestRoot();
var dispatcher = new Mock<IDispatcher>();
dispatcher.Setup(x => x.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback<Action, DispatcherPriority>((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<IRenderLoop>();
var root = new TestRoot();
var sceneBuilder = new Mock<ISceneBuilder>();
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<Scene>(), It.IsAny<LayerDirtyRects>()));
sceneBuilder.Verify(x => x.UpdateAll(It.IsAny<Scene>()));
}
[Fact]
@ -58,19 +65,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var sceneBuilder = new Mock<ISceneBuilder>();
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<Scene>(), It.IsAny<LayerDirtyRects>()), Times.Never);
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), It.IsAny<Visual>(), It.IsAny<LayerDirtyRects>()), Times.Never);
sceneBuilder.Verify(x => x.UpdateAll(It.IsAny<Scene>()), Times.Never);
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), It.IsAny<Visual>()), Times.Never);
}
[Fact]
@ -78,7 +86,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var sceneBuilder = new Mock<ISceneBuilder>();
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<Scene>(), control1, It.IsAny<LayerDirtyRects>()));
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), control2, It.IsAny<LayerDirtyRects>()));
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), control1));
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), control2));
}
[Fact]
@ -106,12 +115,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var dispatcher = new ImmediateDispatcher();
var sceneBuilder = new Mock<ISceneBuilder>();
sceneBuilder.Setup(x => x.UpdateAll(It.IsAny<Scene>(), It.IsAny<LayerDirtyRects>()))
.Callback<Scene, LayerDirtyRects>((scene, dirty) =>
sceneBuilder.Setup(x => x.UpdateAll(It.IsAny<Scene>()))
.Callback<Scene>(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<IRenderLayerFactory>();
@ -212,6 +219,21 @@ namespace Avalonia.Visuals.UnitTests.Rendering
x.CreateDrawingContext() == Mock.Of<IDrawingContextImpl>());
}
private Mock<IRenderLayerFactory> MockLayerFactory(IRenderRoot root)
{
var result = new Mock<IRenderLayerFactory>();
result.Setup(x => x.CreateLayer(root, root.ClientSize)).Returns(CreateLayer());
return result;
}
private Mock<ISceneBuilder> MockSceneBuilder(IRenderRoot root)
{
var result = new Mock<ISceneBuilder>();
result.Setup(x => x.UpdateAll(It.IsAny<Scene>()))
.Callback<Scene>(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)));
return result;
}
private class MockRenderLayerFactory : IRenderLayerFactory
{
private IDictionary<IVisual, IRenderTargetBitmapImpl> _layers;

63
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<IVisual>(), null);
var parent = new VisualNode(new TestRoot(), null);
var child = new VisualNode(Mock.Of<IVisual>(), 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<IVisual>(), null);
var parent = new VisualNode(new TestRoot(), null);
var child = new VisualNode(Mock.Of<IVisual>(), 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<IVisual>(), null);
var parent = new VisualNode(new TestRoot(), null);
var child1 = new VisualNode(Mock.Of<IVisual>(), null);
var child2 = new VisualNode(Mock.Of<IVisual>(), 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<IVisual>(), null);
var root = new TestRoot();
var node = new VisualNode(root, null) { LayerRoot = root };
node.AddChild(new VisualNode(Mock.Of<IVisual>(), node));
node.AddChild(new VisualNode(Mock.Of<IVisual>(), node));
node.AddChild(new VisualNode(Mock.Of<IVisual>(), node));
node.AddChild(new VisualNode(Mock.Of<IVisual>(), node));
node.AddChild(new VisualNode(Mock.Of<IVisual>(), node) { LayerRoot = root });
node.AddChild(new VisualNode(Mock.Of<IVisual>(), node) { LayerRoot = root });
node.AddChild(new VisualNode(Mock.Of<IVisual>(), node) { LayerRoot = root });
node.AddChild(new VisualNode(Mock.Of<IVisual>(), node) { LayerRoot = root });
var target = new DeferredDrawingContextImpl();
var child1 = new VisualNode(Mock.Of<IVisual>(), null);
var child2 = new VisualNode(Mock.Of<IVisual>(), null);
var layers = new SceneLayers();
var target = new DeferredDrawingContextImpl(layers);
var child1 = new VisualNode(Mock.Of<IVisual>(), null) { LayerRoot = root };
var child2 = new VisualNode(Mock.Of<IVisual>(), 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<IVisual>(), 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<IVisual>(), 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<IVisual>(), 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<IVisual>(), 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<IVisual>(), 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))
{

119
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<ILayoutManager>();
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(

176
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<ILayoutManager>();
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<ILayoutManager>();
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<ILayoutManager>();
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);
}
}
}
}

36
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);
}
}
}

34
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);
}
}
}
Loading…
Cancel
Save