diff --git a/src/ImageSharp/Common/Memory/ArrayPointer.cs b/src/ImageSharp/Common/Memory/ArrayPointer.cs index c864d31fd1..56cae35a45 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer.cs @@ -7,6 +7,13 @@ namespace ImageSharp /// internal static class ArrayPointer { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ArrayPointer GetArrayPointer(this PinnedBuffer buffer) + where T : struct + { + return new ArrayPointer(buffer.Array, (void*)buffer.Pointer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int count) where T : struct diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer.cs b/src/ImageSharp/Common/Memory/PinnedBuffer.cs new file mode 100644 index 0000000000..c6e0c7c6f9 --- /dev/null +++ b/src/ImageSharp/Common/Memory/PinnedBuffer.cs @@ -0,0 +1,109 @@ +namespace ImageSharp +{ + using System; + using System.Buffers; + using System.Runtime.InteropServices; + + /// + /// Manages a pinned buffer of 'T' as a Disposable resource. + /// The backing array is either pooled or comes from the outside. + /// TODO: Should replace the pinning/dispose logic in several classes like or ! + /// + /// The value type. + internal class PinnedBuffer : IDisposable + where T : struct + { + private GCHandle handle; + + private bool isBufferRented; + + private bool isDisposed; + + /// + /// TODO: Consider reusing functionality of + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(); + + /// + /// Initializes a new instance of the class. + /// + /// The desired count of elements. (Minimum size for ) + public PinnedBuffer(int count) + { + this.Count = count; + this.Array = ArrayPool.Rent(count); + this.isBufferRented = true; + this.Pin(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The array to pin. + public PinnedBuffer(T[] array) + { + this.Count = array.Length; + this.Array = array; + this.Pin(); + } + + /// + /// The count of "relevant" elements. Usually be smaller than 'Array.Length' when is pooled. + /// + public int Count { get; private set; } + + /// + /// The (pinned) array of elements. + /// + public T[] Array { get; private set; } + + /// + /// Pointer to the pinned . + /// + public IntPtr Pointer { get; private set; } + + /// + /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + this.isDisposed = true; + this.UnPin(); + + if (this.isBufferRented) + { + ArrayPool.Return(this.Array, true); + } + + this.Array = null; + this.Count = 0; + + GC.SuppressFinalize(this); + } + + private void Pin() + { + this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); + this.Pointer = this.handle.AddrOfPinnedObject(); + } + + private void UnPin() + { + if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) + { + return; + } + this.handle.Free(); + this.Pointer = IntPtr.Zero; + } + + ~PinnedBuffer() + { + this.UnPin(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelPool{TColor}.cs b/src/ImageSharp/Image/PixelPool{TColor}.cs index 8193600daf..ea6dad6b12 100644 --- a/src/ImageSharp/Image/PixelPool{TColor}.cs +++ b/src/ImageSharp/Image/PixelPool{TColor}.cs @@ -8,6 +8,7 @@ namespace ImageSharp using System; using System.Buffers; + // TODO: Consider refactoring this into a more general ClearPool, so we can use it in PinnedBuffer! /// /// Provides a resource pool that enables reusing instances of type . /// diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 413bd94515..f2081b9437 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -1,104 +1,132 @@ namespace ImageSharp.Tests.Colors { using System; + using System.Numerics; using Xunit; - - public class BulkPixelOperationsTests + + public abstract class BulkPixelOperationsTests + where TColor : struct, IPixel { - public class TypeParam + public class ColorPixels : BulkPixelOperationsTests { } + public class ArgbPixels : BulkPixelOperationsTests + { + } + public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; + [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromVector4(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromVector4(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToVector4(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToVector4(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToXyzBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToXyzBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromXyzBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromXyzBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToXyzwBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToXyzwBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromXyzwBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromXyzwBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToZyxBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToZyxBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromZyxBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromZyxBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToZyxwBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToZyxwBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromZyxwBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromZyxwBytes(int count) { throw new NotImplementedException(); } + + public class TestBuffers + { + internal static PinnedBuffer Vector4(int length) + { + Vector4[] result = new Vector4[length]; + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) + { + result[i] = GetVector(rnd); + } + + return new PinnedBuffer(result); + } + + internal static PinnedBuffer Pixel(int length) + { + TColor[] result = new TColor[length]; + + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetVector(rnd); + result[i].PackFromVector4(v); + } + + return new PinnedBuffer(result); + } + + private static Vector4 GetVector(Random rnd) + { + return new Vector4( + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble() + ); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs new file mode 100644 index 0000000000..e0783f7165 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -0,0 +1,69 @@ +namespace ImageSharp.Tests.Common +{ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using Xunit; + + public unsafe class PinnedBufferTests + { + public struct Foo + { + public int A; + + public double B; + } + + [Theory] + [InlineData(42)] + [InlineData(1111)] + public void ConstructWithOwnArray(int count) + { + using (PinnedBuffer buffer = new PinnedBuffer(count)) + { + Assert.NotNull(buffer.Array); + Assert.Equal(count, buffer.Count); + Assert.True(buffer.Array.Length >= count); + + VerifyPointer(buffer); + } + } + + [Theory] + [InlineData(42)] + [InlineData(1111)] + public void ConstructWithExistingArray(int count) + { + Foo[] array = new Foo[count]; + using (PinnedBuffer buffer = new PinnedBuffer(array)) + { + Assert.Equal(array, buffer.Array); + Assert.Equal(count, buffer.Count); + + VerifyPointer(buffer); + } + } + + [Fact] + public void GetArrayPointer() + { + Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; + + using (PinnedBuffer buffer = new PinnedBuffer(a)) + { + var arrayPtr = buffer.GetArrayPointer(); + + Assert.Equal(a, arrayPtr.Array); + Assert.Equal(0, arrayPtr.Offset); + Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset); + } + } + + private static void VerifyPointer(PinnedBuffer buffer) + { + IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]); + Assert.Equal(ptr, buffer.Pointer); + } + } +} \ No newline at end of file