From 617f35ce53ad0d0ded0d23d831c129828c707a4b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 28 Jan 2018 02:12:14 -0600 Subject: [PATCH] Add ref-counting checks throughout the SceneGraph nodes. Because IDrawOperations can be shared between IVisualNodes, they are now also ref-counted. --- src/Avalonia.Base/Utilities/Ref.cs | 14 ++-- .../Media/Imaging/RenderTargetBitmap.cs | 9 --- .../Rendering/DeferredRenderer.cs | 8 +-- .../Rendering/SceneGraph/ClipNode.cs | 4 ++ .../SceneGraph/DeferredDrawingContextImpl.cs | 66 +++++++++---------- .../Rendering/SceneGraph/DrawOperation.cs | 4 ++ .../Rendering/SceneGraph/GeometryClipNode.cs | 3 + .../Rendering/SceneGraph/IDrawOperation.cs | 2 +- .../Rendering/SceneGraph/IVisualNode.cs | 5 +- .../Rendering/SceneGraph/ImageNode.cs | 4 +- .../Rendering/SceneGraph/OpacityNode.cs | 4 ++ .../Rendering/SceneGraph/VisualNode.cs | 55 ++++++++++++---- .../Media/DrawingContextImpl.cs | 2 +- 13 files changed, 113 insertions(+), 67 deletions(-) diff --git a/src/Avalonia.Base/Utilities/Ref.cs b/src/Avalonia.Base/Utilities/Ref.cs index 98077cd4fd..f9e8b29b95 100644 --- a/src/Avalonia.Base/Utilities/Ref.cs +++ b/src/Avalonia.Base/Utilities/Ref.cs @@ -50,6 +50,7 @@ namespace Avalonia.Utilities public RefCounter(IDisposable item) { _item = item; + _refs = 1; } public void AddRef() @@ -101,8 +102,6 @@ namespace Avalonia.Utilities { _item = item; _counter = counter; - Interlocked.MemoryBarrier(); - _counter.AddRef(); } public void Dispose() @@ -139,7 +138,11 @@ namespace Avalonia.Utilities lock (_lock) { if (_item != null) - return new Ref(_item, _counter); + { + var newRef = new Ref(_item, _counter); + _counter.AddRef(); + return newRef; + } throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); } } @@ -150,7 +153,10 @@ namespace Avalonia.Utilities { if (_item != null) { - return new Ref((TResult)(object)_item, _counter); + var castRef = new Ref((TResult)(object)_item, _counter); + Interlocked.MemoryBarrier(); + _counter.AddRef(); + return castRef; } throw new ObjectDisposedException("Ref<" + typeof(T) + ">"); } diff --git a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs index 3968c05efb..52bd8a3c04 100644 --- a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs @@ -37,15 +37,6 @@ namespace Avalonia.Media.Imaging /// public new IRef PlatformImpl { get; } - /// - /// Disposes of the bitmap. - /// - public void Dispose() - { - PlatformImpl.Dispose(); - base.Dispose(); - } - /// /// Renders a visual to the . /// diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 850c0a607f..989e2eacdf 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -35,7 +35,7 @@ namespace Avalonia.Rendering private object _rendering = new object(); private int _lastSceneId = -1; private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); - private IDrawOperation _currentDraw; + private IRef _currentDraw; /// /// Initializes a new instance of the class. @@ -159,13 +159,13 @@ namespace Avalonia.Rendering /// Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) { - return (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty; + return (_currentDraw as IRef)?.Item.ChildScenes?[brush.Visual]?.Size ?? Size.Empty; } /// void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) { - var childScene = (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual]; + var childScene = (_currentDraw as IRef)?.Item.ChildScenes?[brush.Visual]; if (childScene != null) { @@ -253,7 +253,7 @@ namespace Avalonia.Rendering foreach (var operation in node.DrawOperations) { _currentDraw = operation; - operation.Render(context); + operation.Item.Render(context); _currentDraw = null; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs index 1cda1d5461..14e6b4ab63 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs @@ -60,5 +60,9 @@ namespace Avalonia.Rendering.SceneGraph context.PopClip(); } } + + public void Dispose() + { + } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 38a10a7b7f..d6ab4bd5cb 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -81,7 +81,7 @@ namespace Avalonia.Rendering.SceneGraph /// public void Dispose() { - // Nothing to do here as we allocate no unmanaged resources. + _node?.Dispose(); } /// @@ -103,9 +103,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(Transform, brush, pen, geometry)) + if (next == null || !next.Item.Equals(Transform, brush, pen, geometry)) { - Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush))); + Add(RefCountable.Create(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)))); } else { @@ -118,9 +118,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(Transform, source, opacity, sourceRect, destRect)) + if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect)) { - Add(new ImageNode(Transform, source, opacity, sourceRect, destRect)); + Add(RefCountable.Create(new ImageNode(Transform, source, opacity, sourceRect, destRect))); } else { @@ -140,9 +140,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(Transform, pen, p1, p2)) + if (next == null || !next.Item.Equals(Transform, pen, p1, p2)) { - Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush))); + Add(RefCountable.Create(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)))); } else { @@ -155,9 +155,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(Transform, null, pen, rect, cornerRadius)) + if (next == null || !next.Item.Equals(Transform, null, pen, rect, cornerRadius)) { - Add(new RectangleNode(Transform, null, pen, rect, cornerRadius, CreateChildScene(pen.Brush))); + Add(RefCountable.Create(new RectangleNode(Transform, null, pen, rect, cornerRadius, CreateChildScene(pen.Brush)))); } else { @@ -170,9 +170,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(Transform, foreground, origin, text)) + if (next == null || !next.Item.Equals(Transform, foreground, origin, text)) { - Add(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground))); + Add(RefCountable.Create(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground)))); } else { @@ -185,9 +185,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(Transform, brush, null, rect, cornerRadius)) + if (next == null || !next.Item.Equals(Transform, brush, null, rect, cornerRadius)) { - Add(new RectangleNode(Transform, brush, null, rect, cornerRadius, CreateChildScene(brush))); + Add(RefCountable.Create(new RectangleNode(Transform, brush, null, rect, cornerRadius, CreateChildScene(brush)))); } else { @@ -205,9 +205,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(null)) + if (next == null || !next.Item.Equals(null)) { - Add(new ClipNode()); + Add(RefCountable.Create(new ClipNode())); } else { @@ -220,9 +220,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(null)) + if (next == null || !next.Item.Equals(null)) { - Add(new GeometryClipNode()); + Add(RefCountable.Create((new GeometryClipNode()))); } else { @@ -235,9 +235,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(null)) + if (next == null || !next.Item.Equals(null)) { - Add(new OpacityNode()); + Add(RefCountable.Create(new OpacityNode())); } else { @@ -250,9 +250,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(null, null)) + if (next == null || !next.Item.Equals(null, null)) { - Add(new OpacityMaskNode()); + Add(RefCountable.Create(new OpacityMaskNode())); } else { @@ -265,9 +265,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(clip)) + if (next == null || !next.Item.Equals(clip)) { - Add(new ClipNode(clip)); + Add(RefCountable.Create(new ClipNode(clip))); } else { @@ -280,9 +280,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(clip)) + if (next == null || !next.Item.Equals(clip)) { - Add(new GeometryClipNode(clip)); + Add(RefCountable.Create(new GeometryClipNode(clip))); } else { @@ -295,9 +295,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(opacity)) + if (next == null || !next.Item.Equals(opacity)) { - Add(new OpacityNode(opacity)); + Add(RefCountable.Create(new OpacityNode(opacity))); } else { @@ -310,9 +310,9 @@ namespace Avalonia.Rendering.SceneGraph { var next = NextDrawAs(); - if (next == null || !next.Equals(mask, bounds)) + if (next == null || !next.Item.Equals(mask, bounds)) { - Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask))); + Add(RefCountable.Create(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)))); } else { @@ -342,7 +342,7 @@ namespace Avalonia.Rendering.SceneGraph foreach (var operation in Owner._node.DrawOperations) { - dirty.Add(operation.Bounds); + dirty.Add(operation.Item.Bounds); } Owner._node = Node; @@ -356,7 +356,7 @@ namespace Avalonia.Rendering.SceneGraph public int DrawOperationIndex { get; } } - private void Add(IDrawOperation node) + private void Add(IRef node) { if (_drawOperationindex < _node.DrawOperations.Count) { @@ -370,9 +370,9 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } - private T NextDrawAs() where T : class, IDrawOperation + private IRef NextDrawAs() where T : class, IDrawOperation { - return _drawOperationindex < _node.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as T : null; + return _drawOperationindex < _node.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as IRef : null; } private IDictionary CreateChildScene(IBrush brush) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs index 4c6ed189ff..1a5a6fad3f 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs @@ -22,5 +22,9 @@ namespace Avalonia.Rendering.SceneGraph public abstract bool HitTest(Point p); public abstract void Render(IDrawingContextImpl context); + + public virtual void Dispose() + { + } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs index a11d641151..2c1decf5dc 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs @@ -60,5 +60,8 @@ namespace Avalonia.Rendering.SceneGraph context.PopGeometryClip(); } } + public void Dispose() + { + } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs index 839fd9b0e5..f8d5bf5b7a 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs @@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Represents a node in the low-level scene graph that represents geometry. /// - public interface IDrawOperation + public interface IDrawOperation : IDisposable { /// /// Gets the bounds of the visible content in the node in global coordinates. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs index 234cadbf31..681f00799b 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph @@ -12,7 +13,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Represents a node in the low-level scene graph representing an . /// - public interface IVisualNode + public interface IVisualNode : IDisposable { /// /// Gets the visual to which the node relates. @@ -66,7 +67,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the drawing operations for the visual. /// - IReadOnlyList DrawOperations { get; } + IReadOnlyList> DrawOperations { get; } /// /// Sets up the drawing context for rendering the node's geometry. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index 8a1a5cb481..06fdb3f86c 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -10,7 +10,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// A node in the scene graph which represents an image draw. /// - internal class ImageNode : DrawOperation, IDisposable + internal class ImageNode : DrawOperation { /// /// Initializes a new instance of the class. @@ -89,7 +89,7 @@ namespace Avalonia.Rendering.SceneGraph /// public override bool HitTest(Point p) => Bounds.Contains(p); - public void Dispose() + public override void Dispose() { Source?.Dispose(); } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityNode.cs index 097bf15828..ef49ac8879 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityNode.cs @@ -60,5 +60,9 @@ namespace Avalonia.Rendering.SceneGraph context.PopOpacity(); } } + + public void Dispose() + { + } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index 6bea4d9bd6..ed8a2960dc 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Linq; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph @@ -15,12 +17,12 @@ namespace Avalonia.Rendering.SceneGraph internal class VisualNode : IVisualNode { private static readonly IReadOnlyList EmptyChildren = new IVisualNode[0]; - private static readonly IReadOnlyList EmptyDrawOperations = new IDrawOperation[0]; + private static readonly IReadOnlyList> EmptyDrawOperations = new IRef[0]; private Rect? _bounds; private double _opacity; private List _children; - private List _drawOperations; + private List> _drawOperations; private bool _drawOperationsCloned; private Matrix transformRestore; @@ -101,7 +103,7 @@ namespace Avalonia.Rendering.SceneGraph public IReadOnlyList Children => _children ?? EmptyChildren; /// - public IReadOnlyList DrawOperations => _drawOperations ?? EmptyDrawOperations; + public IReadOnlyList> DrawOperations => _drawOperations ?? EmptyDrawOperations; /// /// Adds a child to the collection. @@ -117,10 +119,10 @@ namespace Avalonia.Rendering.SceneGraph /// Adds an operation to the collection. /// /// The operation to add. - public void AddDrawOperation(IDrawOperation operation) + public void AddDrawOperation(IRef operation) { EnsureDrawOperationsCreated(); - _drawOperations.Add(operation); + _drawOperations.Add(operation.Clone()); } /// @@ -131,6 +133,7 @@ namespace Avalonia.Rendering.SceneGraph { EnsureChildrenCreated(); _children.Remove(child); + child.Dispose(); } /// @@ -141,7 +144,9 @@ namespace Avalonia.Rendering.SceneGraph public void ReplaceChild(int index, IVisualNode node) { EnsureChildrenCreated(); + var old = _children[index]; _children[index] = node; + old.Dispose(); } /// @@ -149,10 +154,15 @@ namespace Avalonia.Rendering.SceneGraph /// /// The opeation to be replaced. /// The operation to add. - public void ReplaceDrawOperation(int index, IDrawOperation operation) + public void ReplaceDrawOperation(int index, IRef operation) { EnsureDrawOperationsCreated(); - _drawOperations[index] = operation; + var old = _drawOperations[index]; + _drawOperations[index] = operation.Clone(); + if (old is IDisposable disposable) + { + disposable.Dispose(); + } } /// @@ -165,6 +175,10 @@ namespace Avalonia.Rendering.SceneGraph if (first < _children?.Count) { EnsureChildrenCreated(); + for (int i = first; i < _children.Count - first; i++) + { + _children[i].Dispose(); + } _children.RemoveRange(first, _children.Count - first); } } @@ -179,6 +193,10 @@ namespace Avalonia.Rendering.SceneGraph if (first < _drawOperations?.Count) { EnsureDrawOperationsCreated(); + for (int i = first; i < _drawOperations.Count - first; i++) + { + _drawOperations[i].Dispose(); + } _drawOperations.RemoveRange(first, _drawOperations.Count - first); } } @@ -209,7 +227,7 @@ namespace Avalonia.Rendering.SceneGraph { foreach (var operation in DrawOperations) { - if (operation.HitTest(p) == true) + if (operation.Item.HitTest(p) == true) { return true; } @@ -280,7 +298,7 @@ namespace Avalonia.Rendering.SceneGraph foreach (var operation in DrawOperations) { - result = result.Union(operation.Bounds); + result = result.Union(operation.Item.Bounds); } _bounds = result; @@ -299,13 +317,28 @@ namespace Avalonia.Rendering.SceneGraph { if (_drawOperations == null) { - _drawOperations = new List(); + _drawOperations = new List>(); } else if (_drawOperationsCloned) { - _drawOperations = new List(_drawOperations); + _drawOperations = new List>(_drawOperations.Select(op => op.Clone())); _drawOperationsCloned = false; } } + + public void Dispose() + { + foreach (var child in Children) + { + child.Dispose(); + } + if (!_drawOperationsCloned) + { + foreach (var operation in DrawOperations) + { + operation.Dispose(); + } + } + } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index a6c3e97a85..ec21741100 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -399,7 +399,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( imageBrush, _renderTarget, - (BitmapImpl)imageBrush.Source.PlatformImpl, + (BitmapImpl)imageBrush.Source.PlatformImpl.Item, destinationSize); } else if (visualBrush != null)