Browse Source

Merge pull request #1277 from AvaloniaUI/ref-bitmaps

Ref-counting infrastructure for bitmaps
pull/1363/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
fe317ea61e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 209
      src/Avalonia.Base/Utilities/Ref.cs
  2. 2
      src/Avalonia.Controls/WindowIcon.cs
  3. 41
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  4. 6
      src/Avalonia.Visuals/Media/Imaging/IBitmap.cs
  5. 23
      src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
  6. 5
      src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs
  7. 3
      src/Avalonia.Visuals/Platform/IBitmapImpl.cs
  8. 5
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  9. 42
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  10. 9
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  11. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
  12. 53
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  13. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
  14. 3
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs
  15. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs
  16. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  17. 16
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  18. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityNode.cs
  19. 7
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  20. 17
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  21. 61
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  22. 13
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  23. 11
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  24. 4
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  25. 2
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  26. 58
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
  27. 15
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  28. 80
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  29. 28
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs

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

@ -0,0 +1,209 @@
using System;
using System.Runtime.ConstrainedExecution;
using System.Threading;
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
{
/// <summary>
/// The item that is being ref-counted.
/// </summary>
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();
/// <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;
/// <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
{
/// <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
{
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
=> new TempRef<T>(item);
class TempRef<T> : IRef<T> where T : class
{
public void Dispose()
{
}
public TempRef(T item)
{
Item = item;
}
public T Item { get; }
public IRef<T> Clone() => throw new NotSupportedException();
public IRef<TResult> CloneAs<TResult>() where TResult : class
=> throw new NotSupportedException();
public int RefCount => 1;
}
class RefCounter
{
private IDisposable _item;
private volatile int _refs;
public RefCounter(IDisposable item)
{
_item = item;
_refs = 1;
}
public void AddRef()
{
var old = _refs;
while (true)
{
if (old == 0)
{
throw new ObjectDisposedException("Cannot add a reference to a nonreferenced item");
}
var current = Interlocked.CompareExchange(ref _refs, old + 1, old);
if (current == old)
{
break;
}
old = current;
}
}
public void Release()
{
var old = _refs;
while (true)
{
var current = Interlocked.CompareExchange(ref _refs, old - 1, old);
if (current == old)
{
if (old == 1)
{
_item.Dispose();
_item = null;
}
break;
}
old = current;
}
}
internal int RefCount => _refs;
}
class Ref<T> : CriticalFinalizerObject, IRef<T> where T : class
{
private T _item;
private RefCounter _counter;
private object _lock = new object();
public Ref(T item, RefCounter counter)
{
_item = item;
_counter = counter;
}
public void Dispose()
{
lock (_lock)
{
if (_item != null)
{
_counter.Release();
_item = null;
}
GC.SuppressFinalize(this);
}
}
~Ref()
{
_counter?.Release();
}
public T Item
{
get
{
lock (_lock)
{
return _item;
}
}
}
public IRef<T> Clone()
{
lock (_lock)
{
if (_item != null)
{
var newRef = new Ref<T>(_item, _counter);
_counter.AddRef();
return newRef;
}
throw new ObjectDisposedException("Ref<" + typeof(T) + ">");
}
}
public IRef<TResult> CloneAs<TResult>() where TResult : class
{
lock (_lock)
{
if (_item != null)
{
var castRef = new Ref<TResult>((TResult)(object)_item, _counter);
Interlocked.MemoryBarrier();
_counter.AddRef();
return castRef;
}
throw new ObjectDisposedException("Ref<" + typeof(T) + ">");
}
}
public int RefCount => _counter.RefCount;
}
}
}

2
src/Avalonia.Controls/WindowIcon.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls
{ {
public WindowIcon(IBitmap bitmap) public WindowIcon(IBitmap bitmap)
{ {
PlatformImpl = AvaloniaLocator.Current.GetService<IPlatformIconLoader>().LoadIcon(bitmap.PlatformImpl); PlatformImpl = AvaloniaLocator.Current.GetService<IPlatformIconLoader>().LoadIcon(bitmap.PlatformImpl.Item);
} }
public WindowIcon(string fileName) public WindowIcon(string fileName)

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

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media.Imaging namespace Avalonia.Media.Imaging
{ {
@ -19,7 +20,7 @@ namespace Avalonia.Media.Imaging
public Bitmap(string fileName) public Bitmap(string fileName)
{ {
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
PlatformImpl = factory.LoadBitmap(fileName); PlatformImpl = RefCountable.Create(factory.LoadBitmap(fileName));
} }
/// <summary> /// <summary>
@ -29,18 +30,33 @@ namespace Avalonia.Media.Imaging
public Bitmap(Stream stream) public Bitmap(Stream stream)
{ {
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
PlatformImpl = factory.LoadBitmap(stream); PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream));
} }
/// <summary> /// <summary>
/// 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>
public Bitmap(IRef<IBitmapImpl> impl)
{
PlatformImpl = impl.Clone();
}
/// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary>
/// <param name="impl">A platform-specific bitmap implementation. Bitmap class takes the ownership.</param>
protected Bitmap(IBitmapImpl impl) protected Bitmap(IBitmapImpl impl)
{ {
PlatformImpl = impl; PlatformImpl = RefCountable.Create(impl);
} }
/// <inheritdoc/>
public virtual void Dispose()
{
PlatformImpl.Dispose();
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class. /// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary> /// </summary>
@ -51,27 +67,24 @@ namespace Avalonia.Media.Imaging
/// <param name="stride">Bytes per row</param> /// <param name="stride">Bytes per row</param>
public Bitmap(PixelFormat format, IntPtr data, int width, int height, int stride) public Bitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{ {
PlatformImpl = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() PlatformImpl = RefCountable.Create(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
.LoadBitmap(format, data, width, height, stride); .LoadBitmap(format, data, width, height, stride));
} }
/// <summary> /// <summary>
/// Gets the width of the bitmap, in pixels. /// Gets the width of the bitmap, in pixels.
/// </summary> /// </summary>
public int PixelWidth => PlatformImpl.PixelWidth; public int PixelWidth => PlatformImpl.Item.PixelWidth;
/// <summary> /// <summary>
/// Gets the height of the bitmap, in pixels. /// Gets the height of the bitmap, in pixels.
/// </summary> /// </summary>
public int PixelHeight => PlatformImpl.PixelHeight; public int PixelHeight => PlatformImpl.Item.PixelHeight;
/// <summary> /// <summary>
/// Gets the platform-specific bitmap implementation. /// Gets the platform-specific bitmap implementation.
/// </summary> /// </summary>
public IBitmapImpl PlatformImpl public IRef<IBitmapImpl> PlatformImpl { get; }
{
get;
}
/// <summary> /// <summary>
/// Saves the bitmap to a file. /// Saves the bitmap to a file.
@ -79,12 +92,12 @@ namespace Avalonia.Media.Imaging
/// <param name="fileName">The filename.</param> /// <param name="fileName">The filename.</param>
public void Save(string fileName) public void Save(string fileName)
{ {
PlatformImpl.Save(fileName); PlatformImpl.Item.Save(fileName);
} }
public void Save(Stream stream) public void Save(Stream stream)
{ {
PlatformImpl.Save(stream); PlatformImpl.Item.Save(stream);
} }
} }
} }

6
src/Avalonia.Visuals/Media/Imaging/IBitmap.cs

@ -1,15 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.IO; using System.IO;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media.Imaging namespace Avalonia.Media.Imaging
{ {
/// <summary> /// <summary>
/// Represents a bitmap image. /// Represents a bitmap image.
/// </summary> /// </summary>
public interface IBitmap public interface IBitmap : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the width of the bitmap, in pixels. /// Gets the width of the bitmap, in pixels.
@ -24,7 +26,7 @@ namespace Avalonia.Media.Imaging
/// <summary> /// <summary>
/// Gets the platform-specific bitmap implementation. /// Gets the platform-specific bitmap implementation.
/// </summary> /// </summary>
IBitmapImpl PlatformImpl { get; } IRef<IBitmapImpl> PlatformImpl { get; }
/// <summary> /// <summary>
/// Saves the bitmap to a file. /// Saves the bitmap to a file.

23
src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs

@ -2,8 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Runtime.CompilerServices;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Media.Imaging namespace Avalonia.Media.Imaging
@ -21,22 +23,19 @@ namespace Avalonia.Media.Imaging
/// <param name="dpiX">The horizontal DPI of the bitmap.</param> /// <param name="dpiX">The horizontal DPI of the bitmap.</param>
/// <param name="dpiY">The vertical DPI of the bitmap.</param> /// <param name="dpiY">The vertical DPI of the bitmap.</param>
public RenderTargetBitmap(int pixelWidth, int pixelHeight, double dpiX = 96, double dpiY = 96) public RenderTargetBitmap(int pixelWidth, int pixelHeight, double dpiX = 96, double dpiY = 96)
: base(CreateImpl(pixelWidth, pixelHeight, dpiX, dpiY)) : this(RefCountable.Create(CreateImpl(pixelWidth, pixelHeight, dpiX, dpiY)))
{ {
} }
/// <summary> private RenderTargetBitmap(IRef<IRenderTargetBitmapImpl> impl) : base(impl)
/// Gets the platform-specific bitmap implementation. {
/// </summary> PlatformImpl = impl;
public new IRenderTargetBitmapImpl PlatformImpl => (IRenderTargetBitmapImpl)base.PlatformImpl; }
/// <summary> /// <summary>
/// Disposes of the bitmap. /// Gets the platform-specific bitmap implementation.
/// </summary> /// </summary>
public void Dispose() public new IRef<IRenderTargetBitmapImpl> PlatformImpl { get; }
{
PlatformImpl.Dispose();
}
/// <summary> /// <summary>
/// Renders a visual to the <see cref="RenderTargetBitmap"/>. /// Renders a visual to the <see cref="RenderTargetBitmap"/>.
@ -52,13 +51,13 @@ namespace Avalonia.Media.Imaging
/// <param name="dpiX">The horizontal DPI of the bitmap.</param> /// <param name="dpiX">The horizontal DPI of the bitmap.</param>
/// <param name="dpiY">The vertical DPI of the bitmap.</param> /// <param name="dpiY">The vertical DPI of the bitmap.</param>
/// <returns>The platform-specific implementation.</returns> /// <returns>The platform-specific implementation.</returns>
private static IBitmapImpl CreateImpl(int width, int height, double dpiX, double dpiY) private static IRenderTargetBitmapImpl CreateImpl(int width, int height, double dpiX, double dpiY)
{ {
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return factory.CreateRenderTargetBitmap(width, height, dpiX, dpiY); return factory.CreateRenderTargetBitmap(width, height, dpiX, dpiY);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer vbr) => PlatformImpl.CreateDrawingContext(vbr); public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer vbr) => PlatformImpl.Item.CreateDrawingContext(vbr);
} }
} }

5
src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media.Imaging namespace Avalonia.Media.Imaging
{ {
@ -16,7 +17,7 @@ namespace Avalonia.Media.Imaging
: base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWritableBitmap(width, height, format)) : base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWritableBitmap(width, height, format))
{ {
} }
public ILockedFramebuffer Lock() => ((IWritableBitmapImpl) PlatformImpl).Lock(); public ILockedFramebuffer Lock() => ((IWritableBitmapImpl) PlatformImpl.Item).Lock();
} }
} }

3
src/Avalonia.Visuals/Platform/IBitmapImpl.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.IO; using System.IO;
namespace Avalonia.Platform namespace Avalonia.Platform
@ -8,7 +9,7 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.Bitmap"/>. /// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.Bitmap"/>.
/// </summary> /// </summary>
public interface IBitmapImpl public interface IBitmapImpl : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the width of the bitmap, in pixels. /// Gets the width of the bitmap, in pixels.

5
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -3,6 +3,7 @@
using System; using System;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -29,7 +30,7 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity to draw with.</param> /// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</param> /// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect); void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect);
/// <summary> /// <summary>
/// Draws a bitmap image. /// Draws a bitmap image.
@ -38,7 +39,7 @@ namespace Avalonia.Platform
/// <param name="opacityMask">The opacity mask to draw with.</param> /// <param name="opacityMask">The opacity mask to draw with.</param>
/// <param name="opacityMaskRect">The destination rect for the opacity mask.</param> /// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect); void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
/// <summary> /// <summary>
/// Draws a line. /// Draws a line.

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

@ -12,6 +12,7 @@ using System.IO;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using System.Threading; using System.Threading;
using System.Linq; using System.Linq;
using Avalonia.Utilities;
namespace Avalonia.Rendering namespace Avalonia.Rendering
{ {
@ -29,12 +30,12 @@ namespace Avalonia.Rendering
private bool _running; private bool _running;
private Scene _scene; private Scene _scene;
private DirtyVisuals _dirty; private DirtyVisuals _dirty;
private IRenderTargetBitmapImpl _overlay; private IRef<IRenderTargetBitmapImpl> _overlay;
private bool _updateQueued; private bool _updateQueued;
private object _rendering = new object(); private object _rendering = new object();
private int _lastSceneId = -1; private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IDrawOperation _currentDraw; private IRef<IDrawOperation> _currentDraw;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class. /// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
@ -111,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)
@ -158,13 +164,13 @@ namespace Avalonia.Rendering
/// <inheritdoc/> /// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{ {
return (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty; return (_currentDraw.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty;
} }
/// <inheritdoc/> /// <inheritdoc/>
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{ {
var childScene = (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual]; var childScene = (_currentDraw.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual];
if (childScene != null) if (childScene != null)
{ {
@ -252,7 +258,7 @@ namespace Avalonia.Rendering
foreach (var operation in node.DrawOperations) foreach (var operation in node.DrawOperations)
{ {
_currentDraw = operation; _currentDraw = operation;
operation.Render(context); operation.Item.Render(context);
_currentDraw = null; _currentDraw = null;
} }
@ -277,7 +283,7 @@ namespace Avalonia.Rendering
if (node != null) if (node != null)
{ {
using (var context = renderTarget.CreateDrawingContext(this)) using (var context = renderTarget.Item.CreateDrawingContext(this))
{ {
foreach (var rect in layer.Dirty) foreach (var rect in layer.Dirty)
{ {
@ -304,7 +310,7 @@ namespace Avalonia.Rendering
{ {
var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling); var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
using (var context = overlay.CreateDrawingContext(this)) using (var context = overlay.Item.CreateDrawingContext(this))
{ {
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
RenderDirtyRects(context); RenderDirtyRects(context);
@ -333,7 +339,7 @@ namespace Avalonia.Rendering
foreach (var layer in scene.Layers) foreach (var layer in scene.Layers)
{ {
var bitmap = Layers[layer.LayerRoot].Bitmap; var bitmap = Layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); var sourceRect = new Rect(0, 0, bitmap.Item.PixelWidth, bitmap.Item.PixelHeight);
if (layer.GeometryClip != null) if (layer.GeometryClip != null)
{ {
@ -357,7 +363,7 @@ namespace Avalonia.Rendering
if (_overlay != null) if (_overlay != null)
{ {
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight); var sourceRect = new Rect(0, 0, _overlay.Item.PixelWidth, _overlay.Item.PixelHeight);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect); context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
} }
@ -390,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
@ -430,7 +438,7 @@ namespace Avalonia.Rendering
} }
} }
private IRenderTargetBitmapImpl GetOverlay( private IRef<IRenderTargetBitmapImpl> GetOverlay(
IDrawingContextImpl parentContext, IDrawingContextImpl parentContext,
Size size, Size size,
double scaling) double scaling)
@ -438,11 +446,11 @@ namespace Avalonia.Rendering
var pixelSize = size * scaling; var pixelSize = size * scaling;
if (_overlay == null || if (_overlay == null ||
_overlay.PixelWidth != pixelSize.Width || _overlay.Item.PixelWidth != pixelSize.Width ||
_overlay.PixelHeight != pixelSize.Height) _overlay.Item.PixelHeight != pixelSize.Height)
{ {
_overlay?.Dispose(); _overlay?.Dispose();
_overlay = parentContext.CreateLayer(size); _overlay = RefCountable.Create(parentContext.CreateLayer(size));
} }
return _overlay; return _overlay;
@ -455,7 +463,7 @@ namespace Avalonia.Rendering
foreach (var layer in Layers) foreach (var layer in Layers)
{ {
var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png"); var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
layer.Bitmap.Save(fileName); layer.Bitmap.Item.Save(fileName);
} }
} }
} }

9
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -1,6 +1,7 @@
using System; using System;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Rendering namespace Avalonia.Rendering
@ -16,13 +17,13 @@ namespace Avalonia.Rendering
IVisual layerRoot) IVisual layerRoot)
{ {
_drawingContext = drawingContext; _drawingContext = drawingContext;
Bitmap = drawingContext.CreateLayer(size); Bitmap = RefCountable.Create(drawingContext.CreateLayer(size));
Size = size; Size = size;
Scaling = scaling; Scaling = scaling;
LayerRoot = layerRoot; LayerRoot = layerRoot;
} }
public IRenderTargetBitmapImpl Bitmap { get; private set; } public IRef<IRenderTargetBitmapImpl> Bitmap { get; private set; }
public double Scaling { get; private set; } public double Scaling { get; private set; }
public Size Size { get; private set; } public Size Size { get; private set; }
public IVisual LayerRoot { get; } public IVisual LayerRoot { get; }
@ -31,9 +32,9 @@ namespace Avalonia.Rendering
{ {
if (Size != size || Scaling != scaling) if (Size != size || Scaling != scaling)
{ {
var resized = _drawingContext.CreateLayer(size); var resized = RefCountable.Create(_drawingContext.CreateLayer(size));
using (var context = resized.CreateDrawingContext(null)) using (var context = resized.Item.CreateDrawingContext(null))
{ {
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
context.DrawImage(Bitmap, 1, new Rect(Size), new Rect(Size)); context.DrawImage(Bitmap, 1, new Rect(Size), new Rect(Size));

4
src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs

@ -60,5 +60,9 @@ namespace Avalonia.Rendering.SceneGraph
context.PopClip(); context.PopClip();
} }
} }
public void Dispose()
{
}
} }
} }

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

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph namespace Avalonia.Rendering.SceneGraph
@ -80,7 +81,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
// Nothing to do here as we allocate no unmanaged resources. // Nothing to do here since we allocate no unmanaged resources.
} }
/// <summary> /// <summary>
@ -102,7 +103,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<GeometryNode>(); var next = NextDrawAs<GeometryNode>();
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(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
} }
@ -113,11 +114,11 @@ namespace Avalonia.Rendering.SceneGraph
} }
/// <inheritdoc/> /// <inheritdoc/>
public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{ {
var next = NextDrawAs<ImageNode>(); var next = NextDrawAs<ImageNode>();
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(new ImageNode(Transform, source, opacity, sourceRect, destRect));
} }
@ -128,7 +129,7 @@ namespace Avalonia.Rendering.SceneGraph
} }
/// <inheritdoc/> /// <inheritdoc/>
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect) public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
{ {
// This method is currently only used to composite layers so shouldn't be called here. // This method is currently only used to composite layers so shouldn't be called here.
throw new NotSupportedException(); throw new NotSupportedException();
@ -139,7 +140,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<LineNode>(); var next = NextDrawAs<LineNode>();
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(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)));
} }
@ -154,7 +155,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<RectangleNode>(); var next = NextDrawAs<RectangleNode>();
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(new RectangleNode(Transform, null, pen, rect, cornerRadius, CreateChildScene(pen.Brush)));
} }
@ -169,7 +170,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<TextNode>(); var next = NextDrawAs<TextNode>();
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(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground)));
} }
@ -184,7 +185,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<RectangleNode>(); var next = NextDrawAs<RectangleNode>();
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(new RectangleNode(Transform, brush, null, rect, cornerRadius, CreateChildScene(brush)));
} }
@ -204,7 +205,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<ClipNode>(); var next = NextDrawAs<ClipNode>();
if (next == null || !next.Equals(null)) if (next == null || !next.Item.Equals(null))
{ {
Add(new ClipNode()); Add(new ClipNode());
} }
@ -219,9 +220,9 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<GeometryClipNode>(); var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Equals(null)) if (next == null || !next.Item.Equals(null))
{ {
Add(new GeometryClipNode()); Add((new GeometryClipNode()));
} }
else else
{ {
@ -234,7 +235,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<OpacityNode>(); var next = NextDrawAs<OpacityNode>();
if (next == null || !next.Equals(null)) if (next == null || !next.Item.Equals(null))
{ {
Add(new OpacityNode()); Add(new OpacityNode());
} }
@ -249,7 +250,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<OpacityMaskNode>(); var next = NextDrawAs<OpacityMaskNode>();
if (next == null || !next.Equals(null, null)) if (next == null || !next.Item.Equals(null, null))
{ {
Add(new OpacityMaskNode()); Add(new OpacityMaskNode());
} }
@ -264,7 +265,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<ClipNode>(); var next = NextDrawAs<ClipNode>();
if (next == null || !next.Equals(clip)) if (next == null || !next.Item.Equals(clip))
{ {
Add(new ClipNode(clip)); Add(new ClipNode(clip));
} }
@ -279,7 +280,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<GeometryClipNode>(); var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Equals(clip)) if (next == null || !next.Item.Equals(clip))
{ {
Add(new GeometryClipNode(clip)); Add(new GeometryClipNode(clip));
} }
@ -294,7 +295,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<OpacityNode>(); var next = NextDrawAs<OpacityNode>();
if (next == null || !next.Equals(opacity)) if (next == null || !next.Item.Equals(opacity))
{ {
Add(new OpacityNode(opacity)); Add(new OpacityNode(opacity));
} }
@ -309,7 +310,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
var next = NextDrawAs<OpacityMaskNode>(); var next = NextDrawAs<OpacityMaskNode>();
if (next == null || !next.Equals(mask, bounds)) if (next == null || !next.Item.Equals(mask, bounds))
{ {
Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask))); Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)));
} }
@ -341,7 +342,7 @@ namespace Avalonia.Rendering.SceneGraph
foreach (var operation in Owner._node.DrawOperations) foreach (var operation in Owner._node.DrawOperations)
{ {
dirty.Add(operation.Bounds); dirty.Add(operation.Item.Bounds);
} }
Owner._node = Node; Owner._node = Node;
@ -355,7 +356,15 @@ namespace Avalonia.Rendering.SceneGraph
public int DrawOperationIndex { get; } public int DrawOperationIndex { get; }
} }
private void Add(IDrawOperation node) private void Add(IDrawOperation node)
{
using (var refCounted = RefCountable.Create(node))
{
Add(refCounted);
}
}
private void Add(IRef<IDrawOperation> node)
{ {
if (_drawOperationindex < _node.DrawOperations.Count) if (_drawOperationindex < _node.DrawOperations.Count)
{ {
@ -369,9 +378,9 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex; ++_drawOperationindex;
} }
private T NextDrawAs<T>() where T : class, IDrawOperation private IRef<T> NextDrawAs<T>() 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<T> : null;
} }
private IDictionary<IVisual, Scene> CreateChildScene(IBrush brush) private IDictionary<IVisual, Scene> CreateChildScene(IBrush brush)

4
src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs

@ -22,5 +22,9 @@ namespace Avalonia.Rendering.SceneGraph
public abstract bool HitTest(Point p); public abstract bool HitTest(Point p);
public abstract void Render(IDrawingContextImpl context); public abstract void Render(IDrawingContextImpl context);
public virtual void Dispose()
{
}
} }
} }

3
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs

@ -60,5 +60,8 @@ namespace Avalonia.Rendering.SceneGraph
context.PopGeometryClip(); context.PopGeometryClip();
} }
} }
public void Dispose()
{
}
} }
} }

2
src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs

@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// Represents a node in the low-level scene graph that represents geometry. /// Represents a node in the low-level scene graph that represents geometry.
/// </summary> /// </summary>
public interface IDrawOperation public interface IDrawOperation : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the bounds of the visible content in the node in global coordinates. /// Gets the bounds of the visible content in the node in global coordinates.

5
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph namespace Avalonia.Rendering.SceneGraph
@ -12,7 +13,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// Represents a node in the low-level scene graph representing an <see cref="IVisual"/>. /// Represents a node in the low-level scene graph representing an <see cref="IVisual"/>.
/// </summary> /// </summary>
public interface IVisualNode public interface IVisualNode : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the visual to which the node relates. /// Gets the visual to which the node relates.
@ -66,7 +67,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// Gets the drawing operations for the visual. /// Gets the drawing operations for the visual.
/// </summary> /// </summary>
IReadOnlyList<IDrawOperation> DrawOperations { get; } IReadOnlyList<IRef<IDrawOperation>> DrawOperations { get; }
/// <summary> /// <summary>
/// Sets up the drawing context for rendering the node's geometry. /// Sets up the drawing context for rendering the node's geometry.

16
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@ -3,6 +3,7 @@
using System; using System;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Rendering.SceneGraph namespace Avalonia.Rendering.SceneGraph
{ {
@ -19,11 +20,11 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="opacity">The draw opacity.</param> /// <param name="opacity">The draw opacity.</param>
/// <param name="sourceRect">The source rect.</param> /// <param name="sourceRect">The source rect.</param>
/// <param name="destRect">The destination rect.</param> /// <param name="destRect">The destination rect.</param>
public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) public ImageNode(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
: base(destRect, transform, null) : base(destRect, transform, null)
{ {
Transform = transform; Transform = transform;
Source = source; Source = source.Clone();
Opacity = opacity; Opacity = opacity;
SourceRect = sourceRect; SourceRect = sourceRect;
DestRect = destRect; DestRect = destRect;
@ -37,7 +38,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// Gets the image to draw. /// Gets the image to draw.
/// </summary> /// </summary>
public IBitmapImpl Source { get; } public IRef<IBitmapImpl> Source { get; }
/// <summary> /// <summary>
/// Gets the draw opacity. /// Gets the draw opacity.
@ -67,10 +68,10 @@ namespace Avalonia.Rendering.SceneGraph
/// The properties of the other draw operation are passed in as arguments to prevent /// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object. /// allocation of a not-yet-constructed draw operation object.
/// </remarks> /// </remarks>
public bool Equals(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) public bool Equals(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{ {
return transform == Transform && return transform == Transform &&
Equals(source, Source) && Equals(source.Item, Source.Item) &&
opacity == Opacity && opacity == Opacity &&
sourceRect == SourceRect && sourceRect == SourceRect &&
destRect == DestRect; destRect == DestRect;
@ -87,5 +88,10 @@ namespace Avalonia.Rendering.SceneGraph
/// <inheritdoc/> /// <inheritdoc/>
public override bool HitTest(Point p) => Bounds.Contains(p); public override bool HitTest(Point p) => Bounds.Contains(p);
public override void Dispose()
{
Source?.Dispose();
}
} }
} }

4
src/Avalonia.Visuals/Rendering/SceneGraph/OpacityNode.cs

@ -60,5 +60,9 @@ namespace Avalonia.Rendering.SceneGraph
context.PopOpacity(); context.PopOpacity();
} }
} }
public void Dispose()
{
}
} }
} }

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)
{ {

61
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -3,8 +3,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph namespace Avalonia.Rendering.SceneGraph
@ -15,12 +18,13 @@ namespace Avalonia.Rendering.SceneGraph
internal class VisualNode : IVisualNode internal class VisualNode : IVisualNode
{ {
private static readonly IReadOnlyList<IVisualNode> EmptyChildren = new IVisualNode[0]; private static readonly IReadOnlyList<IVisualNode> EmptyChildren = new IVisualNode[0];
private static readonly IReadOnlyList<IDrawOperation> EmptyDrawOperations = new IDrawOperation[0]; private static readonly IReadOnlyList<IRef<IDrawOperation>> EmptyDrawOperations = new IRef<IDrawOperation>[0];
private Rect? _bounds; private Rect? _bounds;
private double _opacity; private double _opacity;
private List<IVisualNode> _children; private List<IVisualNode> _children;
private List<IDrawOperation> _drawOperations; private List<IRef<IDrawOperation>> _drawOperations;
private IRef<IDisposable> _drawOperationsRefCounter;
private bool _drawOperationsCloned; private bool _drawOperationsCloned;
private Matrix transformRestore; private Matrix transformRestore;
@ -101,7 +105,7 @@ namespace Avalonia.Rendering.SceneGraph
public IReadOnlyList<IVisualNode> Children => _children ?? EmptyChildren; public IReadOnlyList<IVisualNode> Children => _children ?? EmptyChildren;
/// <inheritdoc/> /// <inheritdoc/>
public IReadOnlyList<IDrawOperation> DrawOperations => _drawOperations ?? EmptyDrawOperations; public IReadOnlyList<IRef<IDrawOperation>> DrawOperations => _drawOperations ?? EmptyDrawOperations;
/// <summary> /// <summary>
/// Adds a child to the <see cref="Children"/> collection. /// Adds a child to the <see cref="Children"/> collection.
@ -117,10 +121,10 @@ namespace Avalonia.Rendering.SceneGraph
/// Adds an operation to the <see cref="DrawOperations"/> collection. /// Adds an operation to the <see cref="DrawOperations"/> collection.
/// </summary> /// </summary>
/// <param name="operation">The operation to add.</param> /// <param name="operation">The operation to add.</param>
public void AddDrawOperation(IDrawOperation operation) public void AddDrawOperation(IRef<IDrawOperation> operation)
{ {
EnsureDrawOperationsCreated(); EnsureDrawOperationsCreated();
_drawOperations.Add(operation); _drawOperations.Add(operation.Clone());
} }
/// <summary> /// <summary>
@ -131,6 +135,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
EnsureChildrenCreated(); EnsureChildrenCreated();
_children.Remove(child); _children.Remove(child);
child.Dispose();
} }
/// <summary> /// <summary>
@ -141,7 +146,9 @@ namespace Avalonia.Rendering.SceneGraph
public void ReplaceChild(int index, IVisualNode node) public void ReplaceChild(int index, IVisualNode node)
{ {
EnsureChildrenCreated(); EnsureChildrenCreated();
var old = _children[index];
_children[index] = node; _children[index] = node;
old.Dispose();
} }
/// <summary> /// <summary>
@ -149,10 +156,12 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary> /// </summary>
/// <param name="index">The opeation to be replaced.</param> /// <param name="index">The opeation to be replaced.</param>
/// <param name="operation">The operation to add.</param> /// <param name="operation">The operation to add.</param>
public void ReplaceDrawOperation(int index, IDrawOperation operation) public void ReplaceDrawOperation(int index, IRef<IDrawOperation> operation)
{ {
EnsureDrawOperationsCreated(); EnsureDrawOperationsCreated();
_drawOperations[index] = operation; var old = _drawOperations[index];
_drawOperations[index] = operation.Clone();
old.Dispose();
} }
/// <summary> /// <summary>
@ -165,6 +174,10 @@ namespace Avalonia.Rendering.SceneGraph
if (first < _children?.Count) if (first < _children?.Count)
{ {
EnsureChildrenCreated(); EnsureChildrenCreated();
for (int i = first; i < _children.Count - first; i++)
{
_children[i].Dispose();
}
_children.RemoveRange(first, _children.Count - first); _children.RemoveRange(first, _children.Count - first);
} }
} }
@ -179,6 +192,10 @@ namespace Avalonia.Rendering.SceneGraph
if (first < _drawOperations?.Count) if (first < _drawOperations?.Count)
{ {
EnsureDrawOperationsCreated(); EnsureDrawOperationsCreated();
for (int i = first; i < _drawOperations.Count; i++)
{
_drawOperations[i].Dispose();
}
_drawOperations.RemoveRange(first, _drawOperations.Count - first); _drawOperations.RemoveRange(first, _drawOperations.Count - first);
} }
} }
@ -199,6 +216,7 @@ namespace Avalonia.Rendering.SceneGraph
_opacity = Opacity, _opacity = Opacity,
OpacityMask = OpacityMask, OpacityMask = OpacityMask,
_drawOperations = _drawOperations, _drawOperations = _drawOperations,
_drawOperationsRefCounter = _drawOperationsRefCounter?.Clone(),
_drawOperationsCloned = true, _drawOperationsCloned = true,
LayerRoot= LayerRoot, LayerRoot= LayerRoot,
}; };
@ -209,7 +227,7 @@ namespace Avalonia.Rendering.SceneGraph
{ {
foreach (var operation in DrawOperations) foreach (var operation in DrawOperations)
{ {
if (operation.HitTest(p) == true) if (operation.Item.HitTest(p) == true)
{ {
return true; return true;
} }
@ -280,7 +298,7 @@ namespace Avalonia.Rendering.SceneGraph
foreach (var operation in DrawOperations) foreach (var operation in DrawOperations)
{ {
result = result.Union(operation.Bounds); result = result.Union(operation.Item.Bounds);
} }
_bounds = result; _bounds = result;
@ -299,13 +317,34 @@ namespace Avalonia.Rendering.SceneGraph
{ {
if (_drawOperations == null) if (_drawOperations == null)
{ {
_drawOperations = new List<IDrawOperation>(); _drawOperations = new List<IRef<IDrawOperation>>();
_drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations));
_drawOperationsCloned = false;
} }
else if (_drawOperationsCloned) else if (_drawOperationsCloned)
{ {
_drawOperations = new List<IDrawOperation>(_drawOperations); _drawOperations = new List<IRef<IDrawOperation>>(_drawOperations.Select(op => op.Clone()));
_drawOperationsRefCounter.Dispose();
_drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations));
_drawOperationsCloned = false; _drawOperationsCloned = false;
} }
} }
public void Dispose()
{
foreach (var child in Children)
{
child.Dispose();
}
_drawOperationsRefCounter?.Dispose();
}
private void DisposeDrawOperations()
{
foreach (var operation in DrawOperations)
{
operation.Dispose();
}
}
} }
} }

13
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -6,6 +6,7 @@ using System.Linq;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Utilities; using Avalonia.Rendering.Utilities;
using Avalonia.Utilities;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
@ -39,9 +40,9 @@ namespace Avalonia.Skia
Canvas.Clear(color.ToSKColor()); Canvas.Clear(color.ToSKColor());
} }
public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{ {
var impl = (BitmapImpl)source; var impl = (BitmapImpl)source.Item;
var s = sourceRect.ToSKRect(); var s = sourceRect.ToSKRect();
var d = destRect.ToSKRect(); var d = destRect.ToSKRect();
using (var paint = new SKPaint() using (var paint = new SKPaint()
@ -51,10 +52,10 @@ namespace Avalonia.Skia
} }
} }
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{ {
PushOpacityMask(opacityMask, opacityMaskRect); PushOpacityMask(opacityMask, opacityMaskRect);
DrawImage(source, 1, new Rect(0, 0, source.PixelWidth, source.PixelHeight), destRect); DrawImage(source, 1, new Rect(0, 0, source.Item.PixelWidth, source.Item.PixelHeight), destRect);
PopOpacityMask(); PopOpacityMask();
} }
@ -237,7 +238,7 @@ namespace Avalonia.Skia
} }
else else
{ {
tileBrushImage = (BitmapImpl)((tileBrush as IImageBrush)?.Source?.PlatformImpl); tileBrushImage = (BitmapImpl)((tileBrush as IImageBrush)?.Source?.PlatformImpl.Item);
} }
if (tileBrush != null && tileBrushImage != null) if (tileBrush != null && tileBrushImage != null)
@ -252,7 +253,7 @@ namespace Avalonia.Skia
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip); context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform; context.Transform = calc.IntermediateTransform;
context.DrawImage(tileBrushImage, 1, rect, rect); context.DrawImage(RefCountable.CreateUnownedNotClonable(tileBrushImage), 1, rect, rect);
context.PopClip(); context.PopClip();
} }

11
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Utilities;
using SharpDX; using SharpDX;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using SharpDX.Mathematics.Interop; using SharpDX.Mathematics.Interop;
@ -99,9 +100,9 @@ namespace Avalonia.Direct2D1.Media
/// <param name="opacity">The opacity to draw with.</param> /// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</param> /// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{ {
using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget))
{ {
_renderTarget.DrawBitmap( _renderTarget.DrawBitmap(
d2d.Value, d2d.Value,
@ -119,9 +120,9 @@ namespace Avalonia.Direct2D1.Media
/// <param name="opacityMask">The opacity mask to draw with.</param> /// <param name="opacityMask">The opacity mask to draw with.</param>
/// <param name="opacityMaskRect">The destination rect for the opacity mask.</param> /// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{ {
using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget))
using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value)) using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D()))
@ -398,7 +399,7 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl( return new ImageBrushImpl(
imageBrush, imageBrush,
_renderTarget, _renderTarget,
(BitmapImpl)imageBrush.Source.PlatformImpl, (BitmapImpl)imageBrush.Source.PlatformImpl.Item,
destinationSize); destinationSize);
} }
else if (visualBrush != null) else if (visualBrush != null)

4
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -4,6 +4,7 @@
using System; using System;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Rendering.Utilities; using Avalonia.Rendering.Utilities;
using Avalonia.Utilities;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
@ -100,7 +101,8 @@ namespace Avalonia.Direct2D1.Media
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip); context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform; context.Transform = calc.IntermediateTransform;
context.DrawImage(bitmap, 1, rect, rect);
context.DrawImage(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect);
context.PopClip(); context.PopClip();
} }

2
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -359,7 +359,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
private Mock<IDrawingContextImpl> GetLayerContext(DeferredRenderer renderer, IControl layerRoot) private Mock<IDrawingContextImpl> GetLayerContext(DeferredRenderer renderer, IControl layerRoot)
{ {
return Mock.Get(renderer.Layers[layerRoot].Bitmap.CreateDrawingContext(null)); return Mock.Get(renderer.Layers[layerRoot].Bitmap.Item.CreateDrawingContext(null));
} }
private void IgnoreFirstFrame(Mock<IRenderLoop> loop, Mock<ISceneBuilder> sceneBuilder) private void IgnoreFirstFrame(Mock<IRenderLoop> loop, Mock<ISceneBuilder> sceneBuilder)

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

@ -1,8 +1,10 @@
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.VisualTree; using Avalonia.VisualTree;
using Moq; using Moq;
using Xunit; using Xunit;
@ -103,15 +105,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
} }
Assert.Equal(2, node.DrawOperations.Count); Assert.Equal(2, node.DrawOperations.Count);
Assert.IsType<RectangleNode>(node.DrawOperations[0]); Assert.IsType<RectangleNode>(node.DrawOperations[0].Item);
Assert.IsType<RectangleNode>(node.DrawOperations[1]); Assert.IsType<RectangleNode>(node.DrawOperations[1].Item);
} }
[Fact] [Fact]
public void Should_Not_Replace_Identical_DrawOperation() public void Should_Not_Replace_Identical_DrawOperation()
{ {
var node = new VisualNode(new TestRoot(), 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 operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0));
var layers = new SceneLayers(node.Visual); var layers = new SceneLayers(node.Visual);
var target = new DeferredDrawingContextImpl(null, layers); var target = new DeferredDrawingContextImpl(null, layers);
@ -124,16 +126,16 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
} }
Assert.Equal(1, node.DrawOperations.Count); Assert.Equal(1, node.DrawOperations.Count);
Assert.Same(operation, node.DrawOperations.Single()); Assert.Same(operation.Item, node.DrawOperations.Single().Item);
Assert.IsType<RectangleNode>(node.DrawOperations[0]); Assert.IsType<RectangleNode>(node.DrawOperations[0].Item);
} }
[Fact] [Fact]
public void Should_Replace_Different_DrawOperation() public void Should_Replace_Different_DrawOperation()
{ {
var node = new VisualNode(new TestRoot(), 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 operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0));
var layers = new SceneLayers(node.Visual); var layers = new SceneLayers(node.Visual);
var target = new DeferredDrawingContextImpl(null, layers); var target = new DeferredDrawingContextImpl(null, layers);
@ -148,7 +150,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Assert.Equal(1, node.DrawOperations.Count); Assert.Equal(1, node.DrawOperations.Count);
Assert.NotSame(operation, node.DrawOperations.Single()); Assert.NotSame(operation, node.DrawOperations.Single());
Assert.IsType<RectangleNode>(node.DrawOperations[0]); Assert.IsType<RectangleNode>(node.DrawOperations[0].Item);
} }
[Fact] [Fact]
@ -173,13 +175,18 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
public void Should_Trim_DrawOperations() public void Should_Trim_DrawOperations()
{ {
var node = new VisualNode(new TestRoot(), null); var node = new VisualNode(new TestRoot(), null);
node.LayerRoot = node.Visual; node.LayerRoot = node.Visual;
node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 10, 100), 0));
node.AddDrawOperation(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 20, 100), 0));
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));
for (var i = 0; i < 4; ++i)
{
var drawOperation = new Mock<IDrawOperation>();
using (var r = RefCountable.Create(drawOperation.Object))
{
node.AddDrawOperation(r);
}
}
var drawOperations = node.DrawOperations.Select(op => op.Item).ToList();
var layers = new SceneLayers(node.Visual); var layers = new SceneLayers(node.Visual);
var target = new DeferredDrawingContextImpl(null, layers); var target = new DeferredDrawingContextImpl(null, layers);
@ -190,6 +197,33 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
} }
Assert.Equal(2, node.DrawOperations.Count); Assert.Equal(2, node.DrawOperations.Count);
foreach (var i in drawOperations)
{
Mock.Get(i).Verify(x => x.Dispose());
}
}
[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)

80
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
{ {
@ -53,15 +55,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Assert.Equal(1, borderNode.Children.Count); Assert.Equal(1, borderNode.Children.Count);
Assert.Equal(1, borderNode.DrawOperations.Count); Assert.Equal(1, borderNode.DrawOperations.Count);
var backgroundNode = (RectangleNode)borderNode.DrawOperations[0]; var backgroundNode = (RectangleNode)borderNode.DrawOperations[0].Item;
Assert.Equal(Brushes.Red, backgroundNode.Brush); Assert.Equal(Brushes.Red, backgroundNode.Brush);
var textBlockNode = (VisualNode)borderNode.Children[0]; var textBlockNode = borderNode.Children[0];
Assert.Same(textBlockNode, result.FindNode(textBlock)); Assert.Same(textBlockNode, result.FindNode(textBlock));
Assert.Same(textBlock, textBlockNode.Visual); Assert.Same(textBlock, textBlockNode.Visual);
Assert.Equal(1, textBlockNode.DrawOperations.Count); Assert.Equal(1, textBlockNode.DrawOperations.Count);
var textNode = (TextNode)textBlockNode.DrawOperations[0]; var textNode = (TextNode)textBlockNode.DrawOperations[0].Item;
Assert.NotNull(textNode.Text); Assert.NotNull(textNode.Text);
} }
} }
@ -358,15 +360,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var borderNode = (VisualNode)result.Root.Children[0]; var borderNode = (VisualNode)result.Root.Children[0];
Assert.Same(border, borderNode.Visual); Assert.Same(border, borderNode.Visual);
var backgroundNode = (RectangleNode)borderNode.DrawOperations[0]; var backgroundNode = (RectangleNode)borderNode.DrawOperations[0].Item;
Assert.NotSame(initialBackgroundNode, backgroundNode); Assert.NotSame(initialBackgroundNode, backgroundNode);
Assert.Equal(Brushes.Green, backgroundNode.Brush); Assert.Equal(Brushes.Green, backgroundNode.Brush);
var textBlockNode = (VisualNode)borderNode.Children[0]; var textBlockNode = (VisualNode)borderNode.Children[0];
Assert.Same(textBlock, textBlockNode.Visual); Assert.Same(textBlock, textBlockNode.Visual);
var textNode = (TextNode)textBlockNode.DrawOperations[0]; var textNode = (TextNode)textBlockNode.DrawOperations[0].Item;
Assert.Same(initialTextNode, textNode); Assert.Same(initialTextNode.Item, textNode);
} }
} }
@ -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(

28
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/VisualNodeTests.cs

@ -1,5 +1,6 @@
using System; using System;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Moq; using Moq;
using Xunit; using Xunit;
@ -43,7 +44,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var node = new VisualNode(Mock.Of<IVisual>(), null); var node = new VisualNode(Mock.Of<IVisual>(), null);
var collection = node.DrawOperations; var collection = node.DrawOperations;
node.AddDrawOperation(Mock.Of<IDrawOperation>()); node.AddDrawOperation(RefCountable.Create(Mock.Of<IDrawOperation>()));
Assert.NotSame(collection, node.DrawOperations); Assert.NotSame(collection, node.DrawOperations);
} }
@ -52,7 +53,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
public void Cloned_Nodes_Should_Share_DrawOperations_Collection() public void Cloned_Nodes_Should_Share_DrawOperations_Collection()
{ {
var node1 = new VisualNode(Mock.Of<IVisual>(), null); var node1 = new VisualNode(Mock.Of<IVisual>(), null);
node1.AddDrawOperation(Mock.Of<IDrawOperation>()); node1.AddDrawOperation(RefCountable.Create(Mock.Of<IDrawOperation>()));
var node2 = node1.Clone(null); var node2 = node1.Clone(null);
@ -63,18 +64,33 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
public void Adding_DrawOperation_To_Cloned_Node_Should_Create_New_Collection() public void Adding_DrawOperation_To_Cloned_Node_Should_Create_New_Collection()
{ {
var node1 = new VisualNode(Mock.Of<IVisual>(), null); var node1 = new VisualNode(Mock.Of<IVisual>(), null);
var operation1 = Mock.Of<IDrawOperation>(); var operation1 = RefCountable.Create(Mock.Of<IDrawOperation>());
node1.AddDrawOperation(operation1); node1.AddDrawOperation(operation1);
var node2 = node1.Clone(null); var node2 = node1.Clone(null);
var operation2 = Mock.Of<IDrawOperation>(); var operation2 = RefCountable.Create(Mock.Of<IDrawOperation>());
node2.ReplaceDrawOperation(0, operation2); node2.ReplaceDrawOperation(0, operation2);
Assert.NotSame(node1.DrawOperations, node2.DrawOperations); Assert.NotSame(node1.DrawOperations, node2.DrawOperations);
Assert.Equal(1, node1.DrawOperations.Count); Assert.Equal(1, node1.DrawOperations.Count);
Assert.Equal(1, node2.DrawOperations.Count); Assert.Equal(1, node2.DrawOperations.Count);
Assert.Same(operation1, node1.DrawOperations[0]); Assert.Same(operation1.Item, node1.DrawOperations[0].Item);
Assert.Same(operation2, node2.DrawOperations[0]); Assert.Same(operation2.Item, node2.DrawOperations[0].Item);
}
[Fact]
public void DrawOperations_In_Cloned_Node_Are_Cloned()
{
var node1 = new VisualNode(Mock.Of<IVisual>(), null);
var operation1 = RefCountable.Create(Mock.Of<IDrawOperation>());
node1.AddDrawOperation(operation1);
var node2 = node1.Clone(null);
var operation2 = RefCountable.Create(Mock.Of<IDrawOperation>());
node2.AddDrawOperation(operation2);
Assert.Same(node1.DrawOperations[0].Item, node2.DrawOperations[0].Item);
Assert.NotSame(node1.DrawOperations[0], node2.DrawOperations[0]);
} }
} }
} }

Loading…
Cancel
Save