Browse Source

Refactored refcounting and added tests for refcount tracking in the DeferredRenderer and friends.

pull/1277/head
Jeremy Koritzinsky 9 years ago
parent
commit
7e174a79e0
  1. 44
      src/Avalonia.Base/Utilities/Ref.cs
  2. 2
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  3. 13
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  4. 37
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  5. 7
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  6. 17
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  7. 23
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
  8. 15
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  9. 68
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

44
src/Avalonia.Base/Utilities/Ref.cs

@ -4,22 +4,58 @@ using System.Threading;
namespace Avalonia.Utilities namespace Avalonia.Utilities
{ {
/// <summary>
/// A ref-counted wrapper for a disposable object.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IRef<out T> : IDisposable where T : class public interface IRef<out T> : IDisposable where T : class
{ {
/// <summary>
/// The item that is being ref-counted.
/// </summary>
T Item { get; } T Item { get; }
/// <summary>
/// Create another reference to this object and increment the refcount.
/// </summary>
/// <returns>A new reference to this object.</returns>
IRef<T> Clone(); IRef<T> Clone();
/// <summary>
/// Create another reference to the same object, but cast the object to a different type.
/// </summary>
/// <typeparam name="TResult">The type of the new reference.</typeparam>
/// <returns>A reference to the value as the new type but sharing the refcount.</returns>
IRef<TResult> CloneAs<TResult>() where TResult : class; IRef<TResult> CloneAs<TResult>() where TResult : class;
/// <summary>
/// The current refcount of the object tracked in this reference. For debugging/unit test use only.
/// </summary>
int RefCount { get; }
} }
public static class RefCountable public static class RefCountable
{ {
/// <summary>
/// Create a reference counted object wrapping the given item.
/// </summary>
/// <typeparam name="T">The type of item.</typeparam>
/// <param name="item">The item to refcount.</param>
/// <returns>The refcounted reference to the item.</returns>
public static IRef<T> Create<T>(T item) where T : class, IDisposable public static IRef<T> Create<T>(T item) where T : class, IDisposable
{ {
return new Ref<T>(item, new RefCounter(item)); return new Ref<T>(item, new RefCounter(item));
} }
/// <summary>
/// Create an non-owning non-clonable reference to an item.
/// </summary>
/// <typeparam name="T">The type of item.</typeparam>
/// <param name="item">The item.</param>
/// <returns>A temporary reference that cannot be cloned that doesn't own the element.</returns>
public static IRef<T> CreateUnownedNotClonable<T>(T item) where T : class public static IRef<T> CreateUnownedNotClonable<T>(T item) where T : class
=> new TempRef<T>(item); => new TempRef<T>(item);
@ -40,6 +76,8 @@ namespace Avalonia.Utilities
public IRef<TResult> CloneAs<TResult>() where TResult : class public IRef<TResult> CloneAs<TResult>() where TResult : class
=> throw new NotSupportedException(); => throw new NotSupportedException();
public int RefCount => 1;
} }
class RefCounter class RefCounter
@ -90,6 +128,8 @@ namespace Avalonia.Utilities
old = current; old = current;
} }
} }
internal int RefCount => _refs;
} }
class Ref<T> : CriticalFinalizerObject, IRef<T> where T : class class Ref<T> : CriticalFinalizerObject, IRef<T> where T : class
@ -161,6 +201,8 @@ namespace Avalonia.Utilities
throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); throw new ObjectDisposedException("Ref<" + typeof(T) + ">");
} }
} }
public int RefCount => _counter.RefCount;
} }
} }

2
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@ -37,7 +37,7 @@ namespace Avalonia.Media.Imaging
/// Initializes a new instance of the <see cref="Bitmap"/> class. /// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary> /// </summary>
/// <param name="impl">A platform-specific bitmap implementation.</param> /// <param name="impl">A platform-specific bitmap implementation.</param>
protected Bitmap(IRef<IBitmapImpl> impl) public Bitmap(IRef<IBitmapImpl> impl)
{ {
PlatformImpl = impl.Clone(); PlatformImpl = impl.Clone();
} }

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

@ -112,7 +112,12 @@ namespace Avalonia.Rendering
/// <summary> /// <summary>
/// Disposes of the renderer and detaches from the render loop. /// Disposes of the renderer and detaches from the render loop.
/// </summary> /// </summary>
public void Dispose() => Stop(); public void Dispose()
{
var scene = Interlocked.Exchange(ref _scene, null);
scene.Dispose();
Stop();
}
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter)
@ -391,14 +396,16 @@ namespace Avalonia.Rendering
} }
} }
Interlocked.Exchange(ref _scene, scene); var oldScene = Interlocked.Exchange(ref _scene, scene);
oldScene.Dispose();
_dirty.Clear(); _dirty.Clear();
(_root as IRenderRoot)?.Invalidate(new Rect(scene.Size)); (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size));
} }
else else
{ {
Interlocked.Exchange(ref _scene, null); var oldScene = Interlocked.Exchange(ref _scene, null);
oldScene.Dispose();
} }
} }
finally finally

37
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -81,7 +81,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
_node?.Dispose(); // Nothing to do here since we allocate no unmanaged resources.
} }
/// <summary> /// <summary>
@ -105,7 +105,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(Transform, brush, pen, geometry)) if (next == null || !next.Item.Equals(Transform, brush, pen, geometry))
{ {
Add(RefCountable.Create(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)))); Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
} }
else else
{ {
@ -120,7 +120,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect)) if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect))
{ {
Add(RefCountable.Create(new ImageNode(Transform, source, opacity, sourceRect, destRect))); Add(new ImageNode(Transform, source, opacity, sourceRect, destRect));
} }
else else
{ {
@ -142,7 +142,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(Transform, pen, p1, p2)) if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
{ {
Add(RefCountable.Create(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)))); Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)));
} }
else else
{ {
@ -157,7 +157,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(Transform, null, pen, rect, cornerRadius)) if (next == null || !next.Item.Equals(Transform, null, pen, rect, cornerRadius))
{ {
Add(RefCountable.Create(new RectangleNode(Transform, null, pen, rect, cornerRadius, CreateChildScene(pen.Brush)))); Add(new RectangleNode(Transform, null, pen, rect, cornerRadius, CreateChildScene(pen.Brush)));
} }
else else
{ {
@ -172,7 +172,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(Transform, foreground, origin, text)) if (next == null || !next.Item.Equals(Transform, foreground, origin, text))
{ {
Add(RefCountable.Create(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground)))); Add(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground)));
} }
else else
{ {
@ -187,7 +187,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(Transform, brush, null, rect, cornerRadius)) if (next == null || !next.Item.Equals(Transform, brush, null, rect, cornerRadius))
{ {
Add(RefCountable.Create(new RectangleNode(Transform, brush, null, rect, cornerRadius, CreateChildScene(brush)))); Add(new RectangleNode(Transform, brush, null, rect, cornerRadius, CreateChildScene(brush)));
} }
else else
{ {
@ -207,7 +207,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(null)) if (next == null || !next.Item.Equals(null))
{ {
Add(RefCountable.Create(new ClipNode())); Add(new ClipNode());
} }
else else
{ {
@ -222,7 +222,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(null)) if (next == null || !next.Item.Equals(null))
{ {
Add(RefCountable.Create((new GeometryClipNode()))); Add((new GeometryClipNode()));
} }
else else
{ {
@ -237,7 +237,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(null)) if (next == null || !next.Item.Equals(null))
{ {
Add(RefCountable.Create(new OpacityNode())); Add(new OpacityNode());
} }
else else
{ {
@ -252,7 +252,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(null, null)) if (next == null || !next.Item.Equals(null, null))
{ {
Add(RefCountable.Create(new OpacityMaskNode())); Add(new OpacityMaskNode());
} }
else else
{ {
@ -267,7 +267,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(clip)) if (next == null || !next.Item.Equals(clip))
{ {
Add(RefCountable.Create(new ClipNode(clip))); Add(new ClipNode(clip));
} }
else else
{ {
@ -282,7 +282,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(clip)) if (next == null || !next.Item.Equals(clip))
{ {
Add(RefCountable.Create(new GeometryClipNode(clip))); Add(new GeometryClipNode(clip));
} }
else else
{ {
@ -297,7 +297,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(opacity)) if (next == null || !next.Item.Equals(opacity))
{ {
Add(RefCountable.Create(new OpacityNode(opacity))); Add(new OpacityNode(opacity));
} }
else else
{ {
@ -312,7 +312,7 @@ namespace Avalonia.Rendering.SceneGraph
if (next == null || !next.Item.Equals(mask, bounds)) if (next == null || !next.Item.Equals(mask, bounds))
{ {
Add(RefCountable.Create(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)))); Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)));
} }
else else
{ {
@ -356,6 +356,13 @@ namespace Avalonia.Rendering.SceneGraph
public int DrawOperationIndex { get; } public int DrawOperationIndex { get; }
} }
private void Add(IDrawOperation node)
{
var refCounted = RefCountable.Create(node);
Add(refCounted);
refCounted.Dispose(); // Dispose our reference
}
private void Add(IRef<IDrawOperation> node) private void Add(IRef<IDrawOperation> node)
{ {
if (_drawOperationindex < _node.DrawOperations.Count) if (_drawOperationindex < _node.DrawOperations.Count)

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

@ -11,7 +11,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// Represents a scene graph used by the <see cref="DeferredRenderer"/>. /// Represents a scene graph used by the <see cref="DeferredRenderer"/>.
/// </summary> /// </summary>
public class Scene public class Scene : IDisposable
{ {
private Dictionary<IVisual, IVisualNode> _index; private Dictionary<IVisual, IVisualNode> _index;
@ -96,6 +96,11 @@ namespace Avalonia.Rendering.SceneGraph
return result; return result;
} }
public void Dispose()
{
Root.Dispose();
}
/// <summary> /// <summary>
/// Tries to find a node in the scene graph representing the specified visual. /// Tries to find a node in the scene graph representing the specified visual.
/// </summary> /// </summary>

17
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -271,22 +271,21 @@ namespace Avalonia.Rendering.SceneGraph
private static void Deindex(Scene scene, VisualNode node) private static void Deindex(Scene scene, VisualNode node)
{ {
scene.Remove(node);
node.SubTreeUpdated = true;
scene.Layers[node.LayerRoot].Dirty.Add(node.Bounds);
node.Visual.TransformedBounds = null;
foreach (VisualNode child in node.Children) foreach (VisualNode child in node.Children)
{ {
var geometry = child as IDrawOperation;
if (child is VisualNode visual) if (child is VisualNode visual)
{ {
Deindex(scene, visual); Deindex(scene, visual);
} }
} }
scene.Remove(node);
node.SubTreeUpdated = true;
scene.Layers[node.LayerRoot].Dirty.Add(node.Bounds);
node.Visual.TransformedBounds = null;
if (node.LayerRoot == node.Visual && node.Visual != scene.Root.Visual) if (node.LayerRoot == node.Visual && node.Visual != scene.Root.Visual)
{ {

23
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -192,5 +193,27 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Assert.Equal(2, node.DrawOperations.Count); Assert.Equal(2, node.DrawOperations.Count);
} }
[Fact]
public void Trimmed_DrawOperations_Releases_Reference()
{
var node = new VisualNode(new TestRoot(), null);
var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0));
var layers = new SceneLayers(node.Visual);
var target = new DeferredDrawingContextImpl(null, layers);
node.LayerRoot = node.Visual;
node.AddDrawOperation(operation);
Assert.Equal(2, operation.RefCount);
using (target.BeginUpdate(node))
{
target.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100));
}
Assert.Equal(1, node.DrawOperations.Count);
Assert.NotSame(operation, node.DrawOperations.Single());
Assert.Equal(1, operation.RefCount);
}
} }
} }

15
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

@ -2,6 +2,8 @@
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
@ -40,6 +42,19 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds); Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds);
} }
[Fact]
public void Image_Node_Releases_Reference_To_Bitmap_On_Dispose()
{
var bitmap = RefCountable.Create(Mock.Of<IBitmapImpl>());
var imageNode = new ImageNode(Matrix.Identity, bitmap, 1, new Rect(1,1,1,1), new Rect(1,1,1,1));
Assert.Equal(2, bitmap.RefCount);
imageNode.Dispose();
Assert.Equal(1, bitmap.RefCount);
}
private class TestDrawOperation : DrawOperation private class TestDrawOperation : DrawOperation
{ {
public TestDrawOperation(Rect bounds, Matrix transform, Pen pen) public TestDrawOperation(Rect bounds, Matrix transform, Pen pen)

68
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@ -11,6 +11,8 @@ using Moq;
using Avalonia.Platform; using Avalonia.Platform;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{ {
@ -678,6 +680,72 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
} }
} }
[Fact]
public void Disposing_Scene_Releases_DrawOperation_References()
{
using (TestApplication())
{
var bitmap = RefCountable.Create(Mock.Of<IBitmapImpl>());
Image img;
var tree = new TestRoot
{
Child = img = new Image
{
Source = new Bitmap(bitmap)
}
};
Assert.Equal(2, bitmap.RefCount);
IRef<IDrawOperation> operation;
using (var scene = new Scene(tree))
{
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
operation = scene.FindNode(img).DrawOperations[0];
Assert.Equal(1, operation.RefCount);
Assert.Equal(3, bitmap.RefCount);
}
Assert.Equal(0, operation.RefCount);
Assert.Equal(2, bitmap.RefCount);
}
}
[Fact]
public void Replacing_Control_Releases_DrawOperation_Reference()
{
using (TestApplication())
{
var bitmap = RefCountable.Create(Mock.Of<IBitmapImpl>());
Image img;
var tree = new TestRoot
{
Child = img = new Image
{
Source = new Bitmap(bitmap)
}
};
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
var operation = scene.FindNode(img).DrawOperations[0];
tree.Child = new Decorator();
using (var result = scene.Clone())
{
sceneBuilder.Update(result, img);
scene.Dispose();
Assert.Equal(0, operation.RefCount);
Assert.Equal(2, bitmap.RefCount);
}
}
}
private IDisposable TestApplication() private IDisposable TestApplication()
{ {
return UnitTestApplication.Start( return UnitTestApplication.Start(

Loading…
Cancel
Save