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