diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index ace929bd6..b68b02eff 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -117,7 +117,7 @@ namespace ImageSharp.Drawing.Brushes { Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + using (Buffer buffer = new Buffer(scanlineBuffer)) { BufferSpan slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index df492a764..1af58bc62 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -150,7 +150,7 @@ namespace ImageSharp.Drawing.Brushes { Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + using (Buffer buffer = new Buffer(scanlineBuffer)) { BufferSpan slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 46444e550..0ef11e161 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -55,7 +55,7 @@ namespace ImageSharp.Drawing.Processors { DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + using (Buffer buffer = new Buffer(scanlineBuffer)) { BufferSpan slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 257eeb3ae..4e3cd01ae 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -141,7 +141,7 @@ namespace ImageSharp.Drawing.Brushes { Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + using (Buffer buffer = new Buffer(scanlineBuffer)) { BufferSpan slice = buffer.Slice(offset); diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index 125b07bca..74c7081b3 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -89,7 +89,7 @@ namespace ImageSharp.Drawing.Brushes { Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + using (Buffer buffer = new Buffer(scanlineBuffer)) { BufferSpan slice = buffer.Slice(offset); diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 9101b6b23..096e85db8 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -8,6 +8,7 @@ namespace ImageSharp using System; using System.Numerics; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// /// Conains the definition of @@ -15,7 +16,7 @@ namespace ImageSharp public partial struct Color32 { /// - /// implementation optimized for . + /// implementation optimized for . /// internal class BulkOperations : BulkPixelOperations { @@ -23,7 +24,7 @@ namespace ImageSharp /// SIMD optimized bulk implementation of /// that works only with `count` divisible by . /// - /// The to the source colors. + /// The to the source colors. /// The to the dstination vectors. /// The number of pixels to convert. /// @@ -37,14 +38,14 @@ namespace ImageSharp /// /// internal static unsafe void ToVector4SimdAligned( - BufferSpan sourceColors, + BufferSpan sourceColor32s, BufferSpan destVectors, int count) { if (!Vector.IsHardwareAccelerated) { throw new InvalidOperationException( - "Color.BulkOperations.ToVector4SimdAligned() should not be called when Vector.IsHardwareAccelerated == false!"); + "Color32.BulkOperations.ToVector4SimdAligned() should not be called when Vector.IsHardwareAccelerated == false!"); } int vecSize = Vector.Count; @@ -61,21 +62,23 @@ namespace ImageSharp int unpackedRawCount = count * 4; - uint* src = (uint*)sourceColors.PointerAtOffset; - uint* srcEnd = src + count; + ref uint src = ref Unsafe.As(ref sourceColor32s.DangerousGetPinnableReference()); - using (PinnedBuffer tempBuf = new PinnedBuffer( - unpackedRawCount + Vector.Count)) + using (Buffer tempBuf = new Buffer( + unpackedRawCount + Vector.Count)) { - uint* tPtr = (uint*)tempBuf.Pointer; uint[] temp = tempBuf.Array; float[] fTemp = Unsafe.As(temp); - UnpackedRGBA* dst = (UnpackedRGBA*)tPtr; - for (; src < srcEnd; src++, dst++) + ref UnpackedRGBA tempBase = ref Unsafe.As(ref tempBuf[0]); + + for (int i = 0; i < count; i++) { + uint sVal = Unsafe.Add(ref src, i); + ref UnpackedRGBA dst = ref Unsafe.Add(ref tempBase, i); + // This call is the bottleneck now: - dst->Load(*src); + dst.Load(sVal); } for (int i = 0; i < unpackedRawCount; i += vecSize) @@ -90,17 +93,17 @@ namespace ImageSharp vf.CopyTo(fTemp, i); } - BufferSpan.Copy(tempBuf, (BufferSpan)destVectors, unpackedRawCount); + BufferSpan.Copy(tempBuf.Span.AsBytes(), destVectors.AsBytes(), unpackedRawCount * sizeof(uint)); } } /// - internal override void ToVector4(BufferSpan sourceColors, BufferSpan destVectors, int count) + internal override void ToVector4(BufferSpan sourceColor32s, BufferSpan destVectors, int count) { if (count < 256 || !Vector.IsHardwareAccelerated) { // Doesn't worth to bother with SIMD: - base.ToVector4(sourceColors, destVectors, count); + base.ToVector4(sourceColor32s, destVectors, count); return; } @@ -110,143 +113,193 @@ namespace ImageSharp if (alignedCount > 0) { - ToVector4SimdAligned(sourceColors, destVectors, alignedCount); + ToVector4SimdAligned(sourceColor32s, destVectors, alignedCount); } if (remainder > 0) { - sourceColors = sourceColors.Slice(alignedCount); + sourceColor32s = sourceColor32s.Slice(alignedCount); destVectors = destVectors.Slice(alignedCount); - base.ToVector4(sourceColors, destVectors, remainder); + base.ToVector4(sourceColor32s, destVectors, remainder); } } /// - internal override unsafe void PackFromXyzBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) + internal override void PackFromXyzBytes( + BufferSpan sourceBytes, + BufferSpan destColor32s, + int count) { - byte* source = (byte*)sourceBytes; - byte* destination = (byte*)destColors; + ref RGB24 sourceRef = ref Unsafe.As(ref sourceBytes.DangerousGetPinnableReference()); + ref Color32 destRef = ref destColor32s.DangerousGetPinnableReference(); - for (int x = 0; x < count; x++) + for (int i = 0; i < count; i++) { - Unsafe.Write(destination, (uint)(*source << 0 | *(source + 1) << 8 | *(source + 2) << 16 | 255 << 24)); + ref RGB24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Color32 dp = ref Unsafe.Add(ref destRef, i); - source += 3; - destination += 4; + Unsafe.As(ref dp) = sp; + dp.A = 255; } } /// - internal override unsafe void ToXyzBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) + internal override void ToXyzBytes(BufferSpan sourceColor32s, BufferSpan destBytes, int count) { - byte* source = (byte*)sourceColors; - byte* destination = (byte*)destBytes; + ref Color32 sourceRef = ref sourceColor32s.DangerousGetPinnableReference(); + ref RGB24 destRef = ref Unsafe.As(ref destBytes.DangerousGetPinnableReference()); - for (int x = 0; x < count; x++) + for (int i = 0; i < count; i++) { - *destination = *(source + 0); - *(destination + 1) = *(source + 1); - *(destination + 2) = *(source + 2); + ref Color32 sp = ref Unsafe.Add(ref sourceRef, i); + ref RGB24 dp = ref Unsafe.Add(ref destRef, i); - source += 4; - destination += 3; + dp = Unsafe.As(ref sp); } } /// - internal override void PackFromXyzwBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) + internal override unsafe void PackFromXyzwBytes( + BufferSpan sourceBytes, + BufferSpan destColor32s, + int count) { - BufferSpan.Copy(sourceBytes, destColors, count); + BufferSpan.Copy(sourceBytes, destColor32s.AsBytes(), count * sizeof(Color32)); } /// - internal override void ToXyzwBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) + internal override unsafe void ToXyzwBytes(BufferSpan sourceColor32s, BufferSpan destBytes, int count) { - BufferSpan.Copy(sourceColors, destBytes, count); + BufferSpan.Copy(sourceColor32s.AsBytes(), destBytes, count * sizeof(Color32)); } /// - internal override unsafe void PackFromZyxBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) + internal override void PackFromZyxBytes( + BufferSpan sourceBytes, + BufferSpan destColor32s, + int count) { - byte* source = (byte*)sourceBytes; - byte* destination = (byte*)destColors; + ref RGB24 sourceRef = ref Unsafe.As(ref sourceBytes.DangerousGetPinnableReference()); + ref Color32 destRef = ref destColor32s.DangerousGetPinnableReference(); - for (int x = 0; x < count; x++) + for (int i = 0; i < count; i++) { - Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | 255 << 24)); + ref RGB24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Color32 dp = ref Unsafe.Add(ref destRef, i); - source += 3; - destination += 4; + Unsafe.As(ref dp) = sp.ToZyx(); + dp.A = 255; } } /// - internal override unsafe void ToZyxBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) + internal override void ToZyxBytes( + BufferSpan sourceColor32s, + BufferSpan destBytes, + int count) { - byte* source = (byte*)sourceColors; - byte* destination = (byte*)destBytes; + ref Color32 sourceRef = ref sourceColor32s.DangerousGetPinnableReference(); + ref RGB24 destRef = ref Unsafe.As(ref destBytes.DangerousGetPinnableReference()); - for (int x = 0; x < count; x++) + for (int i = 0; i < count; i++) { - *destination = *(source + 2); - *(destination + 1) = *(source + 1); - *(destination + 2) = *(source + 0); + ref Color32 sp = ref Unsafe.Add(ref sourceRef, i); + ref RGB24 dp = ref Unsafe.Add(ref destRef, i); - source += 4; - destination += 3; + dp = Unsafe.As(ref sp).ToZyx(); } } /// - internal override unsafe void PackFromZyxwBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) + internal override void PackFromZyxwBytes( + BufferSpan sourceBytes, + BufferSpan destColor32s, + int count) { - byte* source = (byte*)sourceBytes; - byte* destination = (byte*)destColors; + ref RGBA32 sourceRef = ref Unsafe.As(ref sourceBytes.DangerousGetPinnableReference()); + ref Color32 destRef = ref destColor32s.DangerousGetPinnableReference(); - for (int x = 0; x < count; x++) + for (int i = 0; i < count; i++) { - Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | *(source + 3) << 24)); - - source += 4; - destination += 4; + ref RGBA32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Color32 dp = ref Unsafe.Add(ref destRef, i); + RGBA32 zyxw = sp.ToZyxw(); + dp = Unsafe.As(ref zyxw); } } /// - internal override unsafe void ToZyxwBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) + internal override void ToZyxwBytes( + BufferSpan sourceColor32s, + BufferSpan destBytes, + int count) { - byte* source = (byte*)sourceColors; - byte* destination = (byte*)destBytes; + ref Color32 sourceRef = ref sourceColor32s.DangerousGetPinnableReference(); + ref RGBA32 destRef = ref Unsafe.As(ref destBytes.DangerousGetPinnableReference()); - for (int x = 0; x < count; x++) + for (int i = 0; i < count; i++) { - *destination = *(source + 2); - *(destination + 1) = *(source + 1); - *(destination + 2) = *(source + 0); - *(destination + 3) = *(source + 3); - - source += 4; - destination += 4; + ref RGBA32 sp = ref Unsafe.As(ref Unsafe.Add(ref sourceRef, i)); + ref RGBA32 dp = ref Unsafe.Add(ref destRef, i); + dp = sp.ToZyxw(); } } + /// + /// Helper struct to manipulate 3-byte RGB data. + /// + [StructLayout(LayoutKind.Sequential)] + private struct RGB24 + { + private byte x; + + private byte y; + + private byte z; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RGB24 ToZyx() => new RGB24 { x = this.z, y = this.y, z = this.x }; + } + + /// + /// Helper struct to manipulate 4-byte RGBA data. + /// + [StructLayout(LayoutKind.Sequential)] + private struct RGBA32 + { + private byte x; + + private byte y; + + private byte z; + + private byte w; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RGBA32 ToZyxw() => new RGBA32 { x = this.z, y = this.y, z = this.x, w = this.w }; + } + /// /// Value type to store -s unpacked into multiple -s. /// + [StructLayout(LayoutKind.Sequential)] private struct UnpackedRGBA { private uint r; + private uint g; + private uint b; + private uint a; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Load(uint p) { this.r = p; - this.g = p >> Color32.GreenShift; - this.b = p >> Color32.BlueShift; - this.a = p >> Color32.AlphaShift; + this.g = p >> GreenShift; + this.b = p >> BlueShift; + this.a = p >> AlphaShift; } } } diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs index 7b6169f9c..ccb1c2261 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -13,14 +13,9 @@ namespace ImageSharp /// for pixel buffers of type . /// /// The pixel format. - public unsafe class BulkPixelOperations + public class BulkPixelOperations where TColor : struct, IPixel { - /// - /// The size of in bytes - /// - private static readonly int ColorSize = Unsafe.SizeOf(); - /// /// Gets the global instance for the pixel type /// @@ -37,18 +32,14 @@ namespace ImageSharp BufferSpan destColors, int count) { - Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; - byte* dp = (byte*)destColors; + ref Vector4 sourceRef = ref sourceVectors.DangerousGetPinnableReference(); + ref TColor destRef = ref destColors.DangerousGetPinnableReference(); for (int i = 0; i < count; i++) { - Vector4 v = Unsafe.Read(sp); - TColor c = default(TColor); - c.PackFromVector4(v); - Unsafe.Write(dp, c); - - sp++; - dp += ColorSize; + ref Vector4 sp = ref Unsafe.Add(ref sourceRef, i); + ref TColor dp = ref Unsafe.Add(ref destRef, i); + dp.PackFromVector4(sp); } } @@ -63,15 +54,14 @@ namespace ImageSharp BufferSpan destVectors, int count) { - byte* sp = (byte*)sourceColors; - Vector4* dp = (Vector4*)destVectors.PointerAtOffset; + ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference(); + ref Vector4 destRef = ref destVectors.DangerousGetPinnableReference(); for (int i = 0; i < count; i++) { - TColor c = Unsafe.Read(sp); - *dp = c.ToVector4(); - sp += ColorSize; - dp++; + ref TColor sp = ref Unsafe.Add(ref sourceRef, i); + ref Vector4 dp = ref Unsafe.Add(ref destRef, i); + dp = sp.ToVector4(); } } @@ -86,16 +76,18 @@ namespace ImageSharp BufferSpan destColors, int count) { - byte* sp = (byte*)sourceBytes; - byte* dp = (byte*)destColors.PointerAtOffset; + ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference(); + ref TColor destRef = ref destColors.DangerousGetPinnableReference(); for (int i = 0; i < count; i++) { - TColor c = default(TColor); - c.PackFromBytes(sp[0], sp[1], sp[2], 255); - Unsafe.Write(dp, c); - sp += 3; - dp += ColorSize; + int i3 = i * 3; + ref TColor dp = ref Unsafe.Add(ref destRef, i); + dp.PackFromBytes( + Unsafe.Add(ref sourceRef, i3), + Unsafe.Add(ref sourceRef, i3 + 1), + Unsafe.Add(ref sourceRef, i3 + 2), + 255); } } @@ -107,14 +99,13 @@ namespace ImageSharp /// The number of pixels to convert. internal virtual void ToXyzBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { - byte* sp = (byte*)sourceColors; + ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference(); byte[] dest = destBytes.Array; - for (int i = destBytes.Start; i < destBytes.Start + (count * 3); i += 3) + for (int i = 0; i < count; i++) { - TColor c = Unsafe.Read(sp); - c.ToXyzBytes(dest, i); - sp += ColorSize; + ref TColor sp = ref Unsafe.Add(ref sourceRef, i); + sp.ToXyzBytes(dest, destBytes.Start + (i * 3)); } } @@ -129,16 +120,18 @@ namespace ImageSharp BufferSpan destColors, int count) { - byte* sp = (byte*)sourceBytes; - byte* dp = (byte*)destColors.PointerAtOffset; + ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference(); + ref TColor destRef = ref destColors.DangerousGetPinnableReference(); for (int i = 0; i < count; i++) { - TColor c = default(TColor); - c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]); - Unsafe.Write(dp, c); - sp += 4; - dp += ColorSize; + int i4 = i * 4; + ref TColor dp = ref Unsafe.Add(ref destRef, i); + dp.PackFromBytes( + Unsafe.Add(ref sourceRef, i4), + Unsafe.Add(ref sourceRef, i4 + 1), + Unsafe.Add(ref sourceRef, i4 + 2), + Unsafe.Add(ref sourceRef, i4 + 3)); } } @@ -153,14 +146,13 @@ namespace ImageSharp BufferSpan destBytes, int count) { - byte* sp = (byte*)sourceColors; + ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference(); byte[] dest = destBytes.Array; - for (int i = destBytes.Start; i < destBytes.Start + (count * 4); i += 4) + for (int i = 0; i < count; i++) { - TColor c = Unsafe.Read(sp); - c.ToXyzwBytes(dest, i); - sp += ColorSize; + ref TColor sp = ref Unsafe.Add(ref sourceRef, i); + sp.ToXyzwBytes(dest, destBytes.Start + (i * 4)); } } @@ -175,16 +167,18 @@ namespace ImageSharp BufferSpan destColors, int count) { - byte* sp = (byte*)sourceBytes; - byte* dp = (byte*)destColors.PointerAtOffset; + ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference(); + ref TColor destRef = ref destColors.DangerousGetPinnableReference(); for (int i = 0; i < count; i++) { - TColor c = default(TColor); - c.PackFromBytes(sp[2], sp[1], sp[0], 255); - Unsafe.Write(dp, c); - sp += 3; - dp += ColorSize; + int i3 = i * 3; + ref TColor dp = ref Unsafe.Add(ref destRef, i); + dp.PackFromBytes( + Unsafe.Add(ref sourceRef, i3 + 2), + Unsafe.Add(ref sourceRef, i3 + 1), + Unsafe.Add(ref sourceRef, i3), + 255); } } @@ -196,14 +190,13 @@ namespace ImageSharp /// The number of pixels to convert. internal virtual void ToZyxBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { - byte* sp = (byte*)sourceColors; + ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference(); byte[] dest = destBytes.Array; - for (int i = destBytes.Start; i < destBytes.Start + (count * 3); i += 3) + for (int i = 0; i < count; i++) { - TColor c = Unsafe.Read(sp); - c.ToZyxBytes(dest, i); - sp += ColorSize; + ref TColor sp = ref Unsafe.Add(ref sourceRef, i); + sp.ToZyxBytes(dest, destBytes.Start + (i * 3)); } } @@ -218,16 +211,18 @@ namespace ImageSharp BufferSpan destColors, int count) { - byte* sp = (byte*)sourceBytes; - byte* dp = (byte*)destColors.PointerAtOffset; + ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference(); + ref TColor destRef = ref destColors.DangerousGetPinnableReference(); for (int i = 0; i < count; i++) { - TColor c = default(TColor); - c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]); - Unsafe.Write(dp, c); - sp += 4; - dp += ColorSize; + int i4 = i * 4; + ref TColor dp = ref Unsafe.Add(ref destRef, i); + dp.PackFromBytes( + Unsafe.Add(ref sourceRef, i4 + 2), + Unsafe.Add(ref sourceRef, i4 + 1), + Unsafe.Add(ref sourceRef, i4), + Unsafe.Add(ref sourceRef, i4 + 3)); } } @@ -242,14 +237,13 @@ namespace ImageSharp BufferSpan destBytes, int count) { - byte* sp = (byte*)sourceColors; + ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference(); byte[] dest = destBytes.Array; - for (int i = destBytes.Start; i < destBytes.Start + (count * 4); i += 4) + for (int i = 0; i < count; i++) { - TColor c = Unsafe.Read(sp); - c.ToZyxwBytes(dest, i); - sp += ColorSize; + ref TColor sp = ref Unsafe.Add(ref sourceRef, i); + sp.ToZyxwBytes(dest, destBytes.Start + (i * 4)); } } } diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/Buffer.cs similarity index 68% rename from src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs rename to src/ImageSharp/Common/Memory/Buffer.cs index 611688c99..c26b8ea18 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/Buffer.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -11,13 +11,18 @@ namespace ImageSharp using System.Runtime.InteropServices; /// - /// Manages a pinned buffer of value type objects as a Disposable resource. + /// Manages a buffer of value type objects as a Disposable resource. /// The backing array is either pooled or comes from the outside. /// /// The value type. - internal class PinnedBuffer : IDisposable + internal class Buffer : IDisposable where T : struct { + /// + /// A pointer to the first element of when pinned. + /// + private IntPtr pointer; + /// /// A handle that allows to access the managed as an unmanaged memory by pinning. /// @@ -25,40 +30,38 @@ namespace ImageSharp /// /// A value indicating wheter should be returned to - /// when disposing this instance. + /// when disposing this instance. /// private bool isPoolingOwner; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The desired count of elements. (Minimum size for ) - public PinnedBuffer(int length) + public Buffer(int length) { this.Length = length; this.Array = PixelDataPool.Rent(length); this.isPoolingOwner = true; - this.Pin(); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The array to pin. - public PinnedBuffer(T[] array) + public Buffer(T[] array) { this.Length = array.Length; this.Array = array; this.isPoolingOwner = false; - this.Pin(); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The array to pin. /// The count of "relevant" elements in 'array'. - public PinnedBuffer(T[] array, int length) + public Buffer(T[] array, int length) { if (array.Length < length) { @@ -68,19 +71,18 @@ namespace ImageSharp this.Length = length; this.Array = array; this.isPoolingOwner = false; - this.Pin(); } /// - /// Finalizes an instance of the class. + /// Finalizes an instance of the class. /// - ~PinnedBuffer() + ~Buffer() { this.UnPin(); } /// - /// Gets a value indicating whether this instance is disposed, or has lost ownership of . + /// Gets a value indicating whether this instance is disposed, or has lost ownership of . /// public bool IsDisposedOrLostArrayOwnership { get; private set; } @@ -94,11 +96,6 @@ namespace ImageSharp /// public T[] Array { get; private set; } - /// - /// Gets a pointer to the pinned . - /// - public IntPtr Pointer { get; private set; } - /// /// Gets a to the backing buffer. /// @@ -109,37 +106,35 @@ namespace ImageSharp /// /// The index /// The reference to the specified element - public unsafe ref T this[int index] + public ref T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); - - byte* ptr = (byte*)this.Pointer + BufferSpan.SizeOf(index); - return ref Unsafe.AsRef(ptr); + return ref this.Array[index]; } } /// - /// Converts to an . + /// Converts to an . /// - /// The to convert. + /// The to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe implicit operator BufferSpan(PinnedBuffer buffer) + public static implicit operator BufferSpan(Buffer buffer) { - return new BufferSpan(buffer.Array, (void*)buffer.Pointer, 0, buffer.Length); + return new BufferSpan(buffer.Array, 0, buffer.Length); } /// - /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// Creates a clean instance of initializing it's elements with 'default(T)'. /// /// The desired count of elements. (Minimum size for ) - /// The instance + /// The instance [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PinnedBuffer CreateClean(int count) + public static Buffer CreateClean(int count) { - PinnedBuffer buffer = new PinnedBuffer(count); + Buffer buffer = new Buffer(count); buffer.Clear(); return buffer; } @@ -150,9 +145,9 @@ namespace ImageSharp /// The start /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferSpan Slice(int start) + public BufferSpan Slice(int start) { - return new BufferSpan(this.Array, (void*)this.Pointer, start, this.Length - start); + return new BufferSpan(this.Array, start, this.Length - start); } /// @@ -162,13 +157,13 @@ namespace ImageSharp /// The length of the slice /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferSpan Slice(int start, int length) + public BufferSpan Slice(int start, int length) { - return new BufferSpan(this.Array, (void*)this.Pointer, start, length); + return new BufferSpan(this.Array, start, length); } /// - /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. + /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() @@ -199,11 +194,11 @@ namespace ImageSharp /// /// The unpinned [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] UnPinAndTakeArrayOwnership() + public T[] TakeArrayOwnership() { if (this.IsDisposedOrLostArrayOwnership) { - throw new InvalidOperationException("UnPinAndTakeArrayOwnership() is invalid: either PinnedBuffer is disposed or UnPinAndTakeArrayOwnership() has been called multiple times!"); + throw new InvalidOperationException("TakeArrayOwnership() is invalid: either Buffer is disposed or TakeArrayOwnership() has been called multiple times!"); } this.IsDisposedOrLostArrayOwnership = true; @@ -220,17 +215,29 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { - ((BufferSpan)this).Clear(); + this.Span.Clear(); } /// /// Pins . /// + /// The pinned pointer [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Pin() + public IntPtr Pin() { - this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); - this.Pointer = this.handle.AddrOfPinnedObject(); + if (this.IsDisposedOrLostArrayOwnership) + { + throw new InvalidOperationException( + "Pin() is invalid on a buffer with IsDisposedOrLostArrayOwnership == true!"); + } + + if (this.pointer == IntPtr.Zero) + { + this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); + this.pointer = this.handle.AddrOfPinnedObject(); + } + + return this.pointer; } /// @@ -239,13 +246,13 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UnPin() { - if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) + if (this.pointer == IntPtr.Zero || !this.handle.IsAllocated) { return; } this.handle.Free(); - this.Pointer = IntPtr.Zero; + this.pointer = IntPtr.Zero; } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/Buffer2D.cs similarity index 69% rename from src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs rename to src/ImageSharp/Common/Memory/Buffer2D.cs index 3ff174c5d..c4eb50700 100644 --- a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/Buffer2D.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -9,19 +9,19 @@ namespace ImageSharp using System.Runtime.CompilerServices; /// - /// Represents a pinned buffer of value type objects + /// Represents a buffer of value type objects /// interpreted as a 2D region of x elements. /// /// The value type. - internal class PinnedImageBuffer : PinnedBuffer, IPinnedImageBuffer + internal class Buffer2D : Buffer, IBuffer2D where T : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The number of elements in a row /// The number of rows - public PinnedImageBuffer(int width, int height) + public Buffer2D(int width, int height) : base(width * height) { this.Width = width; @@ -29,12 +29,12 @@ namespace ImageSharp } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The array to pin /// The number of elements in a row /// The number of rows - public PinnedImageBuffer(T[] array, int width, int height) + public Buffer2D(T[] array, int width, int height) : base(array, width * height) { this.Width = width; @@ -63,14 +63,14 @@ namespace ImageSharp } /// - /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// Creates a clean instance of initializing it's elements with 'default(T)'. /// /// The number of elements in a row /// The number of rows - /// The instance - public static PinnedImageBuffer CreateClean(int width, int height) + /// The instance + public static Buffer2D CreateClean(int width, int height) { - PinnedImageBuffer buffer = new PinnedImageBuffer(width, height); + Buffer2D buffer = new Buffer2D(width, height); buffer.Clear(); return buffer; } diff --git a/src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs b/src/ImageSharp/Common/Memory/Buffer2DExtensions.cs similarity index 78% rename from src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs rename to src/ImageSharp/Common/Memory/Buffer2DExtensions.cs index fcd5b3831..4c3cc4d40 100644 --- a/src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs +++ b/src/ImageSharp/Common/Memory/Buffer2DExtensions.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -9,9 +9,9 @@ namespace ImageSharp using System.Runtime.CompilerServices; /// - /// Defines extension methods for . + /// Defines extension methods for . /// - internal static class PinnedImageBufferExtensions + internal static class Buffer2DExtensions { /// /// Gets a to the row 'y' beginning from the pixel at 'x'. @@ -22,7 +22,7 @@ namespace ImageSharp /// The element type /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BufferSpan GetRowSpan(this IPinnedImageBuffer buffer, int x, int y) + public static BufferSpan GetRowSpan(this IBuffer2D buffer, int x, int y) where T : struct { return buffer.Span.Slice((y * buffer.Width) + x, buffer.Width - x); @@ -36,7 +36,7 @@ namespace ImageSharp /// The element type /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BufferSpan GetRowSpan(this IPinnedImageBuffer buffer, int y) + public static BufferSpan GetRowSpan(this IBuffer2D buffer, int y) where T : struct { return buffer.Span.Slice(y * buffer.Width, buffer.Width); diff --git a/src/ImageSharp/Common/Memory/BufferSpan.cs b/src/ImageSharp/Common/Memory/BufferSpan.cs index 42a6fbc6b..c51c110be 100644 --- a/src/ImageSharp/Common/Memory/BufferSpan.cs +++ b/src/ImageSharp/Common/Memory/BufferSpan.cs @@ -6,7 +6,6 @@ namespace ImageSharp { using System; - using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -15,60 +14,49 @@ namespace ImageSharp /// internal static class BufferSpan { - /// - /// It's worth to use Marshal.Copy() or Buffer.BlockCopy() over this size. - /// - private const int ByteCountThreshold = 1024; - /// /// Copy 'count' number of elements of the same type from 'source' to 'dest' /// /// The element type. - /// The input + /// The to copy elements from. /// The destination . /// The number of elements to copy [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(BufferSpan source, BufferSpan destination, int count) + public static unsafe void Copy(BufferSpan source, BufferSpan destination, int count) where T : struct { - CopyImpl(source, destination, count); - } + DebugGuard.MustBeLessThanOrEqualTo(count, source.Length, nameof(count)); + DebugGuard.MustBeLessThanOrEqualTo(count, destination.Length, nameof(count)); - /// - /// Copy 'countInSource' elements of from 'source' into the raw byte buffer 'destination'. - /// - /// The element type. - /// The source buffer of elements to copy from. - /// The destination buffer. - /// The number of elements to copy from 'source' - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(BufferSpan source, BufferSpan destination, int countInSource) - where T : struct - { - CopyImpl(source, destination, countInSource); + ref byte srcRef = ref Unsafe.As(ref source.DangerousGetPinnableReference()); + ref byte destRef = ref Unsafe.As(ref destination.DangerousGetPinnableReference()); + + int byteCount = Unsafe.SizeOf() * count; + + // TODO: Use unfixed Unsafe.CopyBlock(ref T, ref T, int) for small blocks, when it gets available! + fixed (byte* pSrc = &srcRef) + fixed (byte* pDest = &destRef) + { +#if NETSTANDARD1_1 + Unsafe.CopyBlock(pDest, pSrc, (uint)byteCount); +#else + int destLength = destination.Length * Unsafe.SizeOf(); + Buffer.MemoryCopy(pSrc, pDest, destLength, byteCount); +#endif + } } /// - /// Copy 'countInDest' number of elements into 'dest' from a raw byte buffer defined by 'source'. + /// Copy all elements of 'source' into 'destination'. /// /// The element type. - /// The raw source buffer to copy from"/> - /// The destination buffer"/> - /// The number of elements to copy. + /// The to copy elements from. + /// The destination . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferSpan source, BufferSpan destination, int countInDest) + public static void Copy(BufferSpan source, BufferSpan destination) where T : struct { - int byteCount = SizeOf(countInDest); - - if (byteCount > (int)ByteCountThreshold) - { - Marshal.Copy(source.Array, source.Start, destination.PointerAtOffset, byteCount); - } - else - { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); - } + Copy(source, destination, source.Length); } /// @@ -91,39 +79,5 @@ namespace ImageSharp public static uint USizeOf(int count) where T : struct => (uint)SizeOf(count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void CopyImpl(BufferSpan source, BufferSpan destination, int count) - where T : struct - where TDest : struct - { - int byteCount = SizeOf(count); - - if (byteCount > ByteCountThreshold) - { - if (Unsafe.SizeOf() == sizeof(long)) - { - Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); - return; - } - else if (Unsafe.SizeOf() == sizeof(int)) - { - Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); - return; - } - else if (Unsafe.SizeOf() == sizeof(short)) - { - Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); - return; - } - else if (Unsafe.SizeOf() == sizeof(byte)) - { - Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); - return; - } - } - - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs index 8ef88814c..1b0bef314 100644 --- a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Represents a contiguous region of a pinned managed array. - /// The array is usually owned by a instance. + /// The array is usually owned by a instance. /// /// /// is very similar to corefx System.Span<T>, and we try to maintain a compatible API. @@ -30,13 +30,12 @@ namespace ImageSharp /// Initializes a new instance of the struct from a pinned array and an start. /// /// The pinned array - /// Pointer to the beginning of the array /// The index at which to begin the span. /// The length [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferSpan(T[] array, void* pointerToArray, int start, int length) + public BufferSpan(T[] array, int start, int length) { - GuardArrayAndPointer(array, pointerToArray); + GuardArray(array); DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start)); DebugGuard.MustBeLessThanOrEqualTo(length, array.Length - start, nameof(length)); @@ -44,45 +43,40 @@ namespace ImageSharp this.Array = array; this.Length = length; this.Start = start; - this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * start); } /// /// Initializes a new instance of the struct from a pinned array and an start. /// /// The pinned array - /// Pointer to the beginning of the array /// The index at which to begin the span. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferSpan(T[] array, void* pointerToArray, int start) + public BufferSpan(T[] array, int start) { - GuardArrayAndPointer(array, pointerToArray); + GuardArray(array); DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start)); this.Array = array; this.Length = array.Length - start; this.Start = start; - this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * start); } /// /// Initializes a new instance of the struct from a pinned array. /// /// The pinned array - /// Pointer to the start of 'array' [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferSpan(T[] array, void* pointerToArray) + public BufferSpan(T[] array) { - GuardArrayAndPointer(array, pointerToArray); + GuardArray(array); this.Array = array; this.Start = 0; this.Length = array.Length; - this.PointerAtOffset = (IntPtr)pointerToArray; } /// - /// Gets the backing array + /// Gets the backing array. /// public T[] Array { get; private set; } @@ -101,11 +95,6 @@ namespace ImageSharp /// public int ByteOffset => this.Start * Unsafe.SizeOf(); - /// - /// Gets the pointer to the offseted array position - /// - public IntPtr PointerAtOffset { get; private set; } - /// /// Returns a reference to specified element of the span. /// @@ -117,45 +106,36 @@ namespace ImageSharp get { DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); - - byte* ptr = (byte*)this.PointerAtOffset + BufferSpan.SizeOf(index); - return ref Unsafe.AsRef(ptr); + ref T startRef = ref this.DangerousGetPinnableReference(); + return ref Unsafe.Add(ref startRef, index); } } /// - /// Convertes instance to a raw 'void*' pointer - /// - /// The to convert - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator void*(BufferSpan bufferSpan) - { - return (void*)bufferSpan.PointerAtOffset; - } - - /// - /// Converts instance to a raw 'byte*' pointer + /// Converts generic to a of bytes + /// setting it's and to correct values. /// - /// The to convert + /// The span of bytes [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator byte*(BufferSpan bufferSpan) + public BufferSpan AsBytes() { - return (byte*)bufferSpan.PointerAtOffset; + BufferSpan result = default(BufferSpan); + result.Array = Unsafe.As(this.Array); + result.Start = this.Start * Unsafe.SizeOf(); + result.Length = this.Length * Unsafe.SizeOf(); + return result; } /// - /// Converts generic to a of bytes - /// setting it's and to correct values. + /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element + /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. /// - /// The to convert + /// The reference to the 0th element [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator BufferSpan(BufferSpan source) + public ref T DangerousGetPinnableReference() { - BufferSpan result = default(BufferSpan); - result.Array = Unsafe.As(source.Array); - result.Start = source.Start * Unsafe.SizeOf(); - result.PointerAtOffset = source.PointerAtOffset; - return result; + ref T origin = ref this.Array[0]; + return ref Unsafe.Add(ref origin, this.Start); } /// @@ -171,7 +151,6 @@ namespace ImageSharp BufferSpan result = default(BufferSpan); result.Array = this.Array; result.Start = this.Start + start; - result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * start); result.Length = this.Length - start; return result; } @@ -191,7 +170,6 @@ namespace ImageSharp BufferSpan result = default(BufferSpan); result.Array = this.Array; result.Start = this.Start + start; - result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * start); result.Length = length; return result; } @@ -205,14 +183,8 @@ namespace ImageSharp { DebugGuard.MustBeLessThanOrEqualTo(count, this.Length, nameof(count)); - if (count < 256) - { - Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferSpan.USizeOf(count)); - } - else - { - System.Array.Clear(this.Array, this.Start, count); - } + // TODO: Use Unsafe.InitBlock(ref T) for small arrays, when it get's official + System.Array.Clear(this.Array, this.Start, count); } /// @@ -225,13 +197,9 @@ namespace ImageSharp } [Conditional("DEBUG")] - private static void GuardArrayAndPointer(T[] array, void* pointerToArray) + private static void GuardArray(T[] array) { DebugGuard.NotNull(array, nameof(array)); - DebugGuard.IsFalse( - pointerToArray == (void*)0, - nameof(pointerToArray), - "pointerToArray should not be null pointer!"); } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/IBuffer2D.cs similarity index 86% rename from src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs rename to src/ImageSharp/Common/Memory/IBuffer2D.cs index 374cbed99..1ba08e49f 100644 --- a/src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/IBuffer2D.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,7 +10,7 @@ namespace ImageSharp /// interpreted as a 2D region of x elements. /// /// The value type. - internal interface IPinnedImageBuffer + internal interface IBuffer2D where T : struct { /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 66f400c01..c3cf75a0f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -307,7 +307,8 @@ namespace ImageSharp.Formats rgbBytes.Reset(); pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x); - byte* data = (byte*)rgbBytes.DataPointer; + ref byte data0 = ref rgbBytes.Bytes[0]; + int dataIdx = 0; for (int j = 0; j < 8; j++) { @@ -315,9 +316,9 @@ namespace ImageSharp.Formats for (int i = 0; i < 8; i++) { // Convert returned bytes into the YCbCr color space. Assume RGBA - int r = data[0]; - int g = data[1]; - int b = data[2]; + int r = Unsafe.Add(ref data0, dataIdx); + int g = Unsafe.Add(ref data0, dataIdx + 1); + int b = Unsafe.Add(ref data0, dataIdx + 2); // Speed up the algorithm by removing floating point calculation // Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result @@ -343,7 +344,7 @@ namespace ImageSharp.Formats cbBlockRaw[index] = cb; crBlockRaw[index] = cr; - data += 3; + dataIdx += 3; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Utils/JpegUtils.cs b/src/ImageSharp/Formats/Jpeg/Utils/JpegUtils.cs index dd96985d9..ace309812 100644 --- a/src/ImageSharp/Formats/Jpeg/Utils/JpegUtils.cs +++ b/src/ImageSharp/Formats/Jpeg/Utils/JpegUtils.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats.Jpg { using System; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// /// Jpeg specific utilities and extension methods @@ -33,19 +34,6 @@ namespace ImageSharp.Formats.Jpg StretchPixels(dest, stretchFromX, stretchFromY); } - /// - /// Copy an RGB value - /// - /// Source pointer - /// Destination pointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyRgb(byte* source, byte* dest) - { - *dest++ = *source++; // R - *dest++ = *source++; // G - *dest = *source; // B - } - // Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsInvalidStretchStartingPosition(PixelArea area, int fromX, int fromY) @@ -64,31 +52,38 @@ namespace ImageSharp.Formats.Jpg for (int y = 0; y < fromY; y++) { - byte* ptrBase = (byte*)area.DataPointer + (y * area.RowStride); + ref RGB24 ptrBase = ref GetRowStart(area, y); for (int x = fromX; x < area.Width; x++) { - byte* prevPtr = ptrBase + ((x - 1) * 3); - byte* currPtr = ptrBase + (x * 3); - - CopyRgb(prevPtr, currPtr); + // Copy the left neighbour pixel to the current one + Unsafe.Add(ref ptrBase, x) = Unsafe.Add(ref ptrBase, x - 1); } } for (int y = fromY; y < area.Height; y++) { - byte* currBase = (byte*)area.DataPointer + (y * area.RowStride); - byte* prevBase = (byte*)area.DataPointer + ((y - 1) * area.RowStride); + ref RGB24 currBase = ref GetRowStart(area, y); + ref RGB24 prevBase = ref GetRowStart(area, y - 1); for (int x = 0; x < area.Width; x++) { - int x3 = 3 * x; - byte* currPtr = currBase + x3; - byte* prevPtr = prevBase + x3; - - CopyRgb(prevPtr, currPtr); + // Copy the top neighbour pixel to the current one + Unsafe.Add(ref currBase, x) = Unsafe.Add(ref prevBase, x); } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ref RGB24 GetRowStart(PixelArea area, int y) + where TColor : struct, IPixel + { + return ref Unsafe.As(ref area.GetRowSpan(y).DangerousGetPinnableReference()); + } + + [StructLayout(LayoutKind.Sequential, Size = 3)] + private struct RGB24 + { + } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index f5393cfb3..fb3613adf 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -15,14 +15,9 @@ namespace ImageSharp /// Provides per-pixel access to generic pixels. /// /// The pixel format. - public sealed unsafe class PixelAccessor : IDisposable, IPinnedImageBuffer + public sealed class PixelAccessor : IDisposable, IBuffer2D where TColor : struct, IPixel { - /// - /// The position of the first pixel in the image. - /// - private byte* pixelsBase; - /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -35,9 +30,9 @@ namespace ImageSharp private bool isDisposed; /// - /// The containing the pixel data. + /// The containing the pixel data. /// - private PinnedImageBuffer pixelBuffer; + private Buffer2D pixelBuffer; /// /// Initializes a new instance of the class. @@ -59,7 +54,7 @@ namespace ImageSharp /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. public PixelAccessor(int width, int height) - : this(width, height, PinnedImageBuffer.CreateClean(width, height)) + : this(width, height, Buffer2D.CreateClean(width, height)) { } @@ -69,7 +64,7 @@ namespace ImageSharp /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. /// The pixel buffer. - private PixelAccessor(int width, int height, PinnedImageBuffer pixels) + private PixelAccessor(int width, int height, Buffer2D pixels) { Guard.NotNull(pixels, nameof(pixels)); Guard.MustBeGreaterThan(width, 0, nameof(width)); @@ -93,11 +88,6 @@ namespace ImageSharp /// public TColor[] PixelArray => this.pixelBuffer.Array; - /// - /// Gets the pointer to the pixel buffer. - /// - public IntPtr DataPointer => this.pixelBuffer.Pointer; - /// /// Gets the size of a single pixel in the number of bytes. /// @@ -124,7 +114,7 @@ namespace ImageSharp public ParallelOptions ParallelOptions { get; } /// - BufferSpan IPinnedImageBuffer.Span => this.pixelBuffer; + BufferSpan IBuffer2D.Span => this.pixelBuffer; private static BulkPixelOperations Operations => BulkPixelOperations.Instance; @@ -139,15 +129,13 @@ namespace ImageSharp get { this.CheckCoordinates(x, y); - - return Unsafe.Read(this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf())); + return this.PixelArray[(y * this.Width) + x]; } set { this.CheckCoordinates(x, y); - - Unsafe.Write(this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf()), value); + this.PixelArray[(y * this.Width) + x] = value; } } @@ -179,7 +167,7 @@ namespace ImageSharp /// public void Reset() { - Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height)); + this.pixelBuffer.Clear(); } /// @@ -251,7 +239,7 @@ namespace ImageSharp /// If is true then caller is responsible for ensuring is called. internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels) { - TColor[] oldPixels = this.pixelBuffer.UnPinAndTakeArrayOwnership(); + TColor[] oldPixels = this.pixelBuffer.TakeArrayOwnership(); this.SetPixelBufferUnsafe(width, height, pixels); return oldPixels; } @@ -262,9 +250,7 @@ namespace ImageSharp /// The target pixel buffer accessor. internal void CopyTo(PixelAccessor target) { - uint byteCount = (uint)(this.Width * this.Height * Unsafe.SizeOf()); - - Unsafe.CopyBlock(target.pixelsBase, this.pixelsBase, byteCount); + BufferSpan.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span); } /// @@ -424,7 +410,7 @@ namespace ImageSharp private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels) { - this.SetPixelBufferUnsafe(width, height, new PinnedImageBuffer(pixels, width, height)); + this.SetPixelBufferUnsafe(width, height, new Buffer2D(pixels, width, height)); } /// @@ -433,10 +419,9 @@ namespace ImageSharp /// The width. /// The height. /// The pixel buffer - private void SetPixelBufferUnsafe(int width, int height, PinnedImageBuffer pixels) + private void SetPixelBufferUnsafe(int width, int height, Buffer2D pixels) { this.pixelBuffer = pixels; - this.pixelsBase = (byte*)pixels.Pointer; this.Width = width; this.Height = height; diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index bd10c9b6b..176eb0a16 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -13,7 +13,7 @@ namespace ImageSharp /// Represents an area of generic pixels. /// /// The pixel format. - internal sealed unsafe class PixelArea : IDisposable + internal sealed class PixelArea : IDisposable where TColor : struct, IPixel { /// @@ -30,7 +30,7 @@ namespace ImageSharp /// /// The underlying buffer containing the raw pixel data. /// - private PinnedBuffer byteBuffer; + private Buffer byteBuffer; /// /// Initializes a new instance of the class. @@ -66,8 +66,7 @@ namespace ImageSharp this.RowStride = width * GetComponentCount(componentOrder); this.Length = bytes.Length; // TODO: Is this the right value for Length? - this.byteBuffer = new PinnedBuffer(bytes); - this.PixelBase = (byte*)this.byteBuffer.Pointer; + this.byteBuffer = new Buffer(bytes); } /// @@ -117,8 +116,7 @@ namespace ImageSharp this.RowStride = (width * GetComponentCount(componentOrder)) + padding; this.Length = this.RowStride * height; - this.byteBuffer = new PinnedBuffer(this.Length); - this.PixelBase = (byte*)this.byteBuffer.Pointer; + this.byteBuffer = new Buffer(this.Length); } /// @@ -136,21 +134,11 @@ namespace ImageSharp /// public ComponentOrder ComponentOrder { get; } - /// - /// Gets the pointer to the pixel buffer. - /// - public IntPtr DataPointer => this.byteBuffer.Pointer; - /// /// Gets the height. /// public int Height { get; } - /// - /// Gets the data pointer. - /// - public byte* PixelBase { get; private set; } - /// /// Gets the width of one row in the number of bytes. /// @@ -198,7 +186,7 @@ namespace ImageSharp /// public void Reset() { - Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowStride * this.Height)); + this.byteBuffer.Clear(); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index 99b143de6..4c43d654d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -41,7 +41,7 @@ namespace ImageSharp.Processing.Processors /// /// Gets an unsafe float* pointer to the beginning of . /// - public float* Ptr => (float*)this.Span.PointerAtOffset; + public ref float Ptr => ref this.Span.DangerousGetPinnableReference(); /// /// Gets the lenghth of the weights window @@ -56,19 +56,18 @@ namespace ImageSharp.Processing.Processors [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ComputeWeightedRowSum(BufferSpan rowSpan) { - float* horizontalValues = this.Ptr; + ref float horizontalValues = ref this.Ptr; int left = this.Left; - Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; - vecPtr += left; + ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left); // Destination color components Vector4 result = Vector4.Zero; for (int i = 0; i < this.Length; i++) { - float weight = horizontalValues[i]; - result += (*vecPtr) * weight; - vecPtr++; + float weight = Unsafe.Add(ref horizontalValues, i); + Vector4 v = Unsafe.Add(ref vecPtr, i); + result += v * weight; } return result; @@ -83,19 +82,18 @@ namespace ImageSharp.Processing.Processors [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ComputeExpandedWeightedRowSum(BufferSpan rowSpan) { - float* horizontalValues = this.Ptr; + ref float horizontalValues = ref this.Ptr; int left = this.Left; - Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; - vecPtr += left; + ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left); // Destination color components Vector4 result = Vector4.Zero; for (int i = 0; i < this.Length; i++) { - float weight = horizontalValues[i]; - result += (*vecPtr).Expand() * weight; - vecPtr++; + float weight = Unsafe.Add(ref horizontalValues, i); + Vector4 v = Unsafe.Add(ref vecPtr, i); + result += v.Expand() * weight; } return result; @@ -109,9 +107,9 @@ namespace ImageSharp.Processing.Processors /// The column position /// The weighted sum [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ComputeWeightedColumnSum(PinnedImageBuffer firstPassPixels, int x) + public Vector4 ComputeWeightedColumnSum(Buffer2D firstPassPixels, int x) { - float* verticalValues = this.Ptr; + ref float verticalValues = ref this.Ptr; int left = this.Left; // Destination color components @@ -119,7 +117,7 @@ namespace ImageSharp.Processing.Processors for (int i = 0; i < this.Length; i++) { - float yw = verticalValues[i]; + float yw = Unsafe.Add(ref verticalValues, i); int index = left + i; result += firstPassPixels[x, index] * yw; } @@ -133,7 +131,7 @@ namespace ImageSharp.Processing.Processors /// internal class WeightsBuffer : IDisposable { - private PinnedImageBuffer dataBuffer; + private Buffer2D dataBuffer; /// /// Initializes a new instance of the class. @@ -142,7 +140,7 @@ namespace ImageSharp.Processing.Processors /// The size of the destination window public WeightsBuffer(int sourceSize, int destinationSize) { - this.dataBuffer = PinnedImageBuffer.CreateClean(sourceSize, destinationSize); + this.dataBuffer = Buffer2D.CreateClean(sourceSize, destinationSize); this.Weights = new WeightsWindow[destinationSize]; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index 1374e5815..50c75a3fd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Processing.Processors { using System; using System.Buffers; + using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// @@ -111,13 +112,15 @@ namespace ImageSharp.Processing.Processors WeightsWindow ws = result.GetWeightsWindow(i, left, right); result.Weights[i] = ws; - float* weights = ws.Ptr; + ref float weights = ref ws.Ptr; for (int j = left; j <= right; j++) { float weight = sampler.GetValue((j - center) / scale); sum += weight; - weights[j - left] = weight; + + // weights[j - left] = weight: + Unsafe.Add(ref weights, j - left) = weight; } // Normalise, best to do it here rather than in the pixel loop later on. @@ -125,7 +128,9 @@ namespace ImageSharp.Processing.Processors { for (int w = 0; w < ws.Length; w++) { - weights[w] = weights[w] / sum; + // weights[w] = weights[w] / sum: + ref float wRef = ref Unsafe.Add(ref weights, w); + wRef = wRef / sum; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 944e245ac..08d96e283 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -106,7 +106,7 @@ namespace ImageSharp.Processing.Processors using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { using (PixelAccessor sourcePixels = source.Lock()) - using (PinnedImageBuffer firstPassPixels = new PinnedImageBuffer(width, source.Height)) + using (Buffer2D firstPassPixels = new Buffer2D(width, source.Height)) { firstPassPixels.Clear(); @@ -117,7 +117,7 @@ namespace ImageSharp.Processing.Processors y => { // TODO: Without Parallel.For() this buffer object could be reused: - using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) + using (Buffer tempRowBuffer = new Buffer(sourcePixels.Width)) { BufferSpan sourceRow = sourcePixels.GetRowSpan(y); diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs index 6611d571c..135616a5d 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs @@ -6,7 +6,7 @@ using BenchmarkDotNet.Attributes; using ImageSharp; - + /// /// Compares two implementation candidates for general BulkPixelOperations.ToVector4(): /// - One iterating with pointers @@ -14,9 +14,9 @@ /// public unsafe class PackFromVector4ReferenceVsPointer { - private PinnedBuffer destination; + private Buffer destination; - private PinnedBuffer source; + private Buffer source; [Params(16, 128, 1024)] public int Count { get; set; } @@ -24,8 +24,10 @@ [Setup] public void Setup() { - this.destination = new PinnedBuffer(this.Count); - this.source = new PinnedBuffer(this.Count * 4); + this.destination = new Buffer(this.Count); + this.source = new Buffer(this.Count * 4); + this.source.Pin(); + this.destination.Pin(); } [Cleanup] @@ -38,8 +40,8 @@ [Benchmark(Baseline = true)] public void PackUsingPointers() { - Vector4* sp = (Vector4*)this.source.Pointer; - byte* dp = (byte*)this.destination.Pointer; + Vector4* sp = (Vector4*)this.source.Pin(); + byte* dp = (byte*)this.destination.Pin(); int count = this.Count; int size = sizeof(ImageSharp.Color32); diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs index bc38328a8..15f83eb58 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs @@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk public abstract class PackFromXyzw where TColor : struct, IPixel { - private PinnedBuffer destination; + private Buffer destination; - private PinnedBuffer source; + private Buffer source; [Params(16, 128, 1024)] public int Count { get; set; } @@ -18,8 +18,8 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Setup] public void Setup() { - this.destination = new PinnedBuffer(this.Count); - this.source = new PinnedBuffer(this.Count * 4); + this.destination = new Buffer(this.Count); + this.source = new Buffer(this.Count * 4); } [Cleanup] diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 178719cca..21bc668b7 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk public abstract class ToVector4 where TColor : struct, IPixel { - private PinnedBuffer source; + private Buffer source; - private PinnedBuffer destination; + private Buffer destination; [Params(64, 300, 1024)] public int Count { get; set; } @@ -18,8 +18,8 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Setup] public void Setup() { - this.source = new PinnedBuffer(this.Count); - this.destination = new PinnedBuffer(this.Count); + this.source = new Buffer(this.Count); + this.destination = new Buffer(this.Count); } [Cleanup] diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs index 64b21b079..d398b4eaa 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs @@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk public abstract class ToXyz where TColor : struct, IPixel { - private PinnedBuffer source; + private Buffer source; - private PinnedBuffer destination; + private Buffer destination; [Params(16, 128, 1024)] public int Count { get; set; } @@ -18,8 +18,8 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Setup] public void Setup() { - this.source = new PinnedBuffer(this.Count); - this.destination = new PinnedBuffer(this.Count * 3); + this.source = new Buffer(this.Count); + this.destination = new Buffer(this.Count * 3); } [Cleanup] diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs index 946914966..03103f769 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs @@ -13,9 +13,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk public abstract class ToXyzw where TColor : struct, IPixel { - private PinnedBuffer source; + private Buffer source; - private PinnedBuffer destination; + private Buffer destination; [Params(16, 128, 1024)] public int Count { get; set; } @@ -23,8 +23,8 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Setup] public void Setup() { - this.source = new PinnedBuffer(this.Count); - this.destination = new PinnedBuffer(this.Count * 4); + this.source = new Buffer(this.Count); + this.destination = new Buffer(this.Count * 4); } [Cleanup] diff --git a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs index 72fd6dc24..31e9cc0e3 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Benchmarks.General using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; - + [Config(typeof(Config.Short))] public class ArrayCopy { @@ -58,8 +58,7 @@ namespace ImageSharp.Benchmarks.General Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); } } - - + [Benchmark(Description = "Copy using Marshal.Copy")] public unsafe void CopyUsingMarshalCopy() { @@ -68,5 +67,37 @@ namespace ImageSharp.Benchmarks.General Marshal.Copy(this.source, 0, (IntPtr)pinnedDestination, this.Count); } } + + /***************************************************************************************************************** + *************** RESULTS on i7-4810MQ 2.80GHz + Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1085.0 ******************** + ***************************************************************************************************************** + * + * Method | Count | Mean | StdErr | StdDev | Scaled | Scaled-StdDev | + * ---------------------------------- |------ |------------ |----------- |----------- |------- |-------------- | + * 'Copy using Array.Copy()' | 10 | 20.3074 ns | 0.1194 ns | 0.2068 ns | 1.00 | 0.00 | + * 'Copy using Unsafe' | 10 | 6.1002 ns | 0.1981 ns | 0.3432 ns | 0.30 | 0.01 | + * 'Copy using Buffer.BlockCopy()' | 10 | 10.7879 ns | 0.0984 ns | 0.1705 ns | 0.53 | 0.01 | + * 'Copy using Buffer.MemoryCopy' | 10 | 4.9625 ns | 0.0200 ns | 0.0347 ns | 0.24 | 0.00 | + * 'Copy using Marshal.Copy' | 10 | 16.1782 ns | 0.0919 ns | 0.1592 ns | 0.80 | 0.01 | + * + * 'Copy using Array.Copy()' | 100 | 31.5945 ns | 0.2908 ns | 0.5037 ns | 1.00 | 0.00 | + * 'Copy using Unsafe' | 100 | 10.2722 ns | 0.5202 ns | 0.9010 ns | 0.33 | 0.02 | + * 'Copy using Buffer.BlockCopy()' | 100 | 22.0322 ns | 0.0284 ns | 0.0493 ns | 0.70 | 0.01 | + * 'Copy using Buffer.MemoryCopy' | 100 | 10.2472 ns | 0.0359 ns | 0.0622 ns | 0.32 | 0.00 | + * 'Copy using Marshal.Copy' | 100 | 34.3820 ns | 1.1868 ns | 2.0555 ns | 1.09 | 0.05 | + * + * 'Copy using Array.Copy()' | 1000 | 40.9743 ns | 0.0521 ns | 0.0902 ns | 1.00 | 0.00 | + * 'Copy using Unsafe' | 1000 | 42.7840 ns | 2.0139 ns | 3.4882 ns | 1.04 | 0.07 | + * 'Copy using Buffer.BlockCopy()' | 1000 | 33.7361 ns | 0.0751 ns | 0.1300 ns | 0.82 | 0.00 | + * 'Copy using Buffer.MemoryCopy' | 1000 | 35.7541 ns | 0.0480 ns | 0.0832 ns | 0.87 | 0.00 | + * 'Copy using Marshal.Copy' | 1000 | 42.2028 ns | 0.2769 ns | 0.4795 ns | 1.03 | 0.01 | + * + * 'Copy using Array.Copy()' | 10000 | 200.0438 ns | 0.2251 ns | 0.3899 ns | 1.00 | 0.00 | + * 'Copy using Unsafe' | 10000 | 389.6957 ns | 13.2770 ns | 22.9964 ns | 1.95 | 0.09 | + * 'Copy using Buffer.BlockCopy()' | 10000 | 191.3478 ns | 0.1557 ns | 0.2697 ns | 0.96 | 0.00 | + * 'Copy using Buffer.MemoryCopy' | 10000 | 196.4679 ns | 0.2731 ns | 0.4730 ns | 0.98 | 0.00 | + * 'Copy using Marshal.Copy' | 10000 | 202.5392 ns | 0.5561 ns | 0.9631 ns | 1.01 | 0.00 | + * + */ } } diff --git a/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs b/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs index 5428462be..180067020 100644 --- a/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs +++ b/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs @@ -11,15 +11,15 @@ namespace ImageSharp.Benchmarks.General public unsafe class ClearBuffer { - private PinnedBuffer buffer; - + private Buffer buffer; + [Params(32, 128, 512)] public int Count { get; set; } [Setup] public void Setup() { - this.buffer = new PinnedBuffer(this.Count); + this.buffer = new Buffer(this.Count); } [Cleanup] @@ -37,7 +37,7 @@ namespace ImageSharp.Benchmarks.General [Benchmark] public void Unsafe_InitBlock() { - Unsafe.InitBlock((void*)this.buffer.Pointer, default(byte), (uint)this.Count*sizeof(uint)); + Unsafe.InitBlock((void*)this.buffer.Pin(), default(byte), (uint)this.Count * sizeof(uint)); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/IterateArray.cs b/tests/ImageSharp.Benchmarks/General/IterateArray.cs index 48f2316a2..eeab6506a 100644 --- a/tests/ImageSharp.Benchmarks/General/IterateArray.cs +++ b/tests/ImageSharp.Benchmarks/General/IterateArray.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Benchmarks.General public class IterateArray { // Usual pinned stuff - private PinnedBuffer buffer; + private Buffer buffer; // An array that's not pinned by intent! private Vector4[] array; @@ -19,7 +19,8 @@ namespace ImageSharp.Benchmarks.General [Setup] public void Setup() { - this.buffer = new PinnedBuffer(this.Length); + this.buffer = new Buffer(this.Length); + this.buffer.Pin(); this.array = new Vector4[this.Length]; } @@ -41,7 +42,7 @@ namespace ImageSharp.Benchmarks.General { Vector4 sum = new Vector4(); - Vector4* ptr = (Vector4*) this.buffer.Pointer; + Vector4* ptr = (Vector4*) this.buffer.Pin(); Vector4* end = ptr + this.Length; for (; ptr < end; ptr++) diff --git a/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs b/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs new file mode 100644 index 000000000..d9237b801 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs @@ -0,0 +1,360 @@ +namespace ImageSharp.Benchmarks.General +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + using BenchmarkDotNet.Attributes; + + // Pixel indexing benchmarks compare different methods for getting/setting all pixel values in a subsegment of a single pixel row. + public abstract unsafe class PixelIndexing + { + /// + /// https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Pinnable.cs + /// + protected class Pinnable + { + public T Data; + } + + /// + /// The indexer methods are encapsulated into a struct to make sure everything is inlined. + /// + internal struct Data + { + private Vector4* pointer; + + private Pinnable pinnable; + + private Vector4[] array; + + private int width; + + public Data(Buffer2D buffer) + { + this.pointer = (Vector4*)buffer.Pin(); + this.pinnable = Unsafe.As>(buffer.Array); + this.array = buffer.Array; + this.width = buffer.Width; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 GetPointersBasicImpl(int x, int y) + { + return this.pointer[y * this.width + x]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 GetPointersSrcsUnsafeImpl(int x, int y) + { + // This is the original solution in PixelAccessor: + return Unsafe.Read((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf())); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 GetReferencesImpl(int x, int y) + { + int elementOffset = (y * this.width) + x; + return Unsafe.Add(ref this.pinnable.Data, elementOffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref Vector4 GetReferencesRefReturnsImpl(int x, int y) + { + int elementOffset = (y * this.width) + x; + return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IndexWithPointersBasicImpl(int x, int y, Vector4 v) + { + this.pointer[y * this.width + x] = v; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IndexWithPointersSrcsUnsafeImpl(int x, int y, Vector4 v) + { + Unsafe.Write((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf()), v); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IndexWithReferencesOnPinnableIncorrectImpl(int x, int y, Vector4 v) + { + int elementOffset = (y * this.width) + x; + // Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68 + Unsafe.Add(ref this.pinnable.Data, elementOffset) = v; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref Vector4 IndexWithReferencesOnPinnableIncorrectRefReturnImpl(int x, int y) + { + int elementOffset = (y * this.width) + x; + // Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68 + return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IndexWithUnsafeReferenceArithmeticsOnArray0Impl(int x, int y, Vector4 v) + { + int elementOffset = (y * this.width) + x; + Unsafe.Add(ref this.array[0], elementOffset) = v; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref Vector4 IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(int x, int y) + { + int elementOffset = (y * this.width) + x; + return ref Unsafe.Add(ref this.array[0], elementOffset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IndexSetArrayStraightforward(int x, int y, Vector4 v) + { + // No magic. + // We just index right into the array as normal people do. + // And it looks like this is the fastest way! + this.array[(y * this.width) + x] = v; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref Vector4 IndexWithReferencesOnArrayStraightforwardRefReturnImpl(int x, int y) + { + // No magic. + // We just index right into the array as normal people do. + // And it looks like this is the fastest way! + return ref this.array[(y * this.width) + x]; + } + } + + internal Buffer2D buffer; + + protected int width; + + protected int startIndex; + + protected int endIndex; + + protected Vector4* pointer; + + protected Vector4[] array; + + protected Pinnable pinnable; + + // [Params(1024)] + public int Count { get; set; } = 1024; + + [Setup] + public void Setup() + { + this.width = 2048; + this.buffer = new Buffer2D(2048, 2048); + this.pointer = (Vector4*)this.buffer.Pin(); + this.array = this.buffer.Array; + this.pinnable = Unsafe.As>(this.array); + + this.startIndex = 2048 / 2 - (this.Count / 2); + this.endIndex = 2048 / 2 + (this.Count / 2); + } + + [Cleanup] + public void Cleanup() + { + this.buffer.Dispose(); + } + + } + + public class PixelIndexingGetter : PixelIndexing + { + [Benchmark(Description = "Index.Get: Pointers+arithmetics", Baseline = true)] + public Vector4 IndexWithPointersBasic() + { + Vector4 sum = Vector4.Zero; + Data data = new Data(this.buffer); + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + sum += data.GetPointersBasicImpl(x, y); + } + + return sum; + } + + [Benchmark(Description = "Index.Get: Pointers+SRCS.Unsafe")] + public Vector4 IndexWithPointersSrcsUnsafe() + { + Vector4 sum = Vector4.Zero; + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + sum += data.GetPointersSrcsUnsafeImpl(x, y); + } + + return sum; + } + + [Benchmark(Description = "Index.Get: References")] + public Vector4 IndexWithReferences() + { + Vector4 sum = Vector4.Zero; + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + sum += data.GetReferencesImpl(x, y); + } + + return sum; + } + + [Benchmark(Description = "Index.Get: References|refreturns")] + public Vector4 IndexWithReferencesRefReturns() + { + Vector4 sum = Vector4.Zero; + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + sum += data.GetReferencesRefReturnsImpl(x, y); + } + + return sum; + } + } + + public class PixelIndexingSetter : PixelIndexing + { + [Benchmark(Description = "!!! Index.Set: Pointers|arithmetics", Baseline = true)] + public void IndexWithPointersBasic() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + data.IndexWithPointersBasicImpl(x, y, v); + } + } + + [Benchmark(Description = "Index.Set: Pointers|SRCS.Unsafe")] + public void IndexWithPointersSrcsUnsafe() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + data.IndexWithPointersSrcsUnsafeImpl(x, y, v); + } + } + + [Benchmark(Description = "Index.Set: References|IncorrectPinnable")] + public void IndexWithReferencesPinnableBasic() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + data.IndexWithReferencesOnPinnableIncorrectImpl(x, y, v); + } + } + + [Benchmark(Description = "Index.Set: References|IncorrectPinnable|refreturn")] + public void IndexWithReferencesPinnableRefReturn() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + data.IndexWithReferencesOnPinnableIncorrectRefReturnImpl(x, y) = v; + } + } + + [Benchmark(Description = "Index.Set: References|Array[0]Unsafe")] + public void IndexWithReferencesArrayBasic() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + data.IndexWithUnsafeReferenceArithmeticsOnArray0Impl(x, y, v); + } + } + + [Benchmark(Description = "Index.Set: References|Array[0]Unsafe|refreturn")] + public void IndexWithReferencesArrayRefReturn() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + data.IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(x, y) = v; + } + } + + [Benchmark(Description = "!!! Index.Set: References|Array+Straight")] + public void IndexWithReferencesArrayStraightforward() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + // No magic. + // We just index right into the array as normal people do. + // And it looks like this is the fastest way! + data.IndexSetArrayStraightforward(x, y, v); + } + } + + + [Benchmark(Description = "!!! Index.Set: References|Array+Straight|refreturn")] + public void IndexWithReferencesArrayStraightforwardRefReturn() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + int y = this.startIndex; + for (int x = this.startIndex; x < this.endIndex; x++) + { + // No magic. + // We just index right into the array as normal people do. + // And it looks like this is the fastest way! + data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(x, y) = v; + } + } + + [Benchmark(Description = "!!! Index.Set: SmartUnsafe")] + public void SmartUnsafe() + { + Vector4 v = new Vector4(1, 2, 3, 4); + Data data = new Data(this.buffer); + + // This method is basically an unsafe variant of .GetRowSpan(y) + indexing individual pixels in the row. + // If a user seriously needs by-pixel manipulation to be performant, we should provide this option. + + ref Vector4 rowStart = ref data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(this.startIndex, this.startIndex); + + for (int i = 0; i < this.Count; i++) + { + // We don't have to add 'Width * y' here! + Unsafe.Add(ref rowStart, i) = v; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index dad603523..1bd51d8f3 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Sandbox46 { using System; using System.Runtime.DesignerServices; - + using ImageSharp.Tests; using ImageSharp.Tests.Colors; @@ -53,10 +53,10 @@ namespace ImageSharp.Sandbox46 private static void RunToVector4ProfilingTest() { - BulkPixelOperationsTests.Color tests = new BulkPixelOperationsTests.Color(new ConsoleOutput()); + BulkPixelOperationsTests.Color32 tests = new BulkPixelOperationsTests.Color32(new ConsoleOutput()); tests.Benchmark_ToVector4(); } - + private static void RunDecodeJpegProfilingTests() { Console.WriteLine("RunDecodeJpegProfilingTests..."); diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index edfa7901e..d1775a893 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -10,22 +10,22 @@ namespace ImageSharp.Tests.Colors public class BulkPixelOperationsTests { - public class Color : BulkPixelOperationsTests + public class Color32 : BulkPixelOperationsTests { - public Color(ITestOutputHelper output) + public Color32(ITestOutputHelper output) : base(output) { } // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: - public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + //public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; [Fact] public void IsSpecialImplementation() { Assert.IsType(BulkPixelOperations.Instance); } - + [Fact] public void ToVector4SimdAligned() { @@ -36,7 +36,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => ImageSharp.Color32.BulkOperations.ToVector4SimdAligned(s, d, 64) - ); + ); } // [Fact] // Profiling benchmark - enable manually! @@ -45,8 +45,8 @@ namespace ImageSharp.Tests.Colors int times = 200000; int count = 1024; - using (PinnedBuffer source = new PinnedBuffer(count)) - using (PinnedBuffer dest = new PinnedBuffer(count)) + using (Buffer source = new Buffer(count)) + using (Buffer dest = new Buffer(count)) { this.Measure( times, @@ -66,13 +66,13 @@ namespace ImageSharp.Tests.Colors { } - public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; + //public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; } [Theory] [WithBlankImages(1, 1, PixelTypes.All)] public void GetGlobalInstance(TestImageProvider dummy) - where TColor:struct, IPixel + where TColor : struct, IPixel { Assert.NotNull(BulkPixelOperations.Instance); } @@ -112,7 +112,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.PackFromVector4(s, d, count) - ); + ); } internal static Vector4[] CreateExpectedVector4Data(TColor[] source) @@ -137,7 +137,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.ToVector4(s, d, count) - ); + ); } @@ -156,10 +156,10 @@ namespace ImageSharp.Tests.Colors } TestOperation( - source, - expected, + source, + expected, (s, d) => Operations.PackFromXyzBytes(s, d, count) - ); + ); } [Theory] @@ -179,7 +179,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.ToXyzBytes(s, d, count) - ); + ); } [Theory] @@ -200,7 +200,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.PackFromXyzwBytes(s, d, count) - ); + ); } [Theory] @@ -220,7 +220,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.ToXyzwBytes(s, d, count) - ); + ); } [Theory] @@ -241,7 +241,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.PackFromZyxBytes(s, d, count) - ); + ); } [Theory] @@ -261,7 +261,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.ToZyxBytes(s, d, count) - ); + ); } [Theory] @@ -282,7 +282,7 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.PackFromZyxwBytes(s, d, count) - ); + ); } [Theory] @@ -302,26 +302,26 @@ namespace ImageSharp.Tests.Colors source, expected, (s, d) => Operations.ToZyxwBytes(s, d, count) - ); + ); } - + private class TestBuffers : IDisposable where TSource : struct where TDest : struct { - public PinnedBuffer SourceBuffer { get; } - public PinnedBuffer ActualDestBuffer { get; } - public PinnedBuffer ExpectedDestBuffer { get; } + public Buffer SourceBuffer { get; } + public Buffer ActualDestBuffer { get; } + public Buffer ExpectedDestBuffer { get; } public BufferSpan Source => this.SourceBuffer; public BufferSpan ActualDest => this.ActualDestBuffer; - + public TestBuffers(TSource[] source, TDest[] expectedDest) { - this.SourceBuffer = new PinnedBuffer(source); - this.ExpectedDestBuffer = new PinnedBuffer(expectedDest); - this.ActualDestBuffer = new PinnedBuffer(expectedDest.Length); + this.SourceBuffer = new Buffer(source); + this.ExpectedDestBuffer = new Buffer(expectedDest); + this.ActualDestBuffer = new Buffer(expectedDest.Length); } public void Dispose() diff --git a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs b/tests/ImageSharp.Tests/Common/Buffer2DTests.cs similarity index 68% rename from tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs rename to tests/ImageSharp.Tests/Common/Buffer2DTests.cs index a23f93a70..ac92ab87b 100644 --- a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/Buffer2DTests.cs @@ -1,20 +1,34 @@ // ReSharper disable InconsistentNaming namespace ImageSharp.Tests.Common { + using System; using System.Runtime.CompilerServices; using Xunit; using static TestStructs; - public unsafe class PinnedImageBufferTests + public unsafe class Buffer2DTests { + // ReSharper disable once ClassNeverInstantiated.Local + private class Assert : Xunit.Assert + { + public static void SpanPointsTo(BufferSpan span, Buffer buffer, int bufferOffset = 0) + where T : struct + { + ref T actual = ref span.DangerousGetPinnableReference(); + ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset); + + Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); + } + } + [Theory] [InlineData(7, 42)] [InlineData(1025, 17)] public void Construct(int width, int height) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (Buffer2D buffer = new Buffer2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); @@ -28,7 +42,7 @@ namespace ImageSharp.Tests.Common public void Construct_FromExternalArray(int width, int height) { Foo[] array = new Foo[width * height + 10]; - using (PinnedImageBuffer buffer = new PinnedImageBuffer(array, width, height)) + using (Buffer2D buffer = new Buffer2D(array, width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); @@ -42,7 +56,7 @@ namespace ImageSharp.Tests.Common { for (int i = 0; i < 100; i++) { - using (PinnedImageBuffer buffer = PinnedImageBuffer.CreateClean(42, 42)) + using (Buffer2D buffer = Buffer2D.CreateClean(42, 42)) { for (int j = 0; j < buffer.Length; j++) { @@ -59,13 +73,13 @@ namespace ImageSharp.Tests.Common [InlineData(17, 42, 41)] public void GetRowSpanY(int width, int height, int y) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (Buffer2D buffer = new Buffer2D(width, height)) { BufferSpan span = buffer.GetRowSpan(y); Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.Equal(buffer.Pointer + sizeof(Foo) * width * y, span.PointerAtOffset); + Assert.SpanPointsTo(span, buffer, width * y); } } @@ -75,13 +89,13 @@ namespace ImageSharp.Tests.Common [InlineData(17, 42, 0, 41)] public void GetRowSpanXY(int width, int height, int x, int y) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (Buffer2D buffer = new Buffer2D(width, height)) { BufferSpan span = buffer.GetRowSpan(x, y); Assert.Equal(width * y + x, span.Start); Assert.Equal(width - x, span.Length); - Assert.Equal(buffer.Pointer + sizeof(Foo) * (width * y + x), span.PointerAtOffset); + Assert.SpanPointsTo(span, buffer, width * y + x); } } @@ -91,7 +105,7 @@ namespace ImageSharp.Tests.Common [InlineData(99, 88, 98, 87)] public void Indexer(int width, int height, int x, int y) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (Buffer2D buffer = new Buffer2D(width, height)) { Foo[] array = buffer.Array; diff --git a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs index f3bed942a..aa3eb2797 100644 --- a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -1,5 +1,6 @@ // ReSharper disable ObjectCreationAsStatement // ReSharper disable InconsistentNaming + namespace ImageSharp.Tests.Common { using System; @@ -11,18 +12,30 @@ namespace ImageSharp.Tests.Common public unsafe class BufferSpanTests { + // ReSharper disable once ClassNeverInstantiated.Local + private class Assert : Xunit.Assert + { + public static void SameRefs(ref T1 a, ref T2 b) + { + ref T1 bb = ref Unsafe.As(ref b); + + True(Unsafe.AreSame(ref a, ref bb), "References are not same!"); + } + } + [Fact] public void AsBytes() { Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; - using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) + using (Buffer colorBuf = new Buffer(fooz)) { BufferSpan orig = colorBuf.Slice(1); - BufferSpan asBytes = (BufferSpan < byte > )orig; + BufferSpan asBytes = orig.AsBytes(); Assert.Equal(asBytes.Start, sizeof(Foo)); - Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); + Assert.Equal(orig.Length * Unsafe.SizeOf(), asBytes.Length); + Assert.SameRefs(ref orig.DangerousGetPinnableReference(), ref asBytes.DangerousGetPinnableReference()); } } @@ -32,16 +45,14 @@ namespace ImageSharp.Tests.Common public void Basic() { Foo[] array = Foo.CreateArray(3); - fixed (Foo* p = array) - { - // Act: - BufferSpan span = new BufferSpan(array, p); - // Assert: - Assert.Equal(array, span.Array); - Assert.Equal((IntPtr)p, span.PointerAtOffset); - Assert.Equal(3, span.Length); - } + // Act: + BufferSpan span = new BufferSpan(array); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(3, span.Length); + Assert.SameRefs(ref array[0], ref span.DangerousGetPinnableReference()); } [Fact] @@ -49,17 +60,15 @@ namespace ImageSharp.Tests.Common { Foo[] array = Foo.CreateArray(4); int start = 2; - fixed (Foo* p = array) - { - // Act: - BufferSpan span = new BufferSpan(array, p, start); - - // Assert: - Assert.Equal(array, span.Array); - Assert.Equal(start, span.Start); - Assert.Equal((IntPtr)(p + start), span.PointerAtOffset); - Assert.Equal(array.Length - start, span.Length); - } + + // Act: + BufferSpan span = new BufferSpan(array, start); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(start, span.Start); + Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference()); + Assert.Equal(array.Length - start, span.Length); } [Fact] @@ -68,17 +77,14 @@ namespace ImageSharp.Tests.Common Foo[] array = Foo.CreateArray(10); int start = 2; int length = 3; - fixed (Foo* p = array) - { - // Act: - BufferSpan span = new BufferSpan(array, p, start, length); - - // Assert: - Assert.Equal(array, span.Array); - Assert.Equal(start, span.Start); - Assert.Equal((IntPtr)(p + start), span.PointerAtOffset); - Assert.Equal(length, span.Length); - } + // Act: + BufferSpan span = new BufferSpan(array, start, length); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(start, span.Start); + Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference()); + Assert.Equal(length, span.Length); } } @@ -92,19 +98,16 @@ namespace ImageSharp.Tests.Common int start1 = 2; int totalOffset = start0 + start1; - fixed (Foo* p = array) - { - BufferSpan span = new BufferSpan(array, p, start0); + BufferSpan span = new BufferSpan(array, start0); - // Act: - span = span.Slice(start1); + // Act: + span = span.Slice(start1); - // Assert: - Assert.Equal(array, span.Array); - Assert.Equal(totalOffset, span.Start); - Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); - Assert.Equal(array.Length - totalOffset, span.Length); - } + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(totalOffset, span.Start); + Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference()); + Assert.Equal(array.Length - totalOffset, span.Length); } [Fact] @@ -116,24 +119,19 @@ namespace ImageSharp.Tests.Common int totalOffset = start0 + start1; int sliceLength = 3; - fixed (Foo* p = array) - { - BufferSpan span = new BufferSpan(array, p, start0); + BufferSpan span = new BufferSpan(array, start0); - // Act: - span = span.Slice(start1, sliceLength); + // Act: + span = span.Slice(start1, sliceLength); - // Assert: - Assert.Equal(array, span.Array); - Assert.Equal(totalOffset, span.Start); - Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); - Assert.Equal(sliceLength, span.Length); - } + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(totalOffset, span.Start); + Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference()); + Assert.Equal(sliceLength, span.Length); } } - - [Theory] [InlineData(4)] [InlineData(1500)] @@ -142,21 +140,17 @@ namespace ImageSharp.Tests.Common Foo[] array = Foo.CreateArray(count + 42); int offset = 2; - fixed (Foo* p = array) - { - BufferSpan ap = new BufferSpan(array, p, offset); + BufferSpan ap = new BufferSpan(array, offset); - // Act: - ap.Clear(count); + // Act: + ap.Clear(count); - Assert.NotEqual(default(Foo), array[offset-1]); - Assert.Equal(default(Foo), array[offset]); - Assert.Equal(default(Foo), array[offset + count-1]); - Assert.NotEqual(default(Foo), array[offset + count]); - } + Assert.NotEqual(default(Foo), array[offset - 1]); + Assert.Equal(default(Foo), array[offset]); + Assert.Equal(default(Foo), array[offset + count - 1]); + Assert.NotEqual(default(Foo), array[offset + count]); } - public class Indexer { public static readonly TheoryData IndexerData = @@ -175,14 +169,11 @@ namespace ImageSharp.Tests.Common public void Read(int length, int start, int index) { Foo[] a = Foo.CreateArray(length); - fixed (Foo* p = a) - { - BufferSpan span = new BufferSpan(a, p, start); + BufferSpan span = new BufferSpan(a, start); - Foo element = span[index]; + Foo element = span[index]; - Assert.Equal(a[start + index], element); - } + Assert.Equal(a[start + index], element); } [Theory] @@ -190,17 +181,46 @@ namespace ImageSharp.Tests.Common public void Write(int length, int start, int index) { Foo[] a = Foo.CreateArray(length); - fixed (Foo* p = a) - { - BufferSpan span = new BufferSpan(a, p, start); + BufferSpan span = new BufferSpan(a, start); - span[index] = new Foo(666, 666); + span[index] = new Foo(666, 666); - Assert.Equal(new Foo(666, 666), a[start + index]); - } + Assert.Equal(new Foo(666, 666), a[start + index]); + } + + [Theory] + [InlineData(10, 0, 0, 5)] + [InlineData(10, 1, 1, 5)] + [InlineData(10, 1, 1, 6)] + [InlineData(10, 1, 1, 7)] + public void AsBytes_Read(int length, int start, int index, int byteOffset) + { + Foo[] a = Foo.CreateArray(length); + BufferSpan span = new BufferSpan(a, start); + + BufferSpan bytes = span.AsBytes(); + + byte actual = bytes[index * Unsafe.SizeOf() + byteOffset]; + + ref byte baseRef = ref Unsafe.As(ref a[0]); + byte expected = Unsafe.Add(ref baseRef, (start + index) * Unsafe.SizeOf() + byteOffset); + + Assert.Equal(expected, actual); } } + [Theory] + [InlineData(0, 4)] + [InlineData(2, 4)] + [InlineData(3, 4)] + public void DangerousGetPinnableReference(int start, int length) + { + Foo[] a = Foo.CreateArray(length); + BufferSpan span = new BufferSpan(a, start); + ref Foo r = ref span.DangerousGetPinnableReference(); + + Assert.True(Unsafe.AreSame(ref a[start], ref r)); + } public class Copy { @@ -238,14 +258,10 @@ namespace ImageSharp.Tests.Common Foo[] source = Foo.CreateArray(count + 2); Foo[] dest = new Foo[count + 5]; - fixed (Foo* pSource = source) - fixed (Foo* pDest = dest) - { - BufferSpan apSource = new BufferSpan(source, pSource, 1); - BufferSpan apDest = new BufferSpan(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, 1); + BufferSpan apDest = new BufferSpan(dest, 1); - BufferSpan.Copy(apSource, apDest, count-1); - } + BufferSpan.Copy(apSource, apDest, count - 1); AssertNotDefault(source, 1); AssertNotDefault(dest, 1); @@ -253,7 +269,7 @@ namespace ImageSharp.Tests.Common 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.Equal(source[count - 1], dest[count - 1]); Assert.NotEqual(source[count], dest[count]); } @@ -265,14 +281,10 @@ namespace ImageSharp.Tests.Common AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); AlignedFoo[] dest = new AlignedFoo[count + 5]; - fixed (AlignedFoo* pSource = source) - fixed (AlignedFoo* pDest = dest) - { - BufferSpan apSource = new BufferSpan(source, pSource, 1); - BufferSpan apDest = new BufferSpan(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, 1); + BufferSpan apDest = new BufferSpan(dest, 1); - BufferSpan.Copy(apSource, apDest, count - 1); - } + BufferSpan.Copy(apSource, apDest, count - 1); AssertNotDefault(source, 1); AssertNotDefault(dest, 1); @@ -289,17 +301,13 @@ namespace ImageSharp.Tests.Common [InlineData(1500)] public void IntToInt(int count) { - int[] source = CreateTestInts(count+2); + int[] source = CreateTestInts(count + 2); int[] dest = new int[count + 5]; - fixed (int* pSource = source) - fixed (int* pDest = dest) - { - BufferSpan apSource = new BufferSpan(source, pSource, 1); - BufferSpan apDest = new BufferSpan(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, 1); + BufferSpan apDest = new BufferSpan(dest, 1); - BufferSpan.Copy(apSource, apDest, count -1); - } + BufferSpan.Copy(apSource, apDest, count - 1); AssertNotDefault(source, 1); AssertNotDefault(dest, 1); @@ -317,17 +325,13 @@ namespace ImageSharp.Tests.Common public void GenericToBytes(int count) { int destCount = count * sizeof(Foo); - Foo[] source = Foo.CreateArray(count+2); - byte[] dest = new byte[destCount + sizeof(Foo)*2]; + Foo[] source = Foo.CreateArray(count + 2); + byte[] dest = new byte[destCount + sizeof(Foo) * 2]; - fixed (Foo* pSource = source) - fixed (byte* pDest = dest) - { - BufferSpan apSource = new BufferSpan(source, pSource, 1); - BufferSpan apDest = new BufferSpan(dest, pDest, sizeof(Foo)); + BufferSpan apSource = new BufferSpan(source, 1); + BufferSpan apDest = new BufferSpan(dest, sizeof(Foo)); - BufferSpan.Copy(apSource, apDest, count - 1); - } + BufferSpan.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(Foo)); AssertNotDefault(source, 1); @@ -347,14 +351,10 @@ namespace ImageSharp.Tests.Common AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2]; - fixed (AlignedFoo* pSource = source) - fixed (byte* pDest = dest) - { - BufferSpan apSource = new BufferSpan(source, pSource, 1); - BufferSpan apDest = new BufferSpan(dest, pDest, sizeof(AlignedFoo)); + BufferSpan apSource = new BufferSpan(source, 1); + BufferSpan apDest = new BufferSpan(dest, sizeof(AlignedFoo)); - BufferSpan.Copy(apSource, apDest, count - 1); - } + BufferSpan.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(AlignedFoo)); AssertNotDefault(source, 1); @@ -371,17 +371,13 @@ namespace ImageSharp.Tests.Common public void IntToBytes(int count) { int destCount = count * sizeof(int); - int[] source = CreateTestInts(count+2); + int[] source = CreateTestInts(count + 2); byte[] dest = new byte[destCount + sizeof(int) + 1]; - fixed (int* pSource = source) - fixed (byte* pDest = dest) - { - BufferSpan apSource = new BufferSpan(source, pSource); - BufferSpan apDest = new BufferSpan(dest, pDest); + BufferSpan apSource = new BufferSpan(source); + BufferSpan apDest = new BufferSpan(dest); - BufferSpan.Copy(apSource, apDest, count); - } + BufferSpan.Copy(apSource.AsBytes(), apDest, count * sizeof(int)); AssertNotDefault(source, 1); @@ -398,15 +394,11 @@ namespace ImageSharp.Tests.Common int srcCount = count * sizeof(Foo); byte[] source = CreateTestBytes(srcCount); Foo[] dest = new Foo[count + 2]; - - fixed(byte* pSource = source) - fixed (Foo* pDest = dest) - { - BufferSpan apSource = new BufferSpan(source, pSource); - BufferSpan apDest = new BufferSpan(dest, pDest); - BufferSpan.Copy(apSource, apDest, count); - } + BufferSpan apSource = new BufferSpan(source); + BufferSpan apDest = new BufferSpan(dest); + + BufferSpan.Copy(apSource, apDest.AsBytes(), count * sizeof(Foo)); AssertNotDefault(source, sizeof(Foo) + 1); AssertNotDefault(dest, 1); @@ -418,14 +410,14 @@ namespace ImageSharp.Tests.Common } [Fact] - public void ColorToBytes() + public void Color32ToBytes() { Color32[] colors = { new Color32(0, 1, 2, 3), new Color32(4, 5, 6, 7), new Color32(8, 9, 10, 11), }; - using (PinnedBuffer colorBuf = new PinnedBuffer(colors)) - using (PinnedBuffer byteBuf = new PinnedBuffer(colors.Length*4)) + using (Buffer colorBuf = new Buffer(colors)) + using (Buffer byteBuf = new Buffer(colors.Length * 4)) { - BufferSpan.Copy(colorBuf, byteBuf, colorBuf.Length); + BufferSpan.Copy(colorBuf.Span.AsBytes(), byteBuf, colorBuf.Length * sizeof(Color32)); byte[] a = byteBuf.Array; @@ -436,7 +428,6 @@ namespace ImageSharp.Tests.Common } } - internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) { fixed (Foo* pArray = array) diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/BufferTests.cs similarity index 62% rename from tests/ImageSharp.Tests/Common/PinnedBufferTests.cs rename to tests/ImageSharp.Tests/Common/BufferTests.cs index 5e812d5a0..23205d012 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferTests.cs @@ -1,4 +1,5 @@ -namespace ImageSharp.Tests.Common +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Common { using System; using System.Runtime.CompilerServices; @@ -9,21 +10,37 @@ using static TestStructs; - public unsafe class PinnedBufferTests + public unsafe class BufferTests { + // ReSharper disable once ClassNeverInstantiated.Local + private class Assert : Xunit.Assert + { + public static void SpanPointsTo(BufferSpan span, Buffer buffer, int bufferOffset = 0) + where T : struct + { + ref T actual = ref span.DangerousGetPinnableReference(); + ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset); + + Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); + } + + public static void Equal(void* expected, void* actual) + { + Assert.Equal((IntPtr)expected, (IntPtr)actual); + } + } + [Theory] [InlineData(42)] [InlineData(1111)] public void ConstructWithOwnArray(int count) { - using (PinnedBuffer buffer = new PinnedBuffer(count)) + using (Buffer buffer = new Buffer(count)) { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.NotNull(buffer.Array); Assert.Equal(count, buffer.Length); Assert.True(buffer.Array.Length >= count); - - VerifyPointer(buffer); } } @@ -33,13 +50,11 @@ public void ConstructWithExistingArray(int count) { Foo[] array = new Foo[count]; - using (PinnedBuffer buffer = new PinnedBuffer(array)) + using (Buffer buffer = new Buffer(array)) { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.Equal(array, buffer.Array); Assert.Equal(count, buffer.Length); - - VerifyPointer(buffer); } } @@ -49,7 +64,7 @@ public void Clear(int count) { Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; - using (PinnedBuffer buffer = new PinnedBuffer(a)) + using (Buffer buffer = new Buffer(a)) { buffer.Clear(); @@ -63,7 +78,7 @@ { for (int i = 0; i < 100; i++) { - using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42)) + using (Buffer buffer = Buffer.CreateClean(42)) { for (int j = 0; j < buffer.Length; j++) { @@ -90,7 +105,7 @@ { Foo[] a = Foo.CreateArray(length); - using (PinnedBuffer buffer = new PinnedBuffer(a)) + using (Buffer buffer = new Buffer(a)) { Foo element = buffer[index]; @@ -104,7 +119,7 @@ { Foo[] a = Foo.CreateArray(length); - using (PinnedBuffer buffer = new PinnedBuffer(a)) + using (Buffer buffer = new Buffer(a)) { buffer[index] = new Foo(666, 666); @@ -116,24 +131,24 @@ [Fact] public void Dispose() { - PinnedBuffer buffer = new PinnedBuffer(42); + Buffer buffer = new Buffer(42); buffer.Dispose(); Assert.True(buffer.IsDisposedOrLostArrayOwnership); } - + [Theory] [InlineData(7)] [InlineData(123)] public void CastToSpan(int bufferLength) { - using (PinnedBuffer buffer = new PinnedBuffer(bufferLength)) + using (Buffer buffer = new Buffer(bufferLength)) { BufferSpan span = buffer; Assert.Equal(buffer.Array, span.Array); Assert.Equal(0, span.Start); - Assert.Equal(buffer.Pointer, span.PointerAtOffset); + Assert.SpanPointsTo(span, buffer); Assert.Equal(span.Length, bufferLength); } } @@ -141,13 +156,13 @@ [Fact] public void Span() { - using (PinnedBuffer buffer = new PinnedBuffer(42)) + using (Buffer buffer = new Buffer(42)) { BufferSpan span = buffer.Span; Assert.Equal(buffer.Array, span.Array); Assert.Equal(0, span.Start); - Assert.Equal(buffer.Pointer, span.PointerAtOffset); + Assert.SpanPointsTo(span, buffer); Assert.Equal(span.Length, 42); } } @@ -160,13 +175,13 @@ [InlineData(123, 17)] public void WithStartOnly(int bufferLength, int start) { - using (PinnedBuffer buffer = new PinnedBuffer(bufferLength)) + using (Buffer buffer = new Buffer(bufferLength)) { BufferSpan span = buffer.Slice(start); Assert.Equal(buffer.Array, span.Array); Assert.Equal(start, span.Start); - Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf(), span.PointerAtOffset); + Assert.SpanPointsTo(span, buffer, start); Assert.Equal(span.Length, bufferLength - start); } } @@ -176,13 +191,13 @@ [InlineData(123, 17, 42)] public void WithStartAndLength(int bufferLength, int start, int spanLength) { - using (PinnedBuffer buffer = new PinnedBuffer(bufferLength)) + using (Buffer buffer = new Buffer(bufferLength)) { BufferSpan span = buffer.Slice(start, spanLength); Assert.Equal(buffer.Array, span.Array); Assert.Equal(start, span.Start); - Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf(), span.PointerAtOffset); + Assert.SpanPointsTo(span, buffer, start); Assert.Equal(span.Length, spanLength); } } @@ -192,9 +207,9 @@ public void UnPinAndTakeArrayOwnership() { Foo[] data = null; - using (PinnedBuffer buffer = new PinnedBuffer(42)) + using (Buffer buffer = new Buffer(42)) { - data = buffer.UnPinAndTakeArrayOwnership(); + data = buffer.TakeArrayOwnership(); Assert.True(buffer.IsDisposedOrLostArrayOwnership); } @@ -202,10 +217,41 @@ Assert.True(data.Length >= 42); } - private static void VerifyPointer(PinnedBuffer buffer) + public class Pin { - IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]); - Assert.Equal(ptr, buffer.Pointer); + [Fact] + public void ReturnsPinnedPointerToTheBeginningOfArray() + { + using (Buffer buffer = new Buffer(42)) + { + Foo* actual = (Foo*)buffer.Pin(); + fixed (Foo* expected = buffer.Array) + { + Assert.Equal(expected, actual); + } + } + } + + [Fact] + public void SecondCallReturnsTheSamePointer() + { + using (Buffer buffer = new Buffer(42)) + { + IntPtr ptr1 = buffer.Pin(); + IntPtr ptr2 = buffer.Pin(); + + Assert.Equal(ptr1, ptr2); + } + } + + [Fact] + public void WhenCalledOnDisposedBuffer_ThrowsInvalidOperationException() + { + Buffer buffer = new Buffer(42); + buffer.Dispose(); + + Assert.Throws(() => buffer.Pin()); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/TestStructs.cs b/tests/ImageSharp.Tests/Common/TestStructs.cs index 9e762bbd1..f7f09bea2 100644 --- a/tests/ImageSharp.Tests/Common/TestStructs.cs +++ b/tests/ImageSharp.Tests/Common/TestStructs.cs @@ -25,6 +25,8 @@ namespace ImageSharp.Tests.Common } return result; } + + public override string ToString() => $"({this.A},{this.B})"; }