mirror of https://github.com/SixLabors/ImageSharp
10 changed files with 328 additions and 56 deletions
@ -0,0 +1,73 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Holds a <see cref="System.Memory{T}"/> that is either OWNED or CONSUMED.
|
|||
/// Implements content transfer logic in <see cref="SwapOrCopyContent"/> that depends on the ownership status.
|
|||
/// This is needed to transfer the contents of a temporary <see cref="Buffer2D{T}"/> to a persistent <see cref="SixLabors.ImageSharp.ImageFrame{T}.PixelBuffer"/>
|
|||
/// </summary>
|
|||
internal struct BufferManager<T> : IDisposable |
|||
{ |
|||
public BufferManager(IMemoryOwner<T> memoryOwner) |
|||
{ |
|||
this.MemoryOwner = memoryOwner; |
|||
this.Memory = memoryOwner.Memory; |
|||
} |
|||
|
|||
public BufferManager(Memory<T> memory) |
|||
{ |
|||
this.Memory = memory; |
|||
this.MemoryOwner = null; |
|||
} |
|||
|
|||
public IMemoryOwner<T> MemoryOwner { get; private set; } |
|||
|
|||
public Memory<T> Memory { get; private set; } |
|||
|
|||
public bool OwnsMemory => this.MemoryOwner != null; |
|||
|
|||
/// <summary>
|
|||
/// Swaps the contents of 'destination' with 'source' if the buffers are owned (1),
|
|||
/// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2!
|
|||
/// </summary>
|
|||
public static void SwapOrCopyContent(ref BufferManager<T> destination, ref BufferManager<T> source) |
|||
{ |
|||
if (source.OwnsMemory && destination.OwnsMemory) |
|||
{ |
|||
SwapContents(ref destination, ref source); |
|||
} |
|||
else |
|||
{ |
|||
if (destination.Memory.Length != source.Memory.Length) |
|||
{ |
|||
throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!"); |
|||
} |
|||
|
|||
source.Memory.CopyTo(destination.Memory); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
this.MemoryOwner?.Dispose(); |
|||
} |
|||
|
|||
private static void SwapContents(ref BufferManager<T> a, ref BufferManager<T> b) |
|||
{ |
|||
IMemoryOwner<T> tempOwner = a.MemoryOwner; |
|||
Memory<T> tempMemory = a.Memory; |
|||
|
|||
a.MemoryOwner = b.MemoryOwner; |
|||
a.Memory = b.Memory; |
|||
|
|||
b.MemoryOwner = tempOwner; |
|||
b.Memory = tempMemory; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
|
|||
using Xunit; |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory |
|||
{ |
|||
public class BufferManagerTests |
|||
{ |
|||
public class Construction |
|||
{ |
|||
[Fact] |
|||
public void InitializeAsOwner_MemoryOwner_IsPresent() |
|||
{ |
|||
var data = new Rgba32[21]; |
|||
var mmg = new TestMemoryManager<Rgba32>(data); |
|||
|
|||
var a = new BufferManager<Rgba32>(mmg); |
|||
|
|||
Assert.Equal(mmg, a.MemoryOwner); |
|||
Assert.Equal(mmg.Memory, a.Memory); |
|||
Assert.True(a.OwnsMemory); |
|||
} |
|||
|
|||
[Fact] |
|||
public void InitializeAsObserver_MemoryOwner_IsNull() |
|||
{ |
|||
var data = new Rgba32[21]; |
|||
var mmg = new TestMemoryManager<Rgba32>(data); |
|||
|
|||
var a = new BufferManager<Rgba32>(mmg.Memory); |
|||
|
|||
Assert.Null(a.MemoryOwner); |
|||
Assert.Equal(mmg.Memory, a.Memory); |
|||
Assert.False(a.OwnsMemory); |
|||
} |
|||
} |
|||
|
|||
public class Dispose |
|||
{ |
|||
[Fact] |
|||
public void WhenOwnershipIsTransfered_ShouldDisposeMemoryOwner() |
|||
{ |
|||
var mmg = new TestMemoryManager<int>(new int[10]); |
|||
var bmg = new BufferManager<int>(mmg); |
|||
|
|||
bmg.Dispose(); |
|||
Assert.True(mmg.IsDisposed); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenMemoryObserver_ShouldNotDisposeAnything() |
|||
{ |
|||
var mmg = new TestMemoryManager<int>(new int[10]); |
|||
var bmg = new BufferManager<int>(mmg.Memory); |
|||
|
|||
bmg.Dispose(); |
|||
Assert.False(mmg.IsDisposed); |
|||
} |
|||
} |
|||
|
|||
public class SwapOrCopyContent |
|||
{ |
|||
private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); |
|||
|
|||
private BufferManager<T> AllocateBufferManager<T>(int length, AllocationOptions options = AllocationOptions.None) |
|||
where T : struct |
|||
{ |
|||
var owner = (IMemoryOwner<T>)this.MemoryAllocator.Allocate<T>(length, options); |
|||
return new BufferManager<T>(owner); |
|||
} |
|||
|
|||
[Fact] |
|||
public void WhenBothAreMemoryOwners_ShouldSwap() |
|||
{ |
|||
BufferManager<int> a = this.AllocateBufferManager<int>(13); |
|||
BufferManager<int> b = this.AllocateBufferManager<int>(17); |
|||
|
|||
IMemoryOwner<int> aa = a.MemoryOwner; |
|||
IMemoryOwner<int> bb = b.MemoryOwner; |
|||
|
|||
Memory<int> aaa = a.Memory; |
|||
Memory<int> bbb = b.Memory; |
|||
|
|||
BufferManager<int>.SwapOrCopyContent(ref a, ref b); |
|||
|
|||
Assert.Equal(bb, a.MemoryOwner); |
|||
Assert.Equal(aa, b.MemoryOwner); |
|||
|
|||
Assert.Equal(bbb, a.Memory); |
|||
Assert.Equal(aaa, b.Memory); |
|||
Assert.NotEqual(a.Memory, b.Memory); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false)] |
|||
[InlineData(true)] |
|||
public void WhenDestIsNotMemoryOwner_SameSize_ShouldCopy(bool sourceIsOwner) |
|||
{ |
|||
var data = new Rgba32[21]; |
|||
var color = new Rgba32(1, 2, 3, 4); |
|||
|
|||
var destOwner = new TestMemoryManager<Rgba32>(data); |
|||
var dest = new BufferManager<Rgba32>(destOwner.Memory); |
|||
|
|||
var sourceOwner = (IMemoryOwner<Rgba32>)this.MemoryAllocator.Allocate<Rgba32>(21); |
|||
|
|||
BufferManager<Rgba32> source = sourceIsOwner |
|||
? new BufferManager<Rgba32>(sourceOwner) |
|||
: new BufferManager<Rgba32>(sourceOwner.Memory); |
|||
|
|||
sourceOwner.Memory.Span[10] = color; |
|||
|
|||
// Act:
|
|||
BufferManager<Rgba32>.SwapOrCopyContent(ref dest, ref source); |
|||
|
|||
// Assert:
|
|||
Assert.Equal(color, dest.Memory.Span[10]); |
|||
Assert.NotEqual(sourceOwner, dest.MemoryOwner); |
|||
Assert.NotEqual(destOwner, source.MemoryOwner); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(false)] |
|||
[InlineData(true)] |
|||
public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) |
|||
{ |
|||
var data = new Rgba32[21]; |
|||
var color = new Rgba32(1, 2, 3, 4); |
|||
|
|||
var destOwner = new TestMemoryManager<Rgba32>(data); |
|||
var dest = new BufferManager<Rgba32>(destOwner.Memory); |
|||
|
|||
var sourceOwner = (IMemoryOwner<Rgba32>)this.MemoryAllocator.Allocate<Rgba32>(22); |
|||
|
|||
BufferManager<Rgba32> source = sourceIsOwner |
|||
? new BufferManager<Rgba32>(sourceOwner) |
|||
: new BufferManager<Rgba32>(sourceOwner.Memory); |
|||
sourceOwner.Memory.Span[10] = color; |
|||
|
|||
// Act:
|
|||
Assert.ThrowsAny<InvalidOperationException>( |
|||
() => BufferManager<Rgba32>.SwapOrCopyContent(ref dest, ref source) |
|||
); |
|||
|
|||
Assert.Equal(color, source.Memory.Span[10]); |
|||
Assert.NotEqual(color, dest.Memory.Span[10]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory |
|||
{ |
|||
internal class TestMemoryAllocator : MemoryAllocator |
|||
{ |
|||
public TestMemoryAllocator(byte dirtyValue = 42) |
|||
{ |
|||
this.DirtyValue = dirtyValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The value to initilazie the result buffer with, with non-clean options (<see cref="AllocationOptions.None"/>)
|
|||
/// </summary>
|
|||
public byte DirtyValue { get; } |
|||
|
|||
internal override IBuffer<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
T[] array = this.AllocateArray<T>(length, options); |
|||
|
|||
return new BasicArrayBuffer<T>(array, length); |
|||
} |
|||
|
|||
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) |
|||
{ |
|||
byte[] array = this.AllocateArray<byte>(length, options); |
|||
return new ManagedByteBuffer(array); |
|||
} |
|||
|
|||
private T[] AllocateArray<T>(int length, AllocationOptions options) |
|||
where T : struct |
|||
{ |
|||
var array = new T[length + 42]; |
|||
|
|||
if (options == AllocationOptions.None) |
|||
{ |
|||
Span<byte> data = MemoryMarshal.Cast<T, byte>(array.AsSpan()); |
|||
data.Fill(this.DirtyValue); |
|||
} |
|||
|
|||
return array; |
|||
} |
|||
|
|||
private class ManagedByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer |
|||
{ |
|||
public ManagedByteBuffer(byte[] array) |
|||
: base(array) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue