Browse Source
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
19 changed files with 603 additions and 269 deletions
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue