diff --git a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs
index 8ef88814cd..5efa7bc07a 100644
--- a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs
+++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs
@@ -158,6 +158,14 @@ namespace ImageSharp
return result;
}
+ ///
+ /// 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 reference to the 0th element
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref T DangerousGetPinnableReference() => ref Unsafe.AsRef((void*)this.PointerAtOffset);
+
///
/// Forms a slice out of the given BufferSpan, beginning at 'start'.
///
diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs
new file mode 100644
index 0000000000..e912ea29f6
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs
@@ -0,0 +1,74 @@
+namespace ImageSharp.Benchmarks
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ using BenchmarkDotNet.Attributes;
+
+ using ImageSharp;
+
+ ///
+ /// Compares two implementation candidates for general BulkPixelOperations.ToVector4():
+ /// - One iterating with pointers
+ /// - One iterating with ref locals
+ ///
+ public unsafe class PackFromVector4ReferenceVsPointer
+ {
+ private PinnedBuffer destination;
+
+ private PinnedBuffer source;
+
+ [Params(16, 128, 1024)]
+ public int Count { get; set; }
+
+ [Setup]
+ public void Setup()
+ {
+ this.destination = new PinnedBuffer(this.Count);
+ this.source = new PinnedBuffer(this.Count * 4);
+ }
+
+ [Cleanup]
+ public void Cleanup()
+ {
+ this.source.Dispose();
+ this.destination.Dispose();
+ }
+
+ [Benchmark(Baseline = true)]
+ public void PackUsingPointers()
+ {
+ Vector4* sp = (Vector4*)this.source.Pointer;
+ byte* dp = (byte*)this.destination.Pointer;
+ int count = this.Count;
+ int size = sizeof(ImageSharp.Color);
+
+ for (int i = 0; i < count; i++)
+ {
+ Vector4 v = Unsafe.Read(sp);
+ ImageSharp.Color c = default(ImageSharp.Color);
+ c.PackFromVector4(v);
+ Unsafe.Write(dp, c);
+
+ sp++;
+ dp += size;
+ }
+ }
+
+ [Benchmark]
+ public void PackUsingReferences()
+ {
+ ref Vector4 sp = ref this.source.Array[0];
+ ref ImageSharp.Color dp = ref this.destination.Array[0];
+ int count = this.Count;
+
+ for (int i = 0; i < count; i++)
+ {
+ dp.PackFromVector4(sp);
+
+ sp = Unsafe.Add(ref sp, 1);
+ dp = Unsafe.Add(ref dp, 1);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs b/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs
new file mode 100644
index 0000000000..8dc8744eb2
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs
@@ -0,0 +1,238 @@
+namespace ImageSharp.Benchmarks.General
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ using BenchmarkDotNet.Attributes;
+
+ 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 int width;
+
+ public Data(PinnedImageBuffer buffer)
+ {
+ this.pointer = (Vector4*)buffer.Pointer;
+ this.pinnable = Unsafe.As>(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)
+ {
+ 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 IndexWithReferencesImpl(int x, int y, Vector4 v)
+ {
+ int elementOffset = (y * this.width) + x;
+ Unsafe.Add(ref this.pinnable.Data, elementOffset) = v;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref Vector4 IndexWithReferencesRefReturnImpl(int x, int y)
+ {
+ int elementOffset = (y * this.width) + x;
+ return ref Unsafe.Add(ref this.pinnable.Data, elementOffset);
+ }
+ }
+
+ internal PinnedImageBuffer buffer;
+
+ protected int width;
+
+ protected int startIndex;
+
+ protected int endIndex;
+
+ protected Vector4* pointer;
+
+ protected Vector4[] array;
+
+ protected Pinnable pinnable;
+
+ [Params(64, 256, 1024)]
+ public int Count { get; set; }
+
+ [Setup]
+ public void Setup()
+ {
+ this.width = 2048;
+ this.buffer = new PinnedImageBuffer(2048, 2048);
+ this.pointer = (Vector4*)this.buffer.Pointer;
+ 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 unsafe class PixelIndexingGetter : PixelIndexing
+ {
+ [Benchmark(Description = "Index.Get: Pointers+arithmetics", Baseline = true)]
+ public Vector4 IndexWithPointersBasic()
+ {
+ Vector4 sum = Vector4.Zero;
+ Data data = new Data(this.buffer);
+
+ for (int i = this.startIndex; i < this.endIndex; i++)
+ {
+ sum += data.GetPointersBasicImpl(i, i);
+ }
+
+ return sum;
+ }
+
+ [Benchmark(Description = "Index.Get: Pointers+SRCS.Unsafe")]
+ public Vector4 IndexWithPointersSrcsUnsafe()
+ {
+ Vector4 sum = Vector4.Zero;
+ Data data = new Data(this.buffer);
+
+ for (int i = this.startIndex; i < this.endIndex; i++)
+ {
+ sum += data.GetPointersSrcsUnsafeImpl(i, i);
+ }
+
+ return sum;
+ }
+
+ [Benchmark(Description = "Index.Get: References")]
+ public Vector4 IndexWithReferences()
+ {
+ Vector4 sum = Vector4.Zero;
+ Data data = new Data(this.buffer);
+
+ for (int i = this.startIndex; i < this.endIndex; i++)
+ {
+ sum += data.GetReferencesImpl(i, i);
+ }
+
+ return sum;
+ }
+
+ [Benchmark(Description = "Index.Get: References+refreturns")]
+ public Vector4 IndexWithReferencesRefReturns()
+ {
+ Vector4 sum = Vector4.Zero;
+ Data data = new Data(this.buffer);
+
+ for (int i = this.startIndex; i < this.endIndex; i++)
+ {
+ sum += data.GetReferencesRefReturnsImpl(i, i);
+ }
+
+ return sum;
+ }
+ }
+
+ public unsafe 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);
+
+ for (int i = this.startIndex; i < this.endIndex; i++)
+ {
+ data.IndexWithPointersBasicImpl(i, i, 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);
+
+ for (int i = this.startIndex; i < this.endIndex; i++)
+ {
+ data.IndexWithPointersSrcsUnsafeImpl(i, i, v);
+ }
+ }
+
+ [Benchmark(Description = "Index.Set: References")]
+ public void IndexWithReferencesBasic()
+ {
+ Vector4 v = new Vector4(1, 2, 3, 4);
+ Data data = new Data(this.buffer);
+
+ for (int i = this.startIndex; i < this.endIndex; i++)
+ {
+ data.IndexWithReferencesImpl(i, i, v);
+ }
+ }
+
+ [Benchmark(Description = "Index.Set: References+refreturn")]
+ public void IndexWithReferencesRefReturn()
+ {
+ Vector4 v = new Vector4(1, 2, 3, 4);
+ Data data = new Data(this.buffer);
+
+ for (int i = this.startIndex; i < this.endIndex; i++)
+ {
+ data.IndexWithReferencesRefReturnImpl(i, i) = v;
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs
index aee032accd..32ec5dec62 100644
--- a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs
+++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs
@@ -201,6 +201,21 @@ namespace ImageSharp.Tests.Common
}
}
+ [Theory]
+ [InlineData(0, 4)]
+ [InlineData(2, 4)]
+ [InlineData(3, 4)]
+ public void DangerousGetPinnableReference(int start, int length)
+ {
+ Foo[] a = Foo.CreateArray(length);
+ fixed (Foo* p = a)
+ {
+ BufferSpan span = new BufferSpan(a, p, start);
+ ref Foo r = ref span.DangerousGetPinnableReference();
+
+ Assert.True(Unsafe.AreSame(ref a[start], ref r));
+ }
+ }
public class Copy
{