Browse Source

refactored all BufferSpan pointers to ref-s

af/merge-core
Anton Firszov 9 years ago
parent
commit
4854005733
  1. 171
      src/ImageSharp/Colors/Color.BulkOperations.cs
  2. 5
      src/ImageSharp/Colors/Color.cs
  3. 132
      src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs
  4. 116
      src/ImageSharp/Common/Memory/BufferSpan.cs
  5. 71
      src/ImageSharp/Common/Memory/BufferSpan{T}.cs
  6. 12
      src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs
  7. 28
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
  8. 11
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  9. 243
      tests/ImageSharp.Tests/Common/BufferSpanTests.cs
  10. 23
      tests/ImageSharp.Tests/Common/PinnedBufferTests.cs
  11. 19
      tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs

171
src/ImageSharp/Colors/Color.BulkOperations.cs

@ -62,21 +62,20 @@ namespace ImageSharp
int unpackedRawCount = count * 4; int unpackedRawCount = count * 4;
uint* src = (uint*)sourceColors.PointerAtOffset; ref uint src = ref Unsafe.As<Color, uint>(ref sourceColors.DangerousGetPinnableReference());
uint* srcEnd = src + count;
using (PinnedBuffer<uint> tempBuf = new PinnedBuffer<uint>( using (PinnedBuffer<uint> tempBuf = new PinnedBuffer<uint>(unpackedRawCount + Vector<uint>.Count))
unpackedRawCount + Vector<uint>.Count))
{ {
uint* tPtr = (uint*)tempBuf.Pointer; uint* tPtr = (uint*)tempBuf.Pointer;
uint[] temp = tempBuf.Array; uint[] temp = tempBuf.Array;
float[] fTemp = Unsafe.As<float[]>(temp); float[] fTemp = Unsafe.As<float[]>(temp);
UnpackedRGBA* dst = (UnpackedRGBA*)tPtr; UnpackedRGBA* dst = (UnpackedRGBA*)tPtr;
for (; src < srcEnd; src++, dst++) for (int i = 0; i < count; i++)
{ {
// This call is the bottleneck now: // This call is the bottleneck now:
dst->Load(*src); ref uint sp = ref Unsafe.Add(ref src, i);
dst->Load(sp);
} }
for (int i = 0; i < unpackedRawCount; i += vecSize) for (int i = 0; i < unpackedRawCount; i += vecSize)
@ -123,39 +122,44 @@ namespace ImageSharp
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void PackFromXyzBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count) internal override void PackFromXyzBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color> destColors,
int count)
{ {
byte* source = (byte*)sourceBytes; ref RGB24 sourceRef = ref Unsafe.As<byte, RGB24>(ref sourceBytes.DangerousGetPinnableReference());
byte* destination = (byte*)destColors; ref Color destRef = ref destColors.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 Color dp = ref Unsafe.Add(ref destRef, i);
source += 3; Unsafe.As<Color, RGB24>(ref dp) = sp;
destination += 4; dp.A = 255;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void ToXyzBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count) internal override void ToXyzBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count)
{ {
byte* source = (byte*)sourceColors; ref Color sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte* destination = (byte*)destBytes; ref RGB24 destRef = ref Unsafe.As<byte, RGB24>(ref destBytes.DangerousGetPinnableReference());
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
*destination = *(source + 0); ref Color sp = ref Unsafe.Add(ref sourceRef, i);
*(destination + 1) = *(source + 1); ref RGB24 dp = ref Unsafe.Add(ref destRef, i);
*(destination + 2) = *(source + 2);
source += 4; dp = Unsafe.As<Color, RGB24>(ref sp);
destination += 3;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override void PackFromXyzwBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count) internal override void PackFromXyzwBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color> destColors,
int count)
{ {
BufferSpan.Copy(sourceBytes, destColors, count); BufferSpan.Copy(sourceBytes, destColors, count);
} }
@ -167,87 +171,132 @@ namespace ImageSharp
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void PackFromZyxBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count) internal override void PackFromZyxBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color> destColors,
int count)
{ {
byte* source = (byte*)sourceBytes; ref RGB24 sourceRef = ref Unsafe.As<byte, RGB24>(ref sourceBytes.DangerousGetPinnableReference());
byte* destination = (byte*)destColors; ref Color destRef = ref destColors.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 Color dp = ref Unsafe.Add(ref destRef, i);
source += 3; Unsafe.As<Color, RGB24>(ref dp) = sp.ToZyx();
destination += 4; dp.A = 255;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void ToZyxBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count) internal override void ToZyxBytes(
BufferSpan<Color> sourceColors,
BufferSpan<byte> destBytes,
int count)
{ {
byte* source = (byte*)sourceColors; ref Color sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte* destination = (byte*)destBytes; ref RGB24 destRef = ref Unsafe.As<byte, RGB24>(ref destBytes.DangerousGetPinnableReference());
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
*destination = *(source + 2); ref Color sp = ref Unsafe.Add(ref sourceRef, i);
*(destination + 1) = *(source + 1); ref RGB24 dp = ref Unsafe.Add(ref destRef, i);
*(destination + 2) = *(source + 0);
source += 4; dp = Unsafe.As<Color, RGB24>(ref sp).ToZyx();
destination += 3;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void PackFromZyxwBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count) internal override void PackFromZyxwBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color> destColors,
int count)
{ {
byte* source = (byte*)sourceBytes; ref RGBA32 sourceRef = ref Unsafe.As<byte, RGBA32>(ref sourceBytes.DangerousGetPinnableReference());
byte* destination = (byte*)destColors; ref Color destRef = ref destColors.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)); ref RGBA32 sp = ref Unsafe.Add(ref sourceRef, i);
ref Color dp = ref Unsafe.Add(ref destRef, i);
source += 4; RGBA32 zyxw = sp.ToZyxw();
destination += 4; dp = Unsafe.As<RGBA32, Color>(ref zyxw);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void ToZyxwBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count) internal override void ToZyxwBytes(
BufferSpan<Color> sourceColors,
BufferSpan<byte> destBytes,
int count)
{ {
byte* source = (byte*)sourceColors; ref Color sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte* destination = (byte*)destBytes; ref RGBA32 destRef = ref Unsafe.As<byte, RGBA32>(ref destBytes.DangerousGetPinnableReference());
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
*destination = *(source + 2); ref RGBA32 sp = ref Unsafe.As<Color, RGBA32>(ref Unsafe.Add(ref sourceRef, i));
*(destination + 1) = *(source + 1); ref RGBA32 dp = ref Unsafe.Add(ref destRef, i);
*(destination + 2) = *(source + 0); dp = sp.ToZyxw();
*(destination + 3) = *(source + 3);
source += 4;
destination += 4;
} }
} }
/// <summary>
/// Helper struct to manipulate 3-byte RGB data.
/// </summary>
[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 };
}
/// <summary>
/// Helper struct to manipulate 4-byte RGBA data.
/// </summary>
[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 };
}
/// <summary> /// <summary>
/// Value type to store <see cref="Color"/>-s unpacked into multiple <see cref="uint"/>-s. /// Value type to store <see cref="Color"/>-s unpacked into multiple <see cref="uint"/>-s.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct UnpackedRGBA private struct UnpackedRGBA
{ {
private uint r; private uint r;
private uint g; private uint g;
private uint b; private uint b;
private uint a; private uint a;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Load(uint p) public void Load(uint p)
{ {
this.r = p; this.r = p;
this.g = p >> Color.GreenShift; this.g = p >> GreenShift;
this.b = p >> Color.BlueShift; this.b = p >> BlueShift;
this.a = p >> Color.AlphaShift; this.a = p >> AlphaShift;
} }
} }
} }

5
src/ImageSharp/Colors/Color.cs

@ -62,6 +62,7 @@ namespace ImageSharp
/// <param name="g">The green component.</param> /// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param> /// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param> /// <param name="a">The alpha component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(byte r, byte g, byte b, byte a = 255) public Color(byte r, byte g, byte b, byte a = 255)
{ {
this.packedValue = Pack(r, g, b, a); this.packedValue = Pack(r, g, b, a);
@ -74,6 +75,7 @@ namespace ImageSharp
/// <param name="g">The green component.</param> /// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param> /// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param> /// <param name="a">The alpha component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(float r, float g, float b, float a = 1) public Color(float r, float g, float b, float a = 1)
{ {
this.packedValue = Pack(r, g, b, a); this.packedValue = Pack(r, g, b, a);
@ -85,6 +87,7 @@ namespace ImageSharp
/// <param name="vector"> /// <param name="vector">
/// The vector containing the components for the packed vector. /// The vector containing the components for the packed vector.
/// </param> /// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(Vector3 vector) public Color(Vector3 vector)
{ {
this.packedValue = Pack(ref vector); this.packedValue = Pack(ref vector);
@ -96,6 +99,7 @@ namespace ImageSharp
/// <param name="vector"> /// <param name="vector">
/// The vector containing the components for the packed vector. /// The vector containing the components for the packed vector.
/// </param> /// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(Vector4 vector) public Color(Vector4 vector)
{ {
this.packedValue = Pack(ref vector); this.packedValue = Pack(ref vector);
@ -107,6 +111,7 @@ namespace ImageSharp
/// <param name="packed"> /// <param name="packed">
/// The packed value. /// The packed value.
/// </param> /// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(uint packed) public Color(uint packed)
{ {
this.packedValue = packed; this.packedValue = packed;

132
src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs

@ -13,14 +13,9 @@ namespace ImageSharp
/// for pixel buffers of type <typeparamref name="TColor"/>. /// for pixel buffers of type <typeparamref name="TColor"/>.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
public unsafe class BulkPixelOperations<TColor> public class BulkPixelOperations<TColor>
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
/// <summary>
/// The size of <typeparamref name="TColor"/> in bytes
/// </summary>
private static readonly int ColorSize = Unsafe.SizeOf<TColor>();
/// <summary> /// <summary>
/// Gets the global <see cref="BulkPixelOperations{TColor}"/> instance for the pixel type <typeparamref name="TColor"/> /// Gets the global <see cref="BulkPixelOperations{TColor}"/> instance for the pixel type <typeparamref name="TColor"/>
/// </summary> /// </summary>
@ -37,18 +32,14 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; ref Vector4 sourceRef = ref sourceVectors.DangerousGetPinnableReference();
byte* dp = (byte*)destColors; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
Vector4 v = Unsafe.Read<Vector4>(sp); ref Vector4 sp = ref Unsafe.Add(ref sourceRef, i);
TColor c = default(TColor); ref TColor dp = ref Unsafe.Add(ref destRef, i);
c.PackFromVector4(v); dp.PackFromVector4(sp);
Unsafe.Write(dp, c);
sp++;
dp += ColorSize;
} }
} }
@ -63,15 +54,14 @@ namespace ImageSharp
BufferSpan<Vector4> destVectors, BufferSpan<Vector4> destVectors,
int count) int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
Vector4* dp = (Vector4*)destVectors.PointerAtOffset; ref Vector4 destRef = ref destVectors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = Unsafe.Read<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
*dp = c.ToVector4(); ref Vector4 dp = ref Unsafe.Add(ref destRef, i);
sp += ColorSize; dp = sp.ToVector4();
dp++;
} }
} }
@ -86,16 +76,18 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
byte* sp = (byte*)sourceBytes; ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference();
byte* dp = (byte*)destColors.PointerAtOffset; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = default(TColor); int i3 = i * 3;
c.PackFromBytes(sp[0], sp[1], sp[2], 255); ref TColor dp = ref Unsafe.Add(ref destRef, i);
Unsafe.Write(dp, c); dp.PackFromBytes(
sp += 3; Unsafe.Add(ref sourceRef, i3),
dp += ColorSize; Unsafe.Add(ref sourceRef, i3 + 1),
Unsafe.Add(ref sourceRef, i3 + 2),
255);
} }
} }
@ -107,14 +99,13 @@ namespace ImageSharp
/// <param name="count">The number of pixels to convert.</param> /// <param name="count">The number of pixels to convert.</param>
internal virtual void ToXyzBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> destBytes, int count) internal virtual void ToXyzBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> destBytes, int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte[] dest = destBytes.Array; 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<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
c.ToXyzBytes(dest, i); sp.ToXyzBytes(dest, i * 3);
sp += ColorSize;
} }
} }
@ -129,16 +120,18 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
byte* sp = (byte*)sourceBytes; ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference();
byte* dp = (byte*)destColors.PointerAtOffset; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = default(TColor); int i4 = i * 4;
c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]); ref TColor dp = ref Unsafe.Add(ref destRef, i);
Unsafe.Write(dp, c); dp.PackFromBytes(
sp += 4; Unsafe.Add(ref sourceRef, i4),
dp += ColorSize; 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<byte> destBytes, BufferSpan<byte> destBytes,
int count) int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte[] dest = destBytes.Array; 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<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
c.ToXyzwBytes(dest, i); sp.ToXyzwBytes(dest, i * 4);
sp += ColorSize;
} }
} }
@ -175,16 +167,18 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
byte* sp = (byte*)sourceBytes; ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference();
byte* dp = (byte*)destColors.PointerAtOffset; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = default(TColor); int i3 = i * 3;
c.PackFromBytes(sp[2], sp[1], sp[0], 255); ref TColor dp = ref Unsafe.Add(ref destRef, i);
Unsafe.Write(dp, c); dp.PackFromBytes(
sp += 3; Unsafe.Add(ref sourceRef, i3 + 2),
dp += ColorSize; Unsafe.Add(ref sourceRef, i3 + 1),
Unsafe.Add(ref sourceRef, i3),
255);
} }
} }
@ -196,14 +190,13 @@ namespace ImageSharp
/// <param name="count">The number of pixels to convert.</param> /// <param name="count">The number of pixels to convert.</param>
internal virtual void ToZyxBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> destBytes, int count) internal virtual void ToZyxBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> destBytes, int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte[] dest = destBytes.Array; 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<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
c.ToZyxBytes(dest, i); sp.ToZyxBytes(dest, i * 3);
sp += ColorSize;
} }
} }
@ -218,16 +211,18 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
byte* sp = (byte*)sourceBytes; ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference();
byte* dp = (byte*)destColors.PointerAtOffset; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = default(TColor); int i4 = i * 4;
c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]); ref TColor dp = ref Unsafe.Add(ref destRef, i);
Unsafe.Write(dp, c); dp.PackFromBytes(
sp += 4; Unsafe.Add(ref sourceRef, i4 + 2),
dp += ColorSize; Unsafe.Add(ref sourceRef, i4 + 1),
Unsafe.Add(ref sourceRef, i4),
Unsafe.Add(ref sourceRef, i4 + 3));
} }
} }
@ -242,14 +237,13 @@ namespace ImageSharp
BufferSpan<byte> destBytes, BufferSpan<byte> destBytes,
int count) int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte[] dest = destBytes.Array; 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<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
c.ToZyxwBytes(dest, i); sp.ToZyxwBytes(dest, i * 4);
sp += ColorSize;
} }
} }
} }

116
src/ImageSharp/Common/Memory/BufferSpan.cs

@ -6,7 +6,6 @@
namespace ImageSharp namespace ImageSharp
{ {
using System; using System;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -49,25 +48,42 @@ namespace ImageSharp
} }
/// <summary> /// <summary>
/// Copy 'countInDest' number of <typeparamref name="T"/> elements into 'dest' from a raw byte buffer defined by 'source'. /// Copy 'countInDest' number of <typeparamref name="TDest"/> elements into 'dest' from a raw byte buffer defined by 'source'.
/// </summary> /// </summary>
/// <typeparam name="T">The element type.</typeparam> /// <typeparam name="TDest">The element type.</typeparam>
/// <param name="source">The raw source buffer to copy from"/></param> /// <param name="source">The raw source buffer to copy from"/></param>
/// <param name="destination">The destination buffer"/></param> /// <param name="destination">The destination buffer"/></param>
/// <param name="countInDest">The number of <typeparamref name="T"/> elements to copy.</param> /// <param name="countInDest">The number of <typeparamref name="TDest"/> elements to copy.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Copy<T>(BufferSpan<byte> source, BufferSpan<T> destination, int countInDest) public static unsafe void Copy<TDest>(BufferSpan<byte> source, BufferSpan<TDest> destination, int countInDest)
where T : struct where TDest : struct
{ {
int byteCount = SizeOf<T>(countInDest); // TODO: Refactor this method when Unsafe.CopyBlock(ref T, ref T) gets available!
int byteCount = SizeOf<TDest>(countInDest);
if (byteCount > (int)ByteCountThreshold) if (PerTypeValues<TDest>.IsPrimitiveType)
{ {
Marshal.Copy(source.Array, source.Start, destination.PointerAtOffset, byteCount); Buffer.BlockCopy(source.Array, source.ByteOffset, destination.Array, destination.ByteOffset, byteCount);
}
else if (byteCount > ByteCountThreshold)
{
ref byte destRef = ref Unsafe.As<TDest, byte>(ref destination.DangerousGetPinnableReference());
fixed (void* pinnedPtr = &destRef)
{
Marshal.Copy(source.Array, source.Start, (IntPtr)pinnedPtr, byteCount);
}
} }
else else
{ {
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); ref byte srcRef = ref source.DangerousGetPinnableReference();
ref byte destRef = ref Unsafe.As<TDest, byte>(ref destination.DangerousGetPinnableReference());
fixed (void* pinnedSrc = &srcRef)
fixed (void* pinnedDest = &destRef)
{
Unsafe.CopyBlock(pinnedDest, pinnedSrc, (uint)byteCount);
}
} }
} }
@ -93,37 +109,77 @@ namespace ImageSharp
=> (uint)SizeOf<T>(count); => (uint)SizeOf<T>(count);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CopyImpl<T, TDest>(BufferSpan<T> source, BufferSpan<TDest> destination, int count) private static unsafe void CopyImpl<T, TDest>(BufferSpan<T> source, BufferSpan<TDest> destination, int countInSource)
where T : struct where T : struct
where TDest : struct where TDest : struct
{ {
int byteCount = SizeOf<T>(count); // TODO: Refactor this method when Unsafe.CopyBlock(ref T, ref T) gets available!
int byteCount = SizeOf<T>(countInSource);
if (byteCount > ByteCountThreshold) if (PerTypeValues<T>.IsPrimitiveType && PerTypeValues<TDest>.IsPrimitiveType)
{ {
if (Unsafe.SizeOf<T>() == sizeof(long)) Buffer.BlockCopy(source.Array, source.ByteOffset, destination.Array, destination.ByteOffset, byteCount);
{ }
Marshal.Copy(Unsafe.As<long[]>(source.Array), source.Start, destination.PointerAtOffset, count); else if (byteCount > ByteCountThreshold)
return; {
} ref byte destRef = ref Unsafe.As<TDest, byte>(ref destination.DangerousGetPinnableReference());
else if (Unsafe.SizeOf<T>() == sizeof(int))
{ fixed (void* pinnedPtr = &destRef)
Marshal.Copy(Unsafe.As<int[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(short))
{ {
Marshal.Copy(Unsafe.As<short[]>(source.Array), source.Start, destination.PointerAtOffset, count); IntPtr ptr = (IntPtr)pinnedPtr;
return; if (Unsafe.SizeOf<T>() == sizeof(long))
{
Marshal.Copy(Unsafe.As<long[]>(source.Array), source.Start, ptr, countInSource);
}
else if (Unsafe.SizeOf<T>() == sizeof(int))
{
Marshal.Copy(Unsafe.As<int[]>(source.Array), source.Start, ptr, countInSource);
}
else if (Unsafe.SizeOf<T>() == sizeof(short))
{
Marshal.Copy(Unsafe.As<short[]>(source.Array), source.Start, ptr, countInSource);
}
else if (Unsafe.SizeOf<T>() == sizeof(byte))
{
Marshal.Copy(Unsafe.As<byte[]>(source.Array), source.Start, ptr, countInSource);
}
} }
else if (Unsafe.SizeOf<T>() == sizeof(byte)) }
else
{
ref byte srcRef = ref Unsafe.As<T, byte>(ref source.DangerousGetPinnableReference());
ref byte destRef = ref Unsafe.As<TDest, byte>(ref destination.DangerousGetPinnableReference());
fixed (void* pinnedSrc = &srcRef)
fixed (void* pinnedDest = &destRef)
{ {
Marshal.Copy(Unsafe.As<byte[]>(source.Array), source.Start, destination.PointerAtOffset, count); Unsafe.CopyBlock(pinnedDest, pinnedSrc, (uint)byteCount);
return;
} }
} }
}
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); /// <summary>
/// Per-type static value cache for type 'T'
/// </summary>
/// <typeparam name="T">The type</typeparam>
internal class PerTypeValues<T>
{
/// <summary>
/// Gets a value indicating whether 'T' is a primitive type.
/// </summary>
public static readonly bool IsPrimitiveType =
typeof(T) == typeof(byte) ||
typeof(T) == typeof(char) ||
typeof(T) == typeof(short) ||
typeof(T) == typeof(ushort) ||
typeof(T) == typeof(int) ||
typeof(T) == typeof(uint) ||
typeof(T) == typeof(float) ||
typeof(T) == typeof(double) ||
typeof(T) == typeof(long) ||
typeof(T) == typeof(ulong) ||
typeof(T) == typeof(IntPtr) ||
typeof(T) == typeof(UIntPtr);
} }
} }
} }

71
src/ImageSharp/Common/Memory/BufferSpan{T}.cs

@ -30,13 +30,12 @@ namespace ImageSharp
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start. /// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start.
/// </summary> /// </summary>
/// <param name="array">The pinned array</param> /// <param name="array">The pinned array</param>
/// <param name="pointerToArray">Pointer to the beginning of the array</param>
/// <param name="start">The index at which to begin the span.</param> /// <param name="start">The index at which to begin the span.</param>
/// <param name="length">The length</param> /// <param name="length">The length</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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(start, array.Length, nameof(start));
DebugGuard.MustBeLessThanOrEqualTo(length, array.Length - start, nameof(length)); DebugGuard.MustBeLessThanOrEqualTo(length, array.Length - start, nameof(length));
@ -44,41 +43,36 @@ namespace ImageSharp
this.Array = array; this.Array = array;
this.Length = length; this.Length = length;
this.Start = start; this.Start = start;
this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf<T>() * start);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start. /// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start.
/// </summary> /// </summary>
/// <param name="array">The pinned array</param> /// <param name="array">The pinned array</param>
/// <param name="pointerToArray">Pointer to the beginning of the array</param>
/// <param name="start">The index at which to begin the span.</param> /// <param name="start">The index at which to begin the span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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)); DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start));
this.Array = array; this.Array = array;
this.Length = array.Length - start; this.Length = array.Length - start;
this.Start = start; this.Start = start;
this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf<T>() * start);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array. /// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array.
/// </summary> /// </summary>
/// <param name="array">The pinned array</param> /// <param name="array">The pinned array</param>
/// <param name="pointerToArray">Pointer to the start of 'array'</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferSpan(T[] array, void* pointerToArray) public BufferSpan(T[] array)
{ {
GuardArrayAndPointer(array, pointerToArray); GuardArray(array);
this.Array = array; this.Array = array;
this.Start = 0; this.Start = 0;
this.Length = array.Length; this.Length = array.Length;
this.PointerAtOffset = (IntPtr)pointerToArray;
} }
/// <summary> /// <summary>
@ -101,11 +95,6 @@ namespace ImageSharp
/// </summary> /// </summary>
public int ByteOffset => this.Start * Unsafe.SizeOf<T>(); public int ByteOffset => this.Start * Unsafe.SizeOf<T>();
/// <summary>
/// Gets the pointer to the offseted array position
/// </summary>
public IntPtr PointerAtOffset { get; private set; }
/// <summary> /// <summary>
/// Returns a reference to specified element of the span. /// Returns a reference to specified element of the span.
/// </summary> /// </summary>
@ -117,35 +106,14 @@ namespace ImageSharp
get get
{ {
DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); DebugGuard.MustBeLessThan(index, this.Length, nameof(index));
ref T arrayRef = ref this.DangerousGetPinnableReference();
byte* ptr = (byte*)this.PointerAtOffset + BufferSpan.SizeOf<T>(index); return ref Unsafe.Add(ref arrayRef, this.Start);
return ref Unsafe.AsRef<T>(ptr);
} }
} }
/// <summary>
/// Convertes <see cref="BufferSpan{T}"/> instance to a raw 'void*' pointer
/// </summary>
/// <param name="bufferSpan">The <see cref="BufferSpan{T}"/> to convert</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator void*(BufferSpan<T> bufferSpan)
{
return (void*)bufferSpan.PointerAtOffset;
}
/// <summary>
/// Converts <see cref="BufferSpan{T}"/> instance to a raw 'byte*' pointer
/// </summary>
/// <param name="bufferSpan">The <see cref="BufferSpan{T}"/> to convert</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator byte*(BufferSpan<T> bufferSpan)
{
return (byte*)bufferSpan.PointerAtOffset;
}
/// <summary> /// <summary>
/// Converts generic <see cref="BufferSpan{T}"/> to a <see cref="BufferSpan{T}"/> of bytes /// Converts generic <see cref="BufferSpan{T}"/> to a <see cref="BufferSpan{T}"/> of bytes
/// setting it's <see cref="Start"/> and <see cref="PointerAtOffset"/> to correct values. /// setting it's <see cref="Start"/> to correct value.
/// </summary> /// </summary>
/// <param name="source">The <see cref="BufferSpan{T}"/> to convert</param> /// <param name="source">The <see cref="BufferSpan{T}"/> to convert</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -154,7 +122,6 @@ namespace ImageSharp
BufferSpan<byte> result = default(BufferSpan<byte>); BufferSpan<byte> result = default(BufferSpan<byte>);
result.Array = Unsafe.As<byte[]>(source.Array); result.Array = Unsafe.As<byte[]>(source.Array);
result.Start = source.Start * Unsafe.SizeOf<T>(); result.Start = source.Start * Unsafe.SizeOf<T>();
result.PointerAtOffset = source.PointerAtOffset;
return result; return result;
} }
@ -164,7 +131,7 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <returns>The reference to the 0th element</returns> /// <returns>The reference to the 0th element</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T DangerousGetPinnableReference() => ref Unsafe.AsRef<T>((void*)this.PointerAtOffset); public ref T DangerousGetPinnableReference() => ref this.Array[this.Start];
/// <summary> /// <summary>
/// Forms a slice out of the given BufferSpan, beginning at 'start'. /// Forms a slice out of the given BufferSpan, beginning at 'start'.
@ -179,7 +146,6 @@ namespace ImageSharp
BufferSpan<T> result = default(BufferSpan<T>); BufferSpan<T> result = default(BufferSpan<T>);
result.Array = this.Array; result.Array = this.Array;
result.Start = this.Start + start; result.Start = this.Start + start;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * start);
result.Length = this.Length - start; result.Length = this.Length - start;
return result; return result;
} }
@ -199,7 +165,6 @@ namespace ImageSharp
BufferSpan<T> result = default(BufferSpan<T>); BufferSpan<T> result = default(BufferSpan<T>);
result.Array = this.Array; result.Array = this.Array;
result.Start = this.Start + start; result.Start = this.Start + start;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * start);
result.Length = length; result.Length = length;
return result; return result;
} }
@ -213,14 +178,8 @@ namespace ImageSharp
{ {
DebugGuard.MustBeLessThanOrEqualTo(count, this.Length, nameof(count)); DebugGuard.MustBeLessThanOrEqualTo(count, this.Length, nameof(count));
if (count < 256) // TODO: Use Unsafe.InitBlock(ref T) for small arrays, when it get's official
{ System.Array.Clear(this.Array, this.Start, count);
Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferSpan.USizeOf<T>(count));
}
else
{
System.Array.Clear(this.Array, this.Start, count);
}
} }
/// <summary> /// <summary>
@ -233,13 +192,9 @@ namespace ImageSharp
} }
[Conditional("DEBUG")] [Conditional("DEBUG")]
private static void GuardArrayAndPointer(T[] array, void* pointerToArray) private static void GuardArray(T[] array)
{ {
DebugGuard.NotNull(array, nameof(array)); DebugGuard.NotNull(array, nameof(array));
DebugGuard.IsFalse(
pointerToArray == (void*)0,
nameof(pointerToArray),
"pointerToArray should not be null pointer!");
} }
} }
} }

12
src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs

@ -126,9 +126,9 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="buffer">The <see cref="PinnedBuffer{T}"/> to convert.</param> /// <param name="buffer">The <see cref="PinnedBuffer{T}"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe implicit operator BufferSpan<T>(PinnedBuffer<T> buffer) public static implicit operator BufferSpan<T>(PinnedBuffer<T> buffer)
{ {
return new BufferSpan<T>(buffer.Array, (void*)buffer.Pointer, 0, buffer.Length); return new BufferSpan<T>(buffer.Array, 0, buffer.Length);
} }
/// <summary> /// <summary>
@ -150,9 +150,9 @@ namespace ImageSharp
/// <param name="start">The start</param> /// <param name="start">The start</param>
/// <returns>The <see cref="BufferSpan{T}"/></returns> /// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe BufferSpan<T> Slice(int start) public BufferSpan<T> Slice(int start)
{ {
return new BufferSpan<T>(this.Array, (void*)this.Pointer, start, this.Length - start); return new BufferSpan<T>(this.Array, start, this.Length - start);
} }
/// <summary> /// <summary>
@ -162,9 +162,9 @@ namespace ImageSharp
/// <param name="length">The length of the slice</param> /// <param name="length">The length of the slice</param>
/// <returns>The <see cref="BufferSpan{T}"/></returns> /// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe BufferSpan<T> Slice(int start, int length) public BufferSpan<T> Slice(int start, int length)
{ {
return new BufferSpan<T>(this.Array, (void*)this.Pointer, start, length); return new BufferSpan<T>(this.Array, start, length);
} }
/// <summary> /// <summary>

28
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs

@ -41,7 +41,7 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets an unsafe float* pointer to the beginning of <see cref="Span"/>. /// Gets an unsafe float* pointer to the beginning of <see cref="Span"/>.
/// </summary> /// </summary>
public float* Ptr => (float*)this.Span.PointerAtOffset; public ref float Ptr => ref this.Span.DangerousGetPinnableReference();
/// <summary> /// <summary>
/// Gets the lenghth of the weights window /// Gets the lenghth of the weights window
@ -56,19 +56,18 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedRowSum(BufferSpan<Vector4> rowSpan) public Vector4 ComputeWeightedRowSum(BufferSpan<Vector4> rowSpan)
{ {
float* horizontalValues = this.Ptr; ref float horizontalValues = ref this.Ptr;
int left = this.Left; int left = this.Left;
Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left);
vecPtr += left;
// Destination color components // Destination color components
Vector4 result = Vector4.Zero; Vector4 result = Vector4.Zero;
for (int i = 0; i < this.Length; i++) for (int i = 0; i < this.Length; i++)
{ {
float weight = horizontalValues[i]; float weight = Unsafe.Add(ref horizontalValues, i);
result += (*vecPtr) * weight; Vector4 v = Unsafe.Add(ref vecPtr, i);
vecPtr++; result += v * weight;
} }
return result; return result;
@ -83,19 +82,18 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeExpandedWeightedRowSum(BufferSpan<Vector4> rowSpan) public Vector4 ComputeExpandedWeightedRowSum(BufferSpan<Vector4> rowSpan)
{ {
float* horizontalValues = this.Ptr; ref float horizontalValues = ref this.Ptr;
int left = this.Left; int left = this.Left;
Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left);
vecPtr += left;
// Destination color components // Destination color components
Vector4 result = Vector4.Zero; Vector4 result = Vector4.Zero;
for (int i = 0; i < this.Length; i++) for (int i = 0; i < this.Length; i++)
{ {
float weight = horizontalValues[i]; float weight = Unsafe.Add(ref horizontalValues, i);
result += (*vecPtr).Expand() * weight; Vector4 v = Unsafe.Add(ref vecPtr, i);
vecPtr++; result += v.Expand() * weight;
} }
return result; return result;
@ -111,7 +109,7 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedColumnSum(PinnedImageBuffer<Vector4> firstPassPixels, int x) public Vector4 ComputeWeightedColumnSum(PinnedImageBuffer<Vector4> firstPassPixels, int x)
{ {
float* verticalValues = this.Ptr; ref float verticalValues = ref this.Ptr;
int left = this.Left; int left = this.Left;
// Destination color components // Destination color components
@ -119,7 +117,7 @@ namespace ImageSharp.Processing.Processors
for (int i = 0; i < this.Length; i++) for (int i = 0; i < this.Length; i++)
{ {
float yw = verticalValues[i]; float yw = Unsafe.Add(ref verticalValues, i);
int index = left + i; int index = left + i;
result += firstPassPixels[x, index] * yw; result += firstPassPixels[x, index] * yw;
} }

11
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs

@ -7,6 +7,7 @@ namespace ImageSharp.Processing.Processors
{ {
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
/// <summary> /// <summary>
@ -111,13 +112,15 @@ namespace ImageSharp.Processing.Processors
WeightsWindow ws = result.GetWeightsWindow(i, left, right); WeightsWindow ws = result.GetWeightsWindow(i, left, right);
result.Weights[i] = ws; result.Weights[i] = ws;
float* weights = ws.Ptr; ref float weights = ref ws.Ptr;
for (int j = left; j <= right; j++) for (int j = left; j <= right; j++)
{ {
float weight = sampler.GetValue((j - center) / scale); float weight = sampler.GetValue((j - center) / scale);
sum += weight; 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. // 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++) 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;
} }
} }
} }

243
tests/ImageSharp.Tests/Common/BufferSpanTests.cs

@ -1,5 +1,6 @@
// ReSharper disable ObjectCreationAsStatement // ReSharper disable ObjectCreationAsStatement
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Common namespace ImageSharp.Tests.Common
{ {
using System; using System;
@ -8,9 +9,20 @@ namespace ImageSharp.Tests.Common
using Xunit; using Xunit;
using static TestStructs; using static TestStructs;
public unsafe class BufferSpanTests public unsafe class BufferSpanTests
{ {
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
{
public static void SameRefs<T1, T2>(ref T1 a, ref T2 b)
{
ref T1 bb = ref Unsafe.As<T2, T1>(ref b);
True(Unsafe.AreSame(ref a, ref bb), "References are not same!");
}
}
[Fact] [Fact]
public void AsBytes() public void AsBytes()
{ {
@ -19,10 +31,10 @@ namespace ImageSharp.Tests.Common
using (PinnedBuffer<Foo> colorBuf = new PinnedBuffer<Foo>(fooz)) using (PinnedBuffer<Foo> colorBuf = new PinnedBuffer<Foo>(fooz))
{ {
BufferSpan<Foo> orig = colorBuf.Slice(1); BufferSpan<Foo> orig = colorBuf.Slice(1);
BufferSpan<byte> asBytes = (BufferSpan < byte > )orig; BufferSpan<byte> asBytes = (BufferSpan<byte>)orig;
Assert.Equal(asBytes.Start, sizeof(Foo)); Assert.Equal(asBytes.Start, sizeof(Foo));
Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); Assert.SameRefs(ref orig.DangerousGetPinnableReference(), ref asBytes.DangerousGetPinnableReference());
} }
} }
@ -32,16 +44,14 @@ namespace ImageSharp.Tests.Common
public void Basic() public void Basic()
{ {
Foo[] array = Foo.CreateArray(3); Foo[] array = Foo.CreateArray(3);
fixed (Foo* p = array)
{
// Act:
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p);
// Assert: // Act:
Assert.Equal(array, span.Array); BufferSpan<Foo> span = new BufferSpan<Foo>(array);
Assert.Equal((IntPtr)p, span.PointerAtOffset);
Assert.Equal(3, span.Length); // Assert:
} Assert.Equal(array, span.Array);
Assert.Equal(3, span.Length);
Assert.SameRefs(ref array[0], ref span.DangerousGetPinnableReference());
} }
[Fact] [Fact]
@ -49,17 +59,15 @@ namespace ImageSharp.Tests.Common
{ {
Foo[] array = Foo.CreateArray(4); Foo[] array = Foo.CreateArray(4);
int start = 2; int start = 2;
fixed (Foo* p = array)
{ // Act:
// Act: BufferSpan<Foo> span = new BufferSpan<Foo>(array, start);
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start);
// Assert:
// Assert: Assert.Equal(array, span.Array);
Assert.Equal(array, span.Array); Assert.Equal(start, span.Start);
Assert.Equal(start, span.Start); Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference());
Assert.Equal((IntPtr)(p + start), span.PointerAtOffset); Assert.Equal(array.Length - start, span.Length);
Assert.Equal(array.Length - start, span.Length);
}
} }
[Fact] [Fact]
@ -68,17 +76,14 @@ namespace ImageSharp.Tests.Common
Foo[] array = Foo.CreateArray(10); Foo[] array = Foo.CreateArray(10);
int start = 2; int start = 2;
int length = 3; int length = 3;
fixed (Foo* p = array) // Act:
{ BufferSpan<Foo> span = new BufferSpan<Foo>(array, start, length);
// Act:
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start, length); // Assert:
Assert.Equal(array, span.Array);
// Assert: Assert.Equal(start, span.Start);
Assert.Equal(array, span.Array); Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference());
Assert.Equal(start, span.Start); Assert.Equal(length, span.Length);
Assert.Equal((IntPtr)(p + start), span.PointerAtOffset);
Assert.Equal(length, span.Length);
}
} }
} }
@ -92,19 +97,16 @@ namespace ImageSharp.Tests.Common
int start1 = 2; int start1 = 2;
int totalOffset = start0 + start1; int totalOffset = start0 + start1;
fixed (Foo* p = array) BufferSpan<Foo> span = new BufferSpan<Foo>(array, start0);
{
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start0);
// Act: // Act:
span = span.Slice(start1); span = span.Slice(start1);
// Assert: // Assert:
Assert.Equal(array, span.Array); Assert.Equal(array, span.Array);
Assert.Equal(totalOffset, span.Start); Assert.Equal(totalOffset, span.Start);
Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference());
Assert.Equal(array.Length - totalOffset, span.Length); Assert.Equal(array.Length - totalOffset, span.Length);
}
} }
[Fact] [Fact]
@ -116,24 +118,19 @@ namespace ImageSharp.Tests.Common
int totalOffset = start0 + start1; int totalOffset = start0 + start1;
int sliceLength = 3; int sliceLength = 3;
fixed (Foo* p = array) BufferSpan<Foo> span = new BufferSpan<Foo>(array, start0);
{
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start0);
// Act: // Act:
span = span.Slice(start1, sliceLength); span = span.Slice(start1, sliceLength);
// Assert: // Assert:
Assert.Equal(array, span.Array); Assert.Equal(array, span.Array);
Assert.Equal(totalOffset, span.Start); Assert.Equal(totalOffset, span.Start);
Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference());
Assert.Equal(sliceLength, span.Length); Assert.Equal(sliceLength, span.Length);
}
} }
} }
[Theory] [Theory]
[InlineData(4)] [InlineData(4)]
[InlineData(1500)] [InlineData(1500)]
@ -142,21 +139,17 @@ namespace ImageSharp.Tests.Common
Foo[] array = Foo.CreateArray(count + 42); Foo[] array = Foo.CreateArray(count + 42);
int offset = 2; int offset = 2;
fixed (Foo* p = array) BufferSpan<Foo> ap = new BufferSpan<Foo>(array, offset);
{
BufferSpan<Foo> ap = new BufferSpan<Foo>(array, p, offset);
// Act: // Act:
ap.Clear(count); ap.Clear(count);
Assert.NotEqual(default(Foo), array[offset-1]); Assert.NotEqual(default(Foo), array[offset - 1]);
Assert.Equal(default(Foo), array[offset]); Assert.Equal(default(Foo), array[offset]);
Assert.Equal(default(Foo), array[offset + count-1]); Assert.Equal(default(Foo), array[offset + count - 1]);
Assert.NotEqual(default(Foo), array[offset + count]); Assert.NotEqual(default(Foo), array[offset + count]);
}
} }
public class Indexer public class Indexer
{ {
public static readonly TheoryData<int, int, int> IndexerData = public static readonly TheoryData<int, int, int> IndexerData =
@ -175,14 +168,11 @@ namespace ImageSharp.Tests.Common
public void Read(int length, int start, int index) public void Read(int length, int start, int index)
{ {
Foo[] a = Foo.CreateArray(length); Foo[] a = Foo.CreateArray(length);
fixed (Foo* p = a) BufferSpan<Foo> span = new BufferSpan<Foo>(a, start);
{
BufferSpan<Foo> span = new BufferSpan<Foo>(a, p, start);
Foo element = span[index]; Foo element = span[index];
Assert.Equal(a[start + index], element); Assert.Equal(a[start + index], element);
}
} }
[Theory] [Theory]
@ -190,14 +180,11 @@ namespace ImageSharp.Tests.Common
public void Write(int length, int start, int index) public void Write(int length, int start, int index)
{ {
Foo[] a = Foo.CreateArray(length); Foo[] a = Foo.CreateArray(length);
fixed (Foo* p = a) BufferSpan<Foo> span = new BufferSpan<Foo>(a, start);
{
BufferSpan<Foo> span = new BufferSpan<Foo>(a, p, 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]);
}
} }
} }
@ -208,13 +195,10 @@ namespace ImageSharp.Tests.Common
public void DangerousGetPinnableReference(int start, int length) public void DangerousGetPinnableReference(int start, int length)
{ {
Foo[] a = Foo.CreateArray(length); Foo[] a = Foo.CreateArray(length);
fixed (Foo* p = a) BufferSpan<Foo> span = new BufferSpan<Foo>(a, start);
{ ref Foo r = ref span.DangerousGetPinnableReference();
BufferSpan<Foo> span = new BufferSpan<Foo>(a, p, start);
ref Foo r = ref span.DangerousGetPinnableReference();
Assert.True(Unsafe.AreSame(ref a[start], ref r)); Assert.True(Unsafe.AreSame(ref a[start], ref r));
}
} }
public class Copy public class Copy
@ -253,14 +237,10 @@ namespace ImageSharp.Tests.Common
Foo[] source = Foo.CreateArray(count + 2); Foo[] source = Foo.CreateArray(count + 2);
Foo[] dest = new Foo[count + 5]; Foo[] dest = new Foo[count + 5];
fixed (Foo* pSource = source) BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, 1);
fixed (Foo* pDest = dest) BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, 1);
{
BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, pSource, 1);
BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, pDest, 1);
BufferSpan.Copy(apSource, apDest, count-1); BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
AssertNotDefault(dest, 1); AssertNotDefault(dest, 1);
@ -268,7 +248,7 @@ namespace ImageSharp.Tests.Common
Assert.NotEqual(source[0], dest[0]); Assert.NotEqual(source[0], dest[0]);
Assert.Equal(source[1], dest[1]); Assert.Equal(source[1], dest[1]);
Assert.Equal(source[2], dest[2]); 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]); Assert.NotEqual(source[count], dest[count]);
} }
@ -280,14 +260,10 @@ namespace ImageSharp.Tests.Common
AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); AlignedFoo[] source = AlignedFoo.CreateArray(count + 2);
AlignedFoo[] dest = new AlignedFoo[count + 5]; AlignedFoo[] dest = new AlignedFoo[count + 5];
fixed (AlignedFoo* pSource = source) BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, 1);
fixed (AlignedFoo* pDest = dest) BufferSpan<AlignedFoo> apDest = new BufferSpan<AlignedFoo>(dest, 1);
{
BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, pSource, 1);
BufferSpan<AlignedFoo> apDest = new BufferSpan<AlignedFoo>(dest, pDest, 1);
BufferSpan.Copy(apSource, apDest, count - 1); BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
AssertNotDefault(dest, 1); AssertNotDefault(dest, 1);
@ -304,17 +280,13 @@ namespace ImageSharp.Tests.Common
[InlineData(1500)] [InlineData(1500)]
public void IntToInt(int count) public void IntToInt(int count)
{ {
int[] source = CreateTestInts(count+2); int[] source = CreateTestInts(count + 2);
int[] dest = new int[count + 5]; int[] dest = new int[count + 5];
fixed (int* pSource = source) BufferSpan<int> apSource = new BufferSpan<int>(source, 1);
fixed (int* pDest = dest) BufferSpan<int> apDest = new BufferSpan<int>(dest, 1);
{
BufferSpan<int> apSource = new BufferSpan<int>(source, pSource, 1);
BufferSpan<int> apDest = new BufferSpan<int>(dest, pDest, 1);
BufferSpan.Copy(apSource, apDest, count -1); BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
AssertNotDefault(dest, 1); AssertNotDefault(dest, 1);
@ -332,17 +304,13 @@ namespace ImageSharp.Tests.Common
public void GenericToBytes(int count) public void GenericToBytes(int count)
{ {
int destCount = count * sizeof(Foo); int destCount = count * sizeof(Foo);
Foo[] source = Foo.CreateArray(count+2); Foo[] source = Foo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(Foo)*2]; byte[] dest = new byte[destCount + sizeof(Foo) * 2];
fixed (Foo* pSource = source) BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, 1);
fixed (byte* pDest = dest) BufferSpan<byte> apDest = new BufferSpan<byte>(dest, sizeof(Foo));
{
BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, pSource, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest, sizeof(Foo));
BufferSpan.Copy(apSource, apDest, count - 1); BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
@ -362,14 +330,10 @@ namespace ImageSharp.Tests.Common
AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); AlignedFoo[] source = AlignedFoo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2]; byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2];
fixed (AlignedFoo* pSource = source) BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, 1);
fixed (byte* pDest = dest) BufferSpan<byte> apDest = new BufferSpan<byte>(dest, sizeof(AlignedFoo));
{
BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, pSource, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest, sizeof(AlignedFoo));
BufferSpan.Copy(apSource, apDest, count - 1); BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
@ -386,17 +350,13 @@ namespace ImageSharp.Tests.Common
public void IntToBytes(int count) public void IntToBytes(int count)
{ {
int destCount = count * sizeof(int); int destCount = count * sizeof(int);
int[] source = CreateTestInts(count+2); int[] source = CreateTestInts(count + 2);
byte[] dest = new byte[destCount + sizeof(int) + 1]; byte[] dest = new byte[destCount + sizeof(int) + 1];
fixed (int* pSource = source) BufferSpan<int> apSource = new BufferSpan<int>(source);
fixed (byte* pDest = dest) BufferSpan<byte> apDest = new BufferSpan<byte>(dest);
{
BufferSpan<int> apSource = new BufferSpan<int>(source, pSource);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest);
BufferSpan.Copy(apSource, apDest, count); BufferSpan.Copy(apSource, apDest, count);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
@ -413,15 +373,11 @@ namespace ImageSharp.Tests.Common
int srcCount = count * sizeof(Foo); int srcCount = count * sizeof(Foo);
byte[] source = CreateTestBytes(srcCount); byte[] source = CreateTestBytes(srcCount);
Foo[] dest = new Foo[count + 2]; Foo[] dest = new Foo[count + 2];
fixed(byte* pSource = source)
fixed (Foo* pDest = dest)
{
BufferSpan<byte> apSource = new BufferSpan<byte>(source, pSource);
BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, pDest);
BufferSpan.Copy(apSource, apDest, count); BufferSpan<byte> apSource = new BufferSpan<byte>(source);
} BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest);
BufferSpan.Copy(apSource, apDest, count);
AssertNotDefault(source, sizeof(Foo) + 1); AssertNotDefault(source, sizeof(Foo) + 1);
AssertNotDefault(dest, 1); AssertNotDefault(dest, 1);
@ -438,7 +394,7 @@ namespace ImageSharp.Tests.Common
Color[] colors = { new Color(0, 1, 2, 3), new Color(4, 5, 6, 7), new Color(8, 9, 10, 11), }; Color[] colors = { new Color(0, 1, 2, 3), new Color(4, 5, 6, 7), new Color(8, 9, 10, 11), };
using (PinnedBuffer<Color> colorBuf = new PinnedBuffer<Color>(colors)) using (PinnedBuffer<Color> colorBuf = new PinnedBuffer<Color>(colors))
using (PinnedBuffer<byte> byteBuf = new PinnedBuffer<byte>(colors.Length*4)) using (PinnedBuffer<byte> byteBuf = new PinnedBuffer<byte>(colors.Length * 4))
{ {
BufferSpan.Copy<Color>(colorBuf, byteBuf, colorBuf.Length); BufferSpan.Copy<Color>(colorBuf, byteBuf, colorBuf.Length);
@ -451,7 +407,6 @@ namespace ImageSharp.Tests.Common
} }
} }
internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index)
{ {
fixed (Foo* pArray = array) fixed (Foo* pArray = array)

23
tests/ImageSharp.Tests/Common/PinnedBufferTests.cs

@ -11,6 +11,19 @@
public unsafe class PinnedBufferTests public unsafe class PinnedBufferTests
{ {
private class Assert : Xunit.Assert
{
public static void SpanPointsTo<T>(IntPtr ptr, BufferSpan<T> span)
where T : struct
{
ref byte r = ref Unsafe.As<T, byte>(ref span.DangerousGetPinnableReference());
void* p = Unsafe.AsPointer(ref r);
Assert.Equal(ptr, (IntPtr)p);
}
}
[Theory] [Theory]
[InlineData(42)] [InlineData(42)]
[InlineData(1111)] [InlineData(1111)]
@ -121,7 +134,7 @@
Assert.True(buffer.IsDisposedOrLostArrayOwnership); Assert.True(buffer.IsDisposedOrLostArrayOwnership);
} }
[Theory] [Theory]
[InlineData(7)] [InlineData(7)]
[InlineData(123)] [InlineData(123)]
@ -133,7 +146,7 @@
Assert.Equal(buffer.Array, span.Array); Assert.Equal(buffer.Array, span.Array);
Assert.Equal(0, span.Start); Assert.Equal(0, span.Start);
Assert.Equal(buffer.Pointer, span.PointerAtOffset); Assert.SpanPointsTo(buffer.Pointer, span);
Assert.Equal(span.Length, bufferLength); Assert.Equal(span.Length, bufferLength);
} }
} }
@ -147,7 +160,7 @@
Assert.Equal(buffer.Array, span.Array); Assert.Equal(buffer.Array, span.Array);
Assert.Equal(0, span.Start); Assert.Equal(0, span.Start);
Assert.Equal(buffer.Pointer, span.PointerAtOffset); Assert.SpanPointsTo(buffer.Pointer, span);
Assert.Equal(span.Length, 42); Assert.Equal(span.Length, 42);
} }
} }
@ -166,7 +179,7 @@
Assert.Equal(buffer.Array, span.Array); Assert.Equal(buffer.Array, span.Array);
Assert.Equal(start, span.Start); Assert.Equal(start, span.Start);
Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), span.PointerAtOffset); Assert.SpanPointsTo(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), span);
Assert.Equal(span.Length, bufferLength - start); Assert.Equal(span.Length, bufferLength - start);
} }
} }
@ -182,7 +195,7 @@
Assert.Equal(buffer.Array, span.Array); Assert.Equal(buffer.Array, span.Array);
Assert.Equal(start, span.Start); Assert.Equal(start, span.Start);
Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), span.PointerAtOffset); Assert.SpanPointsTo(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), span);
Assert.Equal(span.Length, spanLength); Assert.Equal(span.Length, spanLength);
} }
} }

19
tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs

@ -1,6 +1,7 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Common namespace ImageSharp.Tests.Common
{ {
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Xunit; using Xunit;
@ -9,6 +10,20 @@ namespace ImageSharp.Tests.Common
public unsafe class PinnedImageBufferTests public unsafe class PinnedImageBufferTests
{ {
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
{
public static void SpanPointsTo<T>(IntPtr ptr, BufferSpan<T> span)
where T : struct
{
ref byte r = ref Unsafe.As<T, byte>(ref span.DangerousGetPinnableReference());
void* p = Unsafe.AsPointer(ref r);
Assert.Equal(ptr, (IntPtr)p);
}
}
[Theory] [Theory]
[InlineData(7, 42)] [InlineData(7, 42)]
[InlineData(1025, 17)] [InlineData(1025, 17)]
@ -65,7 +80,7 @@ namespace ImageSharp.Tests.Common
Assert.Equal(width * y, span.Start); Assert.Equal(width * y, span.Start);
Assert.Equal(width, span.Length); Assert.Equal(width, span.Length);
Assert.Equal(buffer.Pointer + sizeof(Foo) * width * y, span.PointerAtOffset); Assert.SpanPointsTo(buffer.Pointer + sizeof(Foo) * width * y, span);
} }
} }
@ -81,7 +96,7 @@ namespace ImageSharp.Tests.Common
Assert.Equal(width * y + x, span.Start); Assert.Equal(width * y + x, span.Start);
Assert.Equal(width - x, span.Length); Assert.Equal(width - x, span.Length);
Assert.Equal(buffer.Pointer + sizeof(Foo) * (width * y + x), span.PointerAtOffset); Assert.SpanPointsTo(buffer.Pointer + sizeof(Foo) * (width * y + x), span);
} }
} }

Loading…
Cancel
Save