Browse Source

Merge remote-tracking branch 'refs/remotes/origin/master' into experiments-no-packed-color

# Conflicts:
#	src/ImageSharp/Colors/Color.BulkOperations.cs
#	src/ImageSharp/Colors/PackedPixel/Rgba32.cs

#	tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs
#	tests/ImageSharp.Benchmarks/General/ClearBuffer.cs
#	tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
#	tests/ImageSharp.Tests/Common/BufferSpanTests.cs
af/merge-core
James Jackson-South 9 years ago
parent
commit
17e87ebc81
  1. 2
      src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
  2. 2
      src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
  3. 2
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  4. 2
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
  5. 2
      src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
  6. 207
      src/ImageSharp/Colors/Color.BulkOperations.cs
  7. 132
      src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs
  8. 99
      src/ImageSharp/Common/Memory/Buffer.cs
  9. 22
      src/ImageSharp/Common/Memory/Buffer2D.cs
  10. 10
      src/ImageSharp/Common/Memory/Buffer2DExtensions.cs
  11. 96
      src/ImageSharp/Common/Memory/BufferSpan.cs
  12. 88
      src/ImageSharp/Common/Memory/BufferSpan{T}.cs
  13. 4
      src/ImageSharp/Common/Memory/IBuffer2D.cs
  14. 11
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  15. 45
      src/ImageSharp/Formats/Jpeg/Utils/JpegUtils.cs
  16. 41
      src/ImageSharp/Image/PixelAccessor{TColor}.cs
  17. 22
      src/ImageSharp/Image/PixelArea{TColor}.cs
  18. 34
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
  19. 11
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  20. 4
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  21. 16
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs
  22. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs
  23. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
  24. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs
  25. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs
  26. 37
      tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
  27. 8
      tests/ImageSharp.Benchmarks/General/ClearBuffer.cs
  28. 7
      tests/ImageSharp.Benchmarks/General/IterateArray.cs
  29. 360
      tests/ImageSharp.Benchmarks/General/PixelIndexing.cs
  30. 6
      tests/ImageSharp.Sandbox46/Program.cs
  31. 58
      tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
  32. 32
      tests/ImageSharp.Tests/Common/Buffer2DTests.cs
  33. 273
      tests/ImageSharp.Tests/Common/BufferSpanTests.cs
  34. 100
      tests/ImageSharp.Tests/Common/BufferTests.cs
  35. 2
      tests/ImageSharp.Tests/Common/TestStructs.cs

2
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<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{
BufferSpan<float> slice = buffer.Slice(offset);

2
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<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{
BufferSpan<float> slice = buffer.Slice(offset);

2
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<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{
BufferSpan<float> slice = buffer.Slice(offset);

2
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<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{
BufferSpan<float> slice = buffer.Slice(offset);

2
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<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{
BufferSpan<float> slice = buffer.Slice(offset);

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

@ -1,4 +1,4 @@
// <copyright file="Color.BulkOperations.cs" company="James Jackson-South">
// <copyright file="Color32.BulkOperations.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -8,6 +8,7 @@ namespace ImageSharp
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/// <content>
/// Conains the definition of <see cref="BulkOperations"/>
@ -15,7 +16,7 @@ namespace ImageSharp
public partial struct Color32
{
/// <summary>
/// <see cref="BulkPixelOperations{TColor}"/> implementation optimized for <see cref="Color32"/>.
/// <see cref="BulkPixelOperations{TColor32}"/> implementation optimized for <see cref="Color32"/>.
/// </summary>
internal class BulkOperations : BulkPixelOperations<Color32>
{
@ -23,7 +24,7 @@ namespace ImageSharp
/// SIMD optimized bulk implementation of <see cref="IPixel.PackFromVector4(Vector4)"/>
/// that works only with `count` divisible by <see cref="Vector{UInt32}.Count"/>.
/// </summary>
/// <param name="sourceColors">The <see cref="BufferSpan{T}"/> to the source colors.</param>
/// <param name="sourceColor32s">The <see cref="BufferSpan{T}"/> to the source colors.</param>
/// <param name="destVectors">The <see cref="BufferSpan{T}"/> to the dstination vectors.</param>
/// <param name="count">The number of pixels to convert.</param>
/// <remarks>
@ -37,14 +38,14 @@ namespace ImageSharp
/// </see>
/// </remarks>
internal static unsafe void ToVector4SimdAligned(
BufferSpan<Color32> sourceColors,
BufferSpan<Color32> sourceColor32s,
BufferSpan<Vector4> 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<uint>.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<Color32, uint>(ref sourceColor32s.DangerousGetPinnableReference());
using (PinnedBuffer<uint> tempBuf = new PinnedBuffer<uint>(
unpackedRawCount + Vector<uint>.Count))
using (Buffer<uint> tempBuf = new Buffer<uint>(
unpackedRawCount + Vector<uint>.Count))
{
uint* tPtr = (uint*)tempBuf.Pointer;
uint[] temp = tempBuf.Array;
float[] fTemp = Unsafe.As<float[]>(temp);
UnpackedRGBA* dst = (UnpackedRGBA*)tPtr;
for (; src < srcEnd; src++, dst++)
ref UnpackedRGBA tempBase = ref Unsafe.As<uint, UnpackedRGBA>(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<uint>(tempBuf, (BufferSpan<byte>)destVectors, unpackedRawCount);
BufferSpan.Copy(tempBuf.Span.AsBytes(), destVectors.AsBytes(), unpackedRawCount * sizeof(uint));
}
}
/// <inheritdoc />
internal override void ToVector4(BufferSpan<Color32> sourceColors, BufferSpan<Vector4> destVectors, int count)
internal override void ToVector4(BufferSpan<Color32> sourceColor32s, BufferSpan<Vector4> 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);
}
}
/// <inheritdoc />
internal override unsafe void PackFromXyzBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color32> destColors, int count)
internal override void PackFromXyzBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color32> destColor32s,
int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
ref RGB24 sourceRef = ref Unsafe.As<byte, RGB24>(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<Color32, RGB24>(ref dp) = sp;
dp.A = 255;
}
}
/// <inheritdoc />
internal override unsafe void ToXyzBytes(BufferSpan<Color32> sourceColors, BufferSpan<byte> destBytes, int count)
internal override void ToXyzBytes(BufferSpan<Color32> sourceColor32s, BufferSpan<byte> destBytes, int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;
ref Color32 sourceRef = ref sourceColor32s.DangerousGetPinnableReference();
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);
*(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<Color32, RGB24>(ref sp);
}
}
/// <inheritdoc />
internal override void PackFromXyzwBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color32> destColors, int count)
internal override unsafe void PackFromXyzwBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color32> destColor32s,
int count)
{
BufferSpan.Copy(sourceBytes, destColors, count);
BufferSpan.Copy(sourceBytes, destColor32s.AsBytes(), count * sizeof(Color32));
}
/// <inheritdoc />
internal override void ToXyzwBytes(BufferSpan<Color32> sourceColors, BufferSpan<byte> destBytes, int count)
internal override unsafe void ToXyzwBytes(BufferSpan<Color32> sourceColor32s, BufferSpan<byte> destBytes, int count)
{
BufferSpan.Copy(sourceColors, destBytes, count);
BufferSpan.Copy(sourceColor32s.AsBytes(), destBytes, count * sizeof(Color32));
}
/// <inheritdoc />
internal override unsafe void PackFromZyxBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color32> destColors, int count)
internal override void PackFromZyxBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color32> destColor32s,
int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
ref RGB24 sourceRef = ref Unsafe.As<byte, RGB24>(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<Color32, RGB24>(ref dp) = sp.ToZyx();
dp.A = 255;
}
}
/// <inheritdoc />
internal override unsafe void ToZyxBytes(BufferSpan<Color32> sourceColors, BufferSpan<byte> destBytes, int count)
internal override void ToZyxBytes(
BufferSpan<Color32> sourceColor32s,
BufferSpan<byte> destBytes,
int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;
ref Color32 sourceRef = ref sourceColor32s.DangerousGetPinnableReference();
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);
*(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<Color32, RGB24>(ref sp).ToZyx();
}
}
/// <inheritdoc />
internal override unsafe void PackFromZyxwBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color32> destColors, int count)
internal override void PackFromZyxwBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color32> destColor32s,
int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
ref RGBA32 sourceRef = ref Unsafe.As<byte, RGBA32>(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<RGBA32, Color32>(ref zyxw);
}
}
/// <inheritdoc />
internal override unsafe void ToZyxwBytes(BufferSpan<Color32> sourceColors, BufferSpan<byte> destBytes, int count)
internal override void ToZyxwBytes(
BufferSpan<Color32> sourceColor32s,
BufferSpan<byte> destBytes,
int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;
ref Color32 sourceRef = ref sourceColor32s.DangerousGetPinnableReference();
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);
*(destination + 1) = *(source + 1);
*(destination + 2) = *(source + 0);
*(destination + 3) = *(source + 3);
source += 4;
destination += 4;
ref RGBA32 sp = ref Unsafe.As<Color32, RGBA32>(ref Unsafe.Add(ref sourceRef, i));
ref RGBA32 dp = ref Unsafe.Add(ref destRef, i);
dp = sp.ToZyxw();
}
}
/// <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>
/// Value type to store <see cref="Color32"/>-s unpacked into multiple <see cref="uint"/>-s.
/// </summary>
[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;
}
}
}

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

@ -13,14 +13,9 @@ namespace ImageSharp
/// for pixel buffers of type <typeparamref name="TColor"/>.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public unsafe class BulkPixelOperations<TColor>
public class BulkPixelOperations<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>
/// Gets the global <see cref="BulkPixelOperations{TColor}"/> instance for the pixel type <typeparamref name="TColor"/>
/// </summary>
@ -37,18 +32,14 @@ namespace ImageSharp
BufferSpan<TColor> 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<Vector4>(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<Vector4> 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<TColor>(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<TColor> 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
/// <param name="count">The number of pixels to convert.</param>
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;
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);
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<TColor> 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<byte> 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<TColor>(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<TColor> 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
/// <param name="count">The number of pixels to convert.</param>
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;
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);
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<TColor> 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<byte> 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<TColor>(sp);
c.ToZyxwBytes(dest, i);
sp += ColorSize;
ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
sp.ToZyxwBytes(dest, destBytes.Start + (i * 4));
}
}
}

99
src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs → src/ImageSharp/Common/Memory/Buffer.cs

@ -1,4 +1,4 @@
// <copyright file="PinnedBuffer{T}.cs" company="James Jackson-South">
// <copyright file="Buffer{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -11,13 +11,18 @@ namespace ImageSharp
using System.Runtime.InteropServices;
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal class PinnedBuffer<T> : IDisposable
internal class Buffer<T> : IDisposable
where T : struct
{
/// <summary>
/// A pointer to the first element of <see cref="Array"/> when pinned.
/// </summary>
private IntPtr pointer;
/// <summary>
/// A handle that allows to access the managed <see cref="Array"/> as an unmanaged memory by pinning.
/// </summary>
@ -25,40 +30,38 @@ namespace ImageSharp
/// <summary>
/// A value indicating wheter <see cref="Array"/> should be returned to <see cref="PixelDataPool{T}"/>
/// when disposing this <see cref="PinnedBuffer{T}"/> instance.
/// when disposing this <see cref="Buffer{T}"/> instance.
/// </summary>
private bool isPoolingOwner;
/// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
/// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary>
/// <param name="length">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
public PinnedBuffer(int length)
public Buffer(int length)
{
this.Length = length;
this.Array = PixelDataPool<T>.Rent(length);
this.isPoolingOwner = true;
this.Pin();
}
/// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
/// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary>
/// <param name="array">The array to pin.</param>
public PinnedBuffer(T[] array)
public Buffer(T[] array)
{
this.Length = array.Length;
this.Array = array;
this.isPoolingOwner = false;
this.Pin();
}
/// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
/// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary>
/// <param name="array">The array to pin.</param>
/// <param name="length">The count of "relevant" elements in 'array'.</param>
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();
}
/// <summary>
/// Finalizes an instance of the <see cref="PinnedBuffer{T}"/> class.
/// Finalizes an instance of the <see cref="Buffer{T}"/> class.
/// </summary>
~PinnedBuffer()
~Buffer()
{
this.UnPin();
}
/// <summary>
/// Gets a value indicating whether this <see cref="PinnedBuffer{T}"/> instance is disposed, or has lost ownership of <see cref="Array"/>.
/// Gets a value indicating whether this <see cref="Buffer{T}"/> instance is disposed, or has lost ownership of <see cref="Array"/>.
/// </summary>
public bool IsDisposedOrLostArrayOwnership { get; private set; }
@ -94,11 +96,6 @@ namespace ImageSharp
/// </summary>
public T[] Array { get; private set; }
/// <summary>
/// Gets a pointer to the pinned <see cref="Array"/>.
/// </summary>
public IntPtr Pointer { get; private set; }
/// <summary>
/// Gets a <see cref="BufferSpan{T}"/> to the backing buffer.
/// </summary>
@ -109,37 +106,35 @@ namespace ImageSharp
/// </summary>
/// <param name="index">The index</param>
/// <returns>The reference to the specified element</returns>
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<T>(index);
return ref Unsafe.AsRef<T>(ptr);
return ref this.Array[index];
}
}
/// <summary>
/// Converts <see cref="PinnedBuffer{T}"/> to an <see cref="BufferSpan{T}"/>.
/// Converts <see cref="Buffer{T}"/> to an <see cref="BufferSpan{T}"/>.
/// </summary>
/// <param name="buffer">The <see cref="PinnedBuffer{T}"/> to convert.</param>
/// <param name="buffer">The <see cref="Buffer{T}"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe implicit operator BufferSpan<T>(PinnedBuffer<T> buffer)
public static implicit operator BufferSpan<T>(Buffer<T> buffer)
{
return new BufferSpan<T>(buffer.Array, (void*)buffer.Pointer, 0, buffer.Length);
return new BufferSpan<T>(buffer.Array, 0, buffer.Length);
}
/// <summary>
/// Creates a clean instance of <see cref="PinnedBuffer{T}"/> initializing it's elements with 'default(T)'.
/// Creates a clean instance of <see cref="Buffer{T}"/> initializing it's elements with 'default(T)'.
/// </summary>
/// <param name="count">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
/// <returns>The <see cref="PinnedBuffer{T}"/> instance</returns>
/// <returns>The <see cref="Buffer{T}"/> instance</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PinnedBuffer<T> CreateClean(int count)
public static Buffer<T> CreateClean(int count)
{
PinnedBuffer<T> buffer = new PinnedBuffer<T>(count);
Buffer<T> buffer = new Buffer<T>(count);
buffer.Clear();
return buffer;
}
@ -150,9 +145,9 @@ namespace ImageSharp
/// <param name="start">The start</param>
/// <returns>The <see cref="BufferSpan{T}"/></returns>
[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>
@ -162,13 +157,13 @@ namespace ImageSharp
/// <param name="length">The length of the slice</param>
/// <returns>The <see cref="BufferSpan{T}"/></returns>
[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>
/// Disposes the <see cref="PinnedBuffer{T}"/> instance by unpinning the array, and returning the pooled buffer when necessary.
/// Disposes the <see cref="Buffer{T}"/> instance by unpinning the array, and returning the pooled buffer when necessary.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
@ -199,11 +194,11 @@ namespace ImageSharp
/// </summary>
/// <returns>The unpinned <see cref="Array"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] UnPinAndTakeArrayOwnership()
public T[] TakeArrayOwnership()
{
if (this.IsDisposedOrLostArrayOwnership)
{
throw new InvalidOperationException("UnPinAndTakeArrayOwnership() is invalid: either PinnedBuffer<T> is disposed or UnPinAndTakeArrayOwnership() has been called multiple times!");
throw new InvalidOperationException("TakeArrayOwnership() is invalid: either Buffer<T> 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<T>)this).Clear();
this.Span.Clear();
}
/// <summary>
/// Pins <see cref="Array"/>.
/// </summary>
/// <returns>The pinned pointer</returns>
[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;
}
/// <summary>
@ -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;
}
}
}

22
src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs → src/ImageSharp/Common/Memory/Buffer2D.cs

@ -1,4 +1,4 @@
// <copyright file="PinnedImageBuffer{T}.cs" company="James Jackson-South">
// <copyright file="Buffer2D{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -9,19 +9,19 @@ namespace ImageSharp
using System.Runtime.CompilerServices;
/// <summary>
/// Represents a pinned buffer of value type objects
/// Represents a buffer of value type objects
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal class PinnedImageBuffer<T> : PinnedBuffer<T>, IPinnedImageBuffer<T>
internal class Buffer2D<T> : Buffer<T>, IBuffer2D<T>
where T : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="PinnedImageBuffer{T}"/> class.
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
public PinnedImageBuffer(int width, int height)
public Buffer2D(int width, int height)
: base(width * height)
{
this.Width = width;
@ -29,12 +29,12 @@ namespace ImageSharp
}
/// <summary>
/// Initializes a new instance of the <see cref="PinnedImageBuffer{T}"/> class.
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary>
/// <param name="array">The array to pin</param>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
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
}
/// <summary>
/// Creates a clean instance of <see cref="PinnedImageBuffer{T}"/> initializing it's elements with 'default(T)'.
/// Creates a clean instance of <see cref="Buffer2D{T}"/> initializing it's elements with 'default(T)'.
/// </summary>
/// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param>
/// <returns>The <see cref="PinnedBuffer{T}"/> instance</returns>
public static PinnedImageBuffer<T> CreateClean(int width, int height)
/// <returns>The <see cref="Buffer{T}"/> instance</returns>
public static Buffer2D<T> CreateClean(int width, int height)
{
PinnedImageBuffer<T> buffer = new PinnedImageBuffer<T>(width, height);
Buffer2D<T> buffer = new Buffer2D<T>(width, height);
buffer.Clear();
return buffer;
}

10
src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs → src/ImageSharp/Common/Memory/Buffer2DExtensions.cs

@ -1,4 +1,4 @@
// <copyright file="PinnedImageBufferExtensions{T}.cs" company="James Jackson-South">
// <copyright file="Buffer2DExtensions{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -9,9 +9,9 @@ namespace ImageSharp
using System.Runtime.CompilerServices;
/// <summary>
/// Defines extension methods for <see cref="IPinnedImageBuffer{T}"/>.
/// Defines extension methods for <see cref="IBuffer2D{T}"/>.
/// </summary>
internal static class PinnedImageBufferExtensions
internal static class Buffer2DExtensions
{
/// <summary>
/// Gets a <see cref="BufferSpan{T}"/> to the row 'y' beginning from the pixel at 'x'.
@ -22,7 +22,7 @@ namespace ImageSharp
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferSpan<T> GetRowSpan<T>(this IPinnedImageBuffer<T> buffer, int x, int y)
public static BufferSpan<T> GetRowSpan<T>(this IBuffer2D<T> 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
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferSpan<T> GetRowSpan<T>(this IPinnedImageBuffer<T> buffer, int y)
public static BufferSpan<T> GetRowSpan<T>(this IBuffer2D<T> buffer, int y)
where T : struct
{
return buffer.Span.Slice(y * buffer.Width, buffer.Width);

96
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
/// </summary>
internal static class BufferSpan
{
/// <summary>
/// It's worth to use Marshal.Copy() or Buffer.BlockCopy() over this size.
/// </summary>
private const int ByteCountThreshold = 1024;
/// <summary>
/// Copy 'count' number of elements of the same type from 'source' to 'dest'
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The input <see cref="BufferSpan{T}"/></param>
/// <param name="source">The <see cref="BufferSpan{T}"/> to copy elements from.</param>
/// <param name="destination">The destination <see cref="BufferSpan{T}"/>.</param>
/// <param name="count">The number of elements to copy</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(BufferSpan<T> source, BufferSpan<T> destination, int count)
public static unsafe void Copy<T>(BufferSpan<T> source, BufferSpan<T> destination, int count)
where T : struct
{
CopyImpl(source, destination, count);
}
DebugGuard.MustBeLessThanOrEqualTo(count, source.Length, nameof(count));
DebugGuard.MustBeLessThanOrEqualTo(count, destination.Length, nameof(count));
/// <summary>
/// Copy 'countInSource' elements of <typeparamref name="T"/> from 'source' into the raw byte buffer 'destination'.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The source buffer of <typeparamref name="T"/> elements to copy from.</param>
/// <param name="destination">The destination buffer.</param>
/// <param name="countInSource">The number of elements to copy from 'source'</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(BufferSpan<T> source, BufferSpan<byte> destination, int countInSource)
where T : struct
{
CopyImpl(source, destination, countInSource);
ref byte srcRef = ref Unsafe.As<T, byte>(ref source.DangerousGetPinnableReference());
ref byte destRef = ref Unsafe.As<T, byte>(ref destination.DangerousGetPinnableReference());
int byteCount = Unsafe.SizeOf<T>() * 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<T>();
Buffer.MemoryCopy(pSrc, pDest, destLength, byteCount);
#endif
}
}
/// <summary>
/// Copy 'countInDest' number of <typeparamref name="T"/> elements into 'dest' from a raw byte buffer defined by 'source'.
/// Copy all elements of 'source' into 'destination'.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The raw source buffer to copy from"/></param>
/// <param name="destination">The destination buffer"/></param>
/// <param name="countInDest">The number of <typeparamref name="T"/> elements to copy.</param>
/// <param name="source">The <see cref="BufferSpan{T}"/> to copy elements from.</param>
/// <param name="destination">The destination <see cref="BufferSpan{T}"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Copy<T>(BufferSpan<byte> source, BufferSpan<T> destination, int countInDest)
public static void Copy<T>(BufferSpan<T> source, BufferSpan<T> destination)
where T : struct
{
int byteCount = SizeOf<T>(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);
}
/// <summary>
@ -91,39 +79,5 @@ namespace ImageSharp
public static uint USizeOf<T>(int count)
where T : struct
=> (uint)SizeOf<T>(count);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CopyImpl<T, TDest>(BufferSpan<T> source, BufferSpan<TDest> destination, int count)
where T : struct
where TDest : struct
{
int byteCount = SizeOf<T>(count);
if (byteCount > ByteCountThreshold)
{
if (Unsafe.SizeOf<T>() == sizeof(long))
{
Marshal.Copy(Unsafe.As<long[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(int))
{
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);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(byte))
{
Marshal.Copy(Unsafe.As<byte[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
}
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount);
}
}
}

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

@ -12,7 +12,7 @@ namespace ImageSharp
/// <summary>
/// Represents a contiguous region of a pinned managed array.
/// The array is usually owned by a <see cref="PinnedBuffer{T}"/> instance.
/// The array is usually owned by a <see cref="Buffer{T}"/> instance.
/// </summary>
/// <remarks>
/// <see cref="BufferSpan{T}"/> is very similar to corefx System.Span&lt;T&gt;, and we try to maintain a compatible API.
@ -30,13 +30,12 @@ namespace ImageSharp
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start.
/// </summary>
/// <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="length">The length</param>
[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<T>() * start);
}
/// <summary>
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start.
/// </summary>
/// <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>
[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<T>() * start);
}
/// <summary>
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array.
/// </summary>
/// <param name="array">The pinned array</param>
/// <param name="pointerToArray">Pointer to the start of 'array'</param>
[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;
}
/// <summary>
/// Gets the backing array
/// Gets the backing array.
/// </summary>
public T[] Array { get; private set; }
@ -101,11 +95,6 @@ namespace ImageSharp
/// </summary>
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>
/// Returns a reference to specified element of the span.
/// </summary>
@ -117,45 +106,36 @@ namespace ImageSharp
get
{
DebugGuard.MustBeLessThan(index, this.Length, nameof(index));
byte* ptr = (byte*)this.PointerAtOffset + BufferSpan.SizeOf<T>(index);
return ref Unsafe.AsRef<T>(ptr);
ref T startRef = ref this.DangerousGetPinnableReference();
return ref Unsafe.Add(ref startRef, index);
}
}
/// <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
/// Converts generic <see cref="BufferSpan{T}"/> to a <see cref="BufferSpan{T}"/> of bytes
/// setting it's <see cref="Start"/> and <see cref="Length"/> to correct values.
/// </summary>
/// <param name="bufferSpan">The <see cref="BufferSpan{T}"/> to convert</param>
/// <returns>The span of bytes</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator byte*(BufferSpan<T> bufferSpan)
public BufferSpan<byte> AsBytes()
{
return (byte*)bufferSpan.PointerAtOffset;
BufferSpan<byte> result = default(BufferSpan<byte>);
result.Array = Unsafe.As<byte[]>(this.Array);
result.Start = this.Start * Unsafe.SizeOf<T>();
result.Length = this.Length * Unsafe.SizeOf<T>();
return result;
}
/// <summary>
/// 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.
/// 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.
/// </summary>
/// <param name="source">The <see cref="BufferSpan{T}"/> to convert</param>
/// <returns>The reference to the 0th element</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator BufferSpan<byte>(BufferSpan<T> source)
public ref T DangerousGetPinnableReference()
{
BufferSpan<byte> result = default(BufferSpan<byte>);
result.Array = Unsafe.As<byte[]>(source.Array);
result.Start = source.Start * Unsafe.SizeOf<T>();
result.PointerAtOffset = source.PointerAtOffset;
return result;
ref T origin = ref this.Array[0];
return ref Unsafe.Add(ref origin, this.Start);
}
/// <summary>
@ -171,7 +151,6 @@ namespace ImageSharp
BufferSpan<T> result = default(BufferSpan<T>);
result.Array = this.Array;
result.Start = this.Start + start;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * start);
result.Length = this.Length - start;
return result;
}
@ -191,7 +170,6 @@ namespace ImageSharp
BufferSpan<T> result = default(BufferSpan<T>);
result.Array = this.Array;
result.Start = this.Start + start;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * 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<T>(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);
}
/// <summary>
@ -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!");
}
}
}

4
src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs → src/ImageSharp/Common/Memory/IBuffer2D.cs

@ -1,4 +1,4 @@
// <copyright file="IPinnedImageBuffer{T}.cs" company="James Jackson-South">
// <copyright file="IBuffer2D{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -10,7 +10,7 @@ namespace ImageSharp
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal interface IPinnedImageBuffer<T>
internal interface IBuffer2D<T>
where T : struct
{
/// <summary>

11
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;
}
}
}

45
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;
/// <summary>
/// Jpeg specific utilities and extension methods
@ -33,19 +34,6 @@ namespace ImageSharp.Formats.Jpg
StretchPixels(dest, stretchFromX, stretchFromY);
}
/// <summary>
/// Copy an RGB value
/// </summary>
/// <param name="source">Source pointer</param>
/// <param name="dest">Destination pointer</param>
[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<TColor>(PixelArea<TColor> 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<TColor>(PixelArea<TColor> area, int y)
where TColor : struct, IPixel<TColor>
{
return ref Unsafe.As<byte, RGB24>(ref area.GetRowSpan(y).DangerousGetPinnableReference());
}
[StructLayout(LayoutKind.Sequential, Size = 3)]
private struct RGB24
{
}
}
}

41
src/ImageSharp/Image/PixelAccessor{TColor}.cs

@ -15,14 +15,9 @@ namespace ImageSharp
/// Provides per-pixel access to generic <see cref="Image{TColor}"/> pixels.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public sealed unsafe class PixelAccessor<TColor> : IDisposable, IPinnedImageBuffer<TColor>
public sealed class PixelAccessor<TColor> : IDisposable, IBuffer2D<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// The position of the first pixel in the image.
/// </summary>
private byte* pixelsBase;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
@ -35,9 +30,9 @@ namespace ImageSharp
private bool isDisposed;
/// <summary>
/// The <see cref="PinnedBuffer{T}"/> containing the pixel data.
/// The <see cref="Buffer{T}"/> containing the pixel data.
/// </summary>
private PinnedImageBuffer<TColor> pixelBuffer;
private Buffer2D<TColor> pixelBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TColor}"/> class.
@ -59,7 +54,7 @@ namespace ImageSharp
/// <param name="width">The width of the image represented by the pixel buffer.</param>
/// <param name="height">The height of the image represented by the pixel buffer.</param>
public PixelAccessor(int width, int height)
: this(width, height, PinnedImageBuffer<TColor>.CreateClean(width, height))
: this(width, height, Buffer2D<TColor>.CreateClean(width, height))
{
}
@ -69,7 +64,7 @@ namespace ImageSharp
/// <param name="width">The width of the image represented by the pixel buffer.</param>
/// <param name="height">The height of the image represented by the pixel buffer.</param>
/// <param name="pixels">The pixel buffer.</param>
private PixelAccessor(int width, int height, PinnedImageBuffer<TColor> pixels)
private PixelAccessor(int width, int height, Buffer2D<TColor> pixels)
{
Guard.NotNull(pixels, nameof(pixels));
Guard.MustBeGreaterThan(width, 0, nameof(width));
@ -93,11 +88,6 @@ namespace ImageSharp
/// </summary>
public TColor[] PixelArray => this.pixelBuffer.Array;
/// <summary>
/// Gets the pointer to the pixel buffer.
/// </summary>
public IntPtr DataPointer => this.pixelBuffer.Pointer;
/// <summary>
/// Gets the size of a single pixel in the number of bytes.
/// </summary>
@ -124,7 +114,7 @@ namespace ImageSharp
public ParallelOptions ParallelOptions { get; }
/// <inheritdoc />
BufferSpan<TColor> IPinnedImageBuffer<TColor>.Span => this.pixelBuffer;
BufferSpan<TColor> IBuffer2D<TColor>.Span => this.pixelBuffer;
private static BulkPixelOperations<TColor> Operations => BulkPixelOperations<TColor>.Instance;
@ -139,15 +129,13 @@ namespace ImageSharp
get
{
this.CheckCoordinates(x, y);
return Unsafe.Read<TColor>(this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf<TColor>()));
return this.PixelArray[(y * this.Width) + x];
}
set
{
this.CheckCoordinates(x, y);
Unsafe.Write(this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf<TColor>()), value);
this.PixelArray[(y * this.Width) + x] = value;
}
}
@ -179,7 +167,7 @@ namespace ImageSharp
/// </summary>
public void Reset()
{
Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height));
this.pixelBuffer.Clear();
}
/// <summary>
@ -251,7 +239,7 @@ namespace ImageSharp
/// <remarks>If <see cref="M:PixelAccessor.PooledMemory"/> is true then caller is responsible for ensuring <see cref="M:PixelDataPool.Return()"/> is called.</remarks>
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
/// <param name="target">The target pixel buffer accessor.</param>
internal void CopyTo(PixelAccessor<TColor> target)
{
uint byteCount = (uint)(this.Width * this.Height * Unsafe.SizeOf<TColor>());
Unsafe.CopyBlock(target.pixelsBase, this.pixelsBase, byteCount);
BufferSpan.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span);
}
/// <summary>
@ -424,7 +410,7 @@ namespace ImageSharp
private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels)
{
this.SetPixelBufferUnsafe(width, height, new PinnedImageBuffer<TColor>(pixels, width, height));
this.SetPixelBufferUnsafe(width, height, new Buffer2D<TColor>(pixels, width, height));
}
/// <summary>
@ -433,10 +419,9 @@ namespace ImageSharp
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="pixels">The pixel buffer</param>
private void SetPixelBufferUnsafe(int width, int height, PinnedImageBuffer<TColor> pixels)
private void SetPixelBufferUnsafe(int width, int height, Buffer2D<TColor> pixels)
{
this.pixelBuffer = pixels;
this.pixelsBase = (byte*)pixels.Pointer;
this.Width = width;
this.Height = height;

22
src/ImageSharp/Image/PixelArea{TColor}.cs

@ -13,7 +13,7 @@ namespace ImageSharp
/// Represents an area of generic <see cref="Image{TColor}"/> pixels.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
internal sealed unsafe class PixelArea<TColor> : IDisposable
internal sealed class PixelArea<TColor> : IDisposable
where TColor : struct, IPixel<TColor>
{
/// <summary>
@ -30,7 +30,7 @@ namespace ImageSharp
/// <summary>
/// The underlying buffer containing the raw pixel data.
/// </summary>
private PinnedBuffer<byte> byteBuffer;
private Buffer<byte> byteBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> 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<byte>(bytes);
this.PixelBase = (byte*)this.byteBuffer.Pointer;
this.byteBuffer = new Buffer<byte>(bytes);
}
/// <summary>
@ -117,8 +116,7 @@ namespace ImageSharp
this.RowStride = (width * GetComponentCount(componentOrder)) + padding;
this.Length = this.RowStride * height;
this.byteBuffer = new PinnedBuffer<byte>(this.Length);
this.PixelBase = (byte*)this.byteBuffer.Pointer;
this.byteBuffer = new Buffer<byte>(this.Length);
}
/// <summary>
@ -136,21 +134,11 @@ namespace ImageSharp
/// </summary>
public ComponentOrder ComponentOrder { get; }
/// <summary>
/// Gets the pointer to the pixel buffer.
/// </summary>
public IntPtr DataPointer => this.byteBuffer.Pointer;
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the data pointer.
/// </summary>
public byte* PixelBase { get; private set; }
/// <summary>
/// Gets the width of one row in the number of bytes.
/// </summary>
@ -198,7 +186,7 @@ namespace ImageSharp
/// </summary>
public void Reset()
{
Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowStride * this.Height));
this.byteBuffer.Clear();
}
/// <summary>

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

@ -41,7 +41,7 @@ namespace ImageSharp.Processing.Processors
/// <summary>
/// Gets an unsafe float* pointer to the beginning of <see cref="Span"/>.
/// </summary>
public float* Ptr => (float*)this.Span.PointerAtOffset;
public ref float Ptr => ref this.Span.DangerousGetPinnableReference();
/// <summary>
/// Gets the lenghth of the weights window
@ -56,19 +56,18 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedRowSum(BufferSpan<Vector4> 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<Vector4> 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
/// <param name="x">The column position</param>
/// <returns>The weighted sum</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedColumnSum(PinnedImageBuffer<Vector4> firstPassPixels, int x)
public Vector4 ComputeWeightedColumnSum(Buffer2D<Vector4> 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
/// </summary>
internal class WeightsBuffer : IDisposable
{
private PinnedImageBuffer<float> dataBuffer;
private Buffer2D<float> dataBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="WeightsBuffer"/> class.
@ -142,7 +140,7 @@ namespace ImageSharp.Processing.Processors
/// <param name="destinationSize">The size of the destination window</param>
public WeightsBuffer(int sourceSize, int destinationSize)
{
this.dataBuffer = PinnedImageBuffer<float>.CreateClean(sourceSize, destinationSize);
this.dataBuffer = Buffer2D<float>.CreateClean(sourceSize, destinationSize);
this.Weights = new WeightsWindow[destinationSize];
}

11
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;
/// <summary>
@ -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;
}
}
}

4
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -106,7 +106,7 @@ namespace ImageSharp.Processing.Processors
using (PixelAccessor<TColor> targetPixels = new PixelAccessor<TColor>(width, height))
{
using (PixelAccessor<TColor> sourcePixels = source.Lock())
using (PinnedImageBuffer<Vector4> firstPassPixels = new PinnedImageBuffer<Vector4>(width, source.Height))
using (Buffer2D<Vector4> firstPassPixels = new Buffer2D<Vector4>(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<Vector4> tempRowBuffer = new PinnedBuffer<Vector4>(sourcePixels.Width))
using (Buffer<Vector4> tempRowBuffer = new Buffer<Vector4>(sourcePixels.Width))
{
BufferSpan<TColor> sourceRow = sourcePixels.GetRowSpan(y);

16
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs

@ -6,7 +6,7 @@
using BenchmarkDotNet.Attributes;
using ImageSharp;
/// <summary>
/// Compares two implementation candidates for general BulkPixelOperations.ToVector4():
/// - One iterating with pointers
@ -14,9 +14,9 @@
/// </summary>
public unsafe class PackFromVector4ReferenceVsPointer
{
private PinnedBuffer<ImageSharp.Color32> destination;
private Buffer<ImageSharp.Color32> destination;
private PinnedBuffer<Vector4> source;
private Buffer<Vector4> source;
[Params(16, 128, 1024)]
public int Count { get; set; }
@ -24,8 +24,10 @@
[Setup]
public void Setup()
{
this.destination = new PinnedBuffer<ImageSharp.Color32>(this.Count);
this.source = new PinnedBuffer<Vector4>(this.Count * 4);
this.destination = new Buffer<ImageSharp.Color32>(this.Count);
this.source = new Buffer<Vector4>(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);

8
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs

@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk
public abstract class PackFromXyzw<TColor>
where TColor : struct, IPixel<TColor>
{
private PinnedBuffer<TColor> destination;
private Buffer<TColor> destination;
private PinnedBuffer<byte> source;
private Buffer<byte> 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<TColor>(this.Count);
this.source = new PinnedBuffer<byte>(this.Count * 4);
this.destination = new Buffer<TColor>(this.Count);
this.source = new Buffer<byte>(this.Count * 4);
}
[Cleanup]

8
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs

@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk
public abstract class ToVector4<TColor>
where TColor : struct, IPixel<TColor>
{
private PinnedBuffer<TColor> source;
private Buffer<TColor> source;
private PinnedBuffer<Vector4> destination;
private Buffer<Vector4> 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<TColor>(this.Count);
this.destination = new PinnedBuffer<Vector4>(this.Count);
this.source = new Buffer<TColor>(this.Count);
this.destination = new Buffer<Vector4>(this.Count);
}
[Cleanup]

8
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs

@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk
public abstract class ToXyz<TColor>
where TColor : struct, IPixel<TColor>
{
private PinnedBuffer<TColor> source;
private Buffer<TColor> source;
private PinnedBuffer<byte> destination;
private Buffer<byte> 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<TColor>(this.Count);
this.destination = new PinnedBuffer<byte>(this.Count * 3);
this.source = new Buffer<TColor>(this.Count);
this.destination = new Buffer<byte>(this.Count * 3);
}
[Cleanup]

8
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs

@ -13,9 +13,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk
public abstract class ToXyzw<TColor>
where TColor : struct, IPixel<TColor>
{
private PinnedBuffer<TColor> source;
private Buffer<TColor> source;
private PinnedBuffer<byte> destination;
private Buffer<byte> 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<TColor>(this.Count);
this.destination = new PinnedBuffer<byte>(this.Count * 4);
this.source = new Buffer<TColor>(this.Count);
this.destination = new Buffer<byte>(this.Count * 4);
}
[Cleanup]

37
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<T>")]
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<T>' | 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<T>' | 10 | 4.9625 ns | 0.0200 ns | 0.0347 ns | 0.24 | 0.00 |
* 'Copy using Marshal.Copy<T>' | 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<T>' | 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<T>' | 100 | 10.2472 ns | 0.0359 ns | 0.0622 ns | 0.32 | 0.00 |
* 'Copy using Marshal.Copy<T>' | 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<T>' | 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<T>' | 1000 | 35.7541 ns | 0.0480 ns | 0.0832 ns | 0.87 | 0.00 |
* 'Copy using Marshal.Copy<T>' | 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<T>' | 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<T>' | 10000 | 196.4679 ns | 0.2731 ns | 0.4730 ns | 0.98 | 0.00 |
* 'Copy using Marshal.Copy<T>' | 10000 | 202.5392 ns | 0.5561 ns | 0.9631 ns | 1.01 | 0.00 |
*
*/
}
}

8
tests/ImageSharp.Benchmarks/General/ClearBuffer.cs

@ -11,15 +11,15 @@ namespace ImageSharp.Benchmarks.General
public unsafe class ClearBuffer
{
private PinnedBuffer<Color32> buffer;
private Buffer<Color32> buffer;
[Params(32, 128, 512)]
public int Count { get; set; }
[Setup]
public void Setup()
{
this.buffer = new PinnedBuffer<ImageSharp.Color32>(this.Count);
this.buffer = new Buffer<ImageSharp.Color32>(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));
}
}
}

7
tests/ImageSharp.Benchmarks/General/IterateArray.cs

@ -8,7 +8,7 @@ namespace ImageSharp.Benchmarks.General
public class IterateArray
{
// Usual pinned stuff
private PinnedBuffer<Vector4> buffer;
private Buffer<Vector4> 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<Vector4>(this.Length);
this.buffer = new Buffer<Vector4>(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++)

360
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
{
/// <summary>
/// https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Pinnable.cs
/// </summary>
protected class Pinnable<T>
{
public T Data;
}
/// <summary>
/// The indexer methods are encapsulated into a struct to make sure everything is inlined.
/// </summary>
internal struct Data
{
private Vector4* pointer;
private Pinnable<Vector4> pinnable;
private Vector4[] array;
private int width;
public Data(Buffer2D<Vector4> buffer)
{
this.pointer = (Vector4*)buffer.Pin();
this.pinnable = Unsafe.As<Pinnable<Vector4>>(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<Vector4>((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf<Vector4>()));
}
[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<Vector4>()), 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<Vector4> buffer;
protected int width;
protected int startIndex;
protected int endIndex;
protected Vector4* pointer;
protected Vector4[] array;
protected Pinnable<Vector4> pinnable;
// [Params(1024)]
public int Count { get; set; } = 1024;
[Setup]
public void Setup()
{
this.width = 2048;
this.buffer = new Buffer2D<Vector4>(2048, 2048);
this.pointer = (Vector4*)this.buffer.Pin();
this.array = this.buffer.Array;
this.pinnable = Unsafe.As<Pinnable<Vector4>>(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;
}
}
}
}

6
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...");

58
tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs

@ -10,22 +10,22 @@ namespace ImageSharp.Tests.Colors
public class BulkPixelOperationsTests
{
public class Color : BulkPixelOperationsTests<ImageSharp.Color32>
public class Color32 : BulkPixelOperationsTests<ImageSharp.Color32>
{
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<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
//public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
[Fact]
public void IsSpecialImplementation()
{
Assert.IsType<ImageSharp.Color32.BulkOperations>(BulkPixelOperations<ImageSharp.Color32>.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<ImageSharp.Color32> source = new PinnedBuffer<ImageSharp.Color32>(count))
using (PinnedBuffer<Vector4> dest = new PinnedBuffer<Vector4>(count))
using (Buffer<ImageSharp.Color32> source = new Buffer<ImageSharp.Color32>(count))
using (Buffer<Vector4> dest = new Buffer<Vector4>(count))
{
this.Measure(
times,
@ -66,13 +66,13 @@ namespace ImageSharp.Tests.Colors
{
}
public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
//public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
}
[Theory]
[WithBlankImages(1, 1, PixelTypes.All)]
public void GetGlobalInstance<TColor>(TestImageProvider<TColor> dummy)
where TColor:struct, IPixel<TColor>
where TColor : struct, IPixel<TColor>
{
Assert.NotNull(BulkPixelOperations<TColor>.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<TSource, TDest> : IDisposable
where TSource : struct
where TDest : struct
{
public PinnedBuffer<TSource> SourceBuffer { get; }
public PinnedBuffer<TDest> ActualDestBuffer { get; }
public PinnedBuffer<TDest> ExpectedDestBuffer { get; }
public Buffer<TSource> SourceBuffer { get; }
public Buffer<TDest> ActualDestBuffer { get; }
public Buffer<TDest> ExpectedDestBuffer { get; }
public BufferSpan<TSource> Source => this.SourceBuffer;
public BufferSpan<TDest> ActualDest => this.ActualDestBuffer;
public TestBuffers(TSource[] source, TDest[] expectedDest)
{
this.SourceBuffer = new PinnedBuffer<TSource>(source);
this.ExpectedDestBuffer = new PinnedBuffer<TDest>(expectedDest);
this.ActualDestBuffer = new PinnedBuffer<TDest>(expectedDest.Length);
this.SourceBuffer = new Buffer<TSource>(source);
this.ExpectedDestBuffer = new Buffer<TDest>(expectedDest);
this.ActualDestBuffer = new Buffer<TDest>(expectedDest.Length);
}
public void Dispose()

32
tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs → 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<T>(BufferSpan<T> span, Buffer<T> 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<Foo> buffer = new PinnedImageBuffer<Foo>(width, height))
using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(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<Foo> buffer = new PinnedImageBuffer<Foo>(array, width, height))
using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(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<int> buffer = PinnedImageBuffer<int>.CreateClean(42, 42))
using (Buffer2D<int> buffer = Buffer2D<int>.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<Foo> buffer = new PinnedImageBuffer<Foo>(width, height))
using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(width, height))
{
BufferSpan<Foo> 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<Foo> buffer = new PinnedImageBuffer<Foo>(width, height))
using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(width, height))
{
BufferSpan<Foo> 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<Foo> buffer = new PinnedImageBuffer<Foo>(width, height))
using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(width, height))
{
Foo[] array = buffer.Array;

273
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<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]
public void AsBytes()
{
Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) };
using (PinnedBuffer<Foo> colorBuf = new PinnedBuffer<Foo>(fooz))
using (Buffer<Foo> colorBuf = new Buffer<Foo>(fooz))
{
BufferSpan<Foo> orig = colorBuf.Slice(1);
BufferSpan<byte> asBytes = (BufferSpan < byte > )orig;
BufferSpan<byte> asBytes = orig.AsBytes();
Assert.Equal(asBytes.Start, sizeof(Foo));
Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset);
Assert.Equal(orig.Length * Unsafe.SizeOf<Foo>(), 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<Foo> span = new BufferSpan<Foo>(array, p);
// Assert:
Assert.Equal(array, span.Array);
Assert.Equal((IntPtr)p, span.PointerAtOffset);
Assert.Equal(3, span.Length);
}
// Act:
BufferSpan<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(array, p, start0);
BufferSpan<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(array, p, start0);
BufferSpan<Foo> span = new BufferSpan<Foo>(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<Foo> ap = new BufferSpan<Foo>(array, p, offset);
BufferSpan<Foo> ap = new BufferSpan<Foo>(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<int, int, int> 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<Foo> span = new BufferSpan<Foo>(a, p, start);
BufferSpan<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(a, p, start);
BufferSpan<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(a, start);
BufferSpan<byte> bytes = span.AsBytes();
byte actual = bytes[index * Unsafe.SizeOf<Foo>() + byteOffset];
ref byte baseRef = ref Unsafe.As<Foo, byte>(ref a[0]);
byte expected = Unsafe.Add(ref baseRef, (start + index) * Unsafe.SizeOf<Foo>() + 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<Foo> span = new BufferSpan<Foo>(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<Foo> apSource = new BufferSpan<Foo>(source, pSource, 1);
BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, pDest, 1);
BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, 1);
BufferSpan<Foo> apDest = new BufferSpan<Foo>(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<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, pSource, 1);
BufferSpan<AlignedFoo> apDest = new BufferSpan<AlignedFoo>(dest, pDest, 1);
BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, 1);
BufferSpan<AlignedFoo> apDest = new BufferSpan<AlignedFoo>(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<int> apSource = new BufferSpan<int>(source, pSource, 1);
BufferSpan<int> apDest = new BufferSpan<int>(dest, pDest, 1);
BufferSpan<int> apSource = new BufferSpan<int>(source, 1);
BufferSpan<int> apDest = new BufferSpan<int>(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<Foo> apSource = new BufferSpan<Foo>(source, pSource, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest, sizeof(Foo));
BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(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<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, pSource, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest, sizeof(AlignedFoo));
BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(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<int> apSource = new BufferSpan<int>(source, pSource);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest);
BufferSpan<int> apSource = new BufferSpan<int>(source);
BufferSpan<byte> apDest = new BufferSpan<byte>(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<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.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<Color32> colorBuf = new PinnedBuffer<Color32>(colors))
using (PinnedBuffer<byte> byteBuf = new PinnedBuffer<byte>(colors.Length*4))
using (Buffer<Color32> colorBuf = new Buffer<Color32>(colors))
using (Buffer<byte> byteBuf = new Buffer<byte>(colors.Length * 4))
{
BufferSpan.Copy<Color32>(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)

100
tests/ImageSharp.Tests/Common/PinnedBufferTests.cs → 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<T>(BufferSpan<T> span, Buffer<T> 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<Foo> buffer = new PinnedBuffer<Foo>(count))
using (Buffer<Foo> buffer = new Buffer<Foo>(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<Foo> buffer = new PinnedBuffer<Foo>(array))
using (Buffer<Foo> buffer = new Buffer<Foo>(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<Foo> buffer = new PinnedBuffer<Foo>(a))
using (Buffer<Foo> buffer = new Buffer<Foo>(a))
{
buffer.Clear();
@ -63,7 +78,7 @@
{
for (int i = 0; i < 100; i++)
{
using (PinnedBuffer<int> buffer = PinnedBuffer<int>.CreateClean(42))
using (Buffer<int> buffer = Buffer<int>.CreateClean(42))
{
for (int j = 0; j < buffer.Length; j++)
{
@ -90,7 +105,7 @@
{
Foo[] a = Foo.CreateArray(length);
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(a))
using (Buffer<Foo> buffer = new Buffer<Foo>(a))
{
Foo element = buffer[index];
@ -104,7 +119,7 @@
{
Foo[] a = Foo.CreateArray(length);
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(a))
using (Buffer<Foo> buffer = new Buffer<Foo>(a))
{
buffer[index] = new Foo(666, 666);
@ -116,24 +131,24 @@
[Fact]
public void Dispose()
{
PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42);
Buffer<Foo> buffer = new Buffer<Foo>(42);
buffer.Dispose();
Assert.True(buffer.IsDisposedOrLostArrayOwnership);
}
[Theory]
[InlineData(7)]
[InlineData(123)]
public void CastToSpan(int bufferLength)
{
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(bufferLength))
using (Buffer<Foo> buffer = new Buffer<Foo>(bufferLength))
{
BufferSpan<Foo> 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<Foo> buffer = new PinnedBuffer<Foo>(42))
using (Buffer<Foo> buffer = new Buffer<Foo>(42))
{
BufferSpan<Foo> 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<Foo> buffer = new PinnedBuffer<Foo>(bufferLength))
using (Buffer<Foo> buffer = new Buffer<Foo>(bufferLength))
{
BufferSpan<Foo> span = buffer.Slice(start);
Assert.Equal(buffer.Array, span.Array);
Assert.Equal(start, span.Start);
Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), 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<Foo> buffer = new PinnedBuffer<Foo>(bufferLength))
using (Buffer<Foo> buffer = new Buffer<Foo>(bufferLength))
{
BufferSpan<Foo> span = buffer.Slice(start, spanLength);
Assert.Equal(buffer.Array, span.Array);
Assert.Equal(start, span.Start);
Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), span.PointerAtOffset);
Assert.SpanPointsTo(span, buffer, start);
Assert.Equal(span.Length, spanLength);
}
}
@ -192,9 +207,9 @@
public void UnPinAndTakeArrayOwnership()
{
Foo[] data = null;
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42))
using (Buffer<Foo> buffer = new Buffer<Foo>(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<Foo> buffer)
public class Pin
{
IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]);
Assert.Equal(ptr, buffer.Pointer);
[Fact]
public void ReturnsPinnedPointerToTheBeginningOfArray()
{
using (Buffer<Foo> buffer = new Buffer<Foo>(42))
{
Foo* actual = (Foo*)buffer.Pin();
fixed (Foo* expected = buffer.Array)
{
Assert.Equal(expected, actual);
}
}
}
[Fact]
public void SecondCallReturnsTheSamePointer()
{
using (Buffer<Foo> buffer = new Buffer<Foo>(42))
{
IntPtr ptr1 = buffer.Pin();
IntPtr ptr2 = buffer.Pin();
Assert.Equal(ptr1, ptr2);
}
}
[Fact]
public void WhenCalledOnDisposedBuffer_ThrowsInvalidOperationException()
{
Buffer<Foo> buffer = new Buffer<Foo>(42);
buffer.Dispose();
Assert.Throws<InvalidOperationException>(() => buffer.Pin());
}
}
}
}

2
tests/ImageSharp.Tests/Common/TestStructs.cs

@ -25,6 +25,8 @@ namespace ImageSharp.Tests.Common
}
return result;
}
public override string ToString() => $"({this.A},{this.B})";
}

Loading…
Cancel
Save