Browse Source

introducing BufferManager<T>

pull/660/head
Anton Firszov 8 years ago
parent
commit
706babe2a2
  1. 5
      ImageSharp.sln.DotSettings
  2. 73
      src/ImageSharp/Memory/BufferManager.cs
  3. 8
      src/ImageSharp/Memory/IBuffer{T}.cs
  4. 11
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  5. 50
      tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
  6. 158
      tests/ImageSharp.Tests/Memory/BufferManagerTests.cs
  7. 3
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  8. 55
      tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs
  9. 19
      tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs
  10. 2
      tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

5
ImageSharp.sln.DotSettings

@ -343,8 +343,11 @@
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;/Patterns&gt;</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/QualifiedUsingAtNestedScope/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">// Copyright (c) Six Labors and contributors.&#xD;
// Licensed under the Apache License, Version 2.0.&#xD;
</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AC/@EntryIndexedValue">AC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DC/@EntryIndexedValue">DC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DCT/@EntryIndexedValue">DCT</s:String>

73
src/ImageSharp/Memory/BufferManager.cs

@ -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;
}
}
}

8
src/ImageSharp/Memory/IBuffer{T}.cs

@ -19,14 +19,14 @@ namespace SixLabors.Memory
where T : struct
{
/// <summary>
/// Gets the <see cref="Memory{T}"/> ownerd/consumed by this buffer.
/// Gets a value indicating whether this instance is owning the <see cref="Memory"/>.
/// </summary>
Memory<T> Memory { get; }
bool IsMemoryOwner { get; }
/// <summary>
/// Gets a value indicating whether this instance is owning the <see cref="Memory"/>.
/// Gets the <see cref="Memory{T}"/> ownerd/consumed by this buffer.
/// </summary>
bool IsMemoryOwner { get; }
Memory<T> Memory { get; }
/// <summary>
/// Gets the span to the memory "promised" by this buffer when it's OWNED (1).

11
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -7,7 +7,11 @@ namespace SixLabors.Memory
/// </summary>
internal static class MemoryAllocatorExtensions
{
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, int width, int height, AllocationOptions options = AllocationOptions.None)
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
int width,
int height,
AllocationOptions options = AllocationOptions.None)
where T : struct
{
IBuffer<T> buffer = memoryAllocator.Allocate<T>(width * height, options);
@ -15,7 +19,10 @@ namespace SixLabors.Memory
return new Buffer2D<T>(buffer, width, height);
}
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, Size size, AllocationOptions options = AllocationOptions.None)
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
Size size,
AllocationOptions options = AllocationOptions.None)
where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, options);

50
tests/ImageSharp.Tests/Memory/Buffer2DTests.cs

@ -1,20 +1,20 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Memory
{
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.ImageSharp.Tests.Common;
using SixLabors.Primitives;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
using Xunit;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Memory
{
public class Buffer2DTests
{
// ReSharper disable once ClassNeverInstantiated.Local
@ -30,31 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
}
}
private MemoryAllocator MemoryAllocator { get; } = new MockMemoryAllocator();
private class MockMemoryAllocator : MemoryAllocator
{
internal override IBuffer<T> Allocate<T>(int length, AllocationOptions options)
{
var array = new T[length + 42];
if (options == AllocationOptions.None)
{
Span<byte> data = MemoryMarshal.Cast<T, byte>(array.AsSpan());
for (int i = 0; i < data.Length; i++)
{
data[i] = 42;
}
}
return new BasicArrayBuffer<T>(array, length);
}
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options)
{
throw new NotImplementedException();
}
}
private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator();
[Theory]
[InlineData(7, 42)]
@ -134,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
public class SwapOrCopyContent
{
private MemoryAllocator MemoryAllocator { get; } = new MockMemoryAllocator();
private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator();
[Fact]
public void WhenBothBuffersAreMemoryOwners_ShouldSwap()

158
tests/ImageSharp.Tests/Memory/BufferManagerTests.cs

@ -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]);
}
}
}
}

3
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -3,6 +3,7 @@
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
@ -91,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
using (Image<TPixel> image0 = provider.GetImage())
{
var mmg = TestMemoryManager<TPixel>.CreateAsCopyOfPixelData(image0);
var mmg = TestMemoryManager<TPixel>.CreateAsCopyOf(image0.GetPixelSpan());
using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height))
{

55
tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs

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

19
tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs

@ -7,18 +7,16 @@ namespace SixLabors.ImageSharp.Tests
using SixLabors.ImageSharp.PixelFormats;
class TestMemoryManager<T> : MemoryManager<T>
where T : struct, IPixel<T>
where T : struct
{
public TestMemoryManager(T[] pixelArray)
{
this.PixelArray = pixelArray;
}
public T[] PixelArray { get; }
public T[] PixelArray { get; private set; }
protected override void Dispose(bool disposing)
{
}
public bool IsDisposed { get; private set; }
public override Span<T> GetSpan()
{
@ -35,16 +33,17 @@ namespace SixLabors.ImageSharp.Tests
throw new NotImplementedException();
}
public static TestMemoryManager<T> CreateAsCopyOfPixelData(Span<T> pixelData)
public static TestMemoryManager<T> CreateAsCopyOf(Span<T> copyThisBuffer)
{
var pixelArray = new T[pixelData.Length];
pixelData.CopyTo(pixelArray);
var pixelArray = new T[copyThisBuffer.Length];
copyThisBuffer.CopyTo(pixelArray);
return new TestMemoryManager<T>(pixelArray);
}
public static TestMemoryManager<T> CreateAsCopyOfPixelData(Image<T> image)
protected override void Dispose(bool disposing)
{
return CreateAsCopyOfPixelData(image.GetPixelSpan());
this.IsDisposed = true;
this.PixelArray = null;
}
}
}

2
tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

@ -214,7 +214,7 @@ namespace SixLabors.ImageSharp.Tests
using (Image<TPixel> image0 = provider.GetImage())
{
var mmg = TestMemoryManager<TPixel>.CreateAsCopyOfPixelData(image0.GetPixelSpan());
var mmg = TestMemoryManager<TPixel>.CreateAsCopyOf(image0.GetPixelSpan());
using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height))
{

Loading…
Cancel
Save