diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 18f9d92a6..8ec332a91 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -59,8 +59,8 @@ namespace ImageSharp ); Vector bVec = new Vector(256.0f / 255.0f); - Vector magicInt = new Vector(1191182336); Vector magicFloat = new Vector(32768.0f); + Vector magicInt = new Vector(1191182336); // reinterpreded value of 32768.0f Vector mask = new Vector(255); int rawInputSize = count * 4; @@ -92,17 +92,7 @@ namespace ImageSharp vf.CopyTo(fTemp, i); } - // TODO: Replace this with an optimized ArrayPointer.Copy() implementation: - uint byteCount = (uint)rawInputSize * sizeof(float); - - if (byteCount > 1024u) - { - Marshal.Copy(fTemp, 0, destVectors.PointerAtOffset, rawInputSize); - } - else - { - Unsafe.CopyBlock((void*)destVectors, tPtr, byteCount); - } + BufferPointer.Copy(tempBuf, (BufferPointer) destVectors, rawInputSize); } } diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index f8798ba24..cc544341e 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -6,13 +6,17 @@ namespace ImageSharp { using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// /// Utility methods for /// internal static class BufferPointer { - + /// + /// It's worth to use Marshal.Copy() over this size. + /// + private const uint ByteCountThreshold = 1024u; /// /// Copy 'count' number of elements of the same type from 'source' to 'dest' @@ -25,7 +29,19 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) where T : struct { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(count)); + int elementSize = Unsafe.SizeOf(); + uint byteCount = (uint) (count * elementSize); + + if (byteCount > ByteCountThreshold && elementSize == sizeof(int)) + { + // TODO: Add the optimized path for non int-compatible types + int[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, count); + } + else + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + } } /// @@ -39,7 +55,19 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) where T : struct { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(countInSource)); + int elementSize = Unsafe.SizeOf(); + uint byteCount = (uint)(countInSource * elementSize); + + if (byteCount > ByteCountThreshold && elementSize == sizeof(int)) + { + // TODO: Add the optimized path for non int-compatible types + int[] srcArray = Unsafe.As(source.Array); + Marshal.Copy(srcArray, source.Offset, destination.PointerAtOffset, countInSource); + } + else + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + } } /// @@ -53,7 +81,16 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInDest) where T : struct { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, USizeOf(countInDest)); + int byteCount = SizeOf(countInDest); + + if (byteCount > (int)ByteCountThreshold) + { + Marshal.Copy(source.Array, source.Offset, destination.PointerAtOffset, byteCount); + } + else + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); + } } /// diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index e05650a70..a9935c5dd 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -87,7 +87,22 @@ namespace ImageSharp { return (byte*)bufferPointer.PointerAtOffset; } - + + /// + /// Converts instance to + /// setting it's and to correct values. + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator BufferPointer(BufferPointer source) + { + BufferPointer result = default(BufferPointer); + result.Array = Unsafe.As(source.Array); + result.Offset = source.Offset * Unsafe.SizeOf(); + result.PointerAtOffset = source.PointerAtOffset; + return result; + } + /// /// Forms a slice out of the given BufferPointer, beginning at 'offset'. /// diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 8e5ded048..ea76252c5 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -117,6 +117,18 @@ namespace ImageSharp return new BufferPointer(this.Array, (void*)this.Pointer); } + /// + /// Gets a to the beginning of the raw data in 'buffer'. + /// + /// The element type + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe BufferPointer Slice(int offset) + { + return new BufferPointer(this.Array, (void*)this.Pointer, offset); + } + + /// /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. /// diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index a8b7ceb33..4d3548c81 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -212,6 +212,9 @@ Tests\Colors\BulkPixelOperationsTests.cs + + Tests\Common\BufferPointerTests.cs + Tests\Common\PinnedBufferTests.cs diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 41abd9d4a..fa1b536f3 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -17,7 +17,7 @@ { Assert.IsType(BulkPixelOperations.Instance); } - + [Fact] public void ToVector4SimdAligned() { diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs index a0b91b4c4..fc26bf097 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -26,11 +26,26 @@ namespace ImageSharp.Tests.Common Foo[] result = new Foo[size]; for (int i = 0; i < size; i++) { - result[i] = new Foo(i, i); + result[i] = new Foo(i+1, i+1); } return result; } } + + [Fact] + public void AsBytes() + { + Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; + + using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) + { + BufferPointer orig = colorBuf.Slice(1); + BufferPointer asBytes = (BufferPointer < byte > )orig; + + Assert.Equal(asBytes.Offset, sizeof(Foo)); + Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); + } + } [Fact] public void ConstructWithoutOffset() @@ -93,6 +108,27 @@ namespace ImageSharp.Tests.Common Assert.NotEqual(default(T), data[idx]); } + + private static byte[] CreateTestBytes(int count) + { + byte[] result = new byte[count]; + for (int i = 0; i < result.Length; i++) + { + result[i] = (byte)((i % 200) + 1); + } + return result; + } + + private static int[] CreateTestInts(int count) + { + int[] result = new int[count]; + for (int i = 0; i < result.Length; i++) + { + result[i] = i + 1; + } + return result; + } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -104,56 +140,102 @@ namespace ImageSharp.Tests.Common fixed (Foo* pSource = source) fixed (Foo* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, 1); - BufferPointer.Copy(apSource, apDest, count); + BufferPointer.Copy(apSource, apDest, count-1); } AssertNotDefault(source, 1); AssertNotDefault(dest, 1); - Assert.Equal(source[0], dest[0]); + Assert.NotEqual(source[0], dest[0]); Assert.Equal(source[1], dest[1]); + Assert.Equal(source[2], dest[2]); Assert.Equal(source[count-1], dest[count-1]); Assert.NotEqual(source[count], dest[count]); } - + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void IntToInt(int count) + { + int[] source = CreateTestInts(count+2); + int[] dest = new int[count + 5]; + + fixed (int* pSource = source) + fixed (int* pDest = dest) + { + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, 1); + + BufferPointer.Copy(apSource, apDest, count -1); + } + + AssertNotDefault(source, 1); + AssertNotDefault(dest, 1); + + Assert.NotEqual(source[0], dest[0]); + Assert.Equal(source[1], dest[1]); + Assert.Equal(source[2], dest[2]); + Assert.Equal(source[count - 1], dest[count - 1]); + Assert.NotEqual(source[count], dest[count]); + } + [Theory] [InlineData(4)] [InlineData(1500)] public void GenericToBytes(int count) { int destCount = count * sizeof(Foo); - Foo[] source = Foo.CreateArray(count + 2); - byte[] dest = new byte[destCount + sizeof(Foo) + 1]; + Foo[] source = Foo.CreateArray(count+2); + byte[] dest = new byte[destCount + sizeof(Foo)*2]; fixed (Foo* pSource = source) fixed (byte* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(dest, pDest, sizeof(Foo)); - BufferPointer.Copy(apSource, apDest, count); + BufferPointer.Copy(apSource, apDest, count - 1); } AssertNotDefault(source, 1); - Assert.True(ElementsAreEqual(source, dest, 0)); + Assert.False(ElementsAreEqual(source, dest, 0)); + Assert.True(ElementsAreEqual(source, dest, 1)); + Assert.True(ElementsAreEqual(source, dest, 2)); Assert.True(ElementsAreEqual(source, dest, count - 1)); Assert.False(ElementsAreEqual(source, dest, count)); } - - private static byte[] CreateTestBytes(int count) + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void IntToBytes(int count) { - byte[] result = new byte[count]; - for (int i = 0; i < result.Length; i++) + int destCount = count * sizeof(int); + int[] source = CreateTestInts(count+2); + byte[] dest = new byte[destCount + sizeof(int) + 1]; + + fixed (int* pSource = source) + fixed (byte* pDest = dest) { - result[i] = (byte)(i % 255); + BufferPointer apSource = new BufferPointer(source, pSource); + BufferPointer apDest = new BufferPointer(dest, pDest); + + BufferPointer.Copy(apSource, apDest, count); } - return result; + + AssertNotDefault(source, 1); + + Assert.True(ElementsAreEqual(source, dest, 0)); + Assert.True(ElementsAreEqual(source, dest, count - 1)); + Assert.False(ElementsAreEqual(source, dest, count)); } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -199,8 +281,8 @@ namespace ImageSharp.Tests.Common } } } - - private static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) + + internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) { fixed (Foo* pArray = array) fixed (byte* pRaw = rawArray) @@ -213,6 +295,20 @@ namespace ImageSharp.Tests.Common return val1.Equals(val2); } } + + internal static bool ElementsAreEqual(int[] array, byte[] rawArray, int index) + { + fixed (int* pArray = array) + fixed (byte* pRaw = rawArray) + { + int* pCasted = (int*)pRaw; + + int val1 = pArray[index]; + int val2 = pCasted[index]; + + return val1.Equals(val2); + } + } } } } \ No newline at end of file