diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs
index 75d9eb580..9d698b8ac 100644
--- a/src/ImageSharp/Colors/Color.BulkOperations.cs
+++ b/src/ImageSharp/Colors/Color.BulkOperations.cs
@@ -1,24 +1,129 @@
namespace ImageSharp
{
+ using System;
using System.Numerics;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
public partial struct Color
{
+ ///
+ /// implementation optimized for .
+ ///
internal class BulkOperations : BulkPixelOperations
{
- private static readonly int VectorSize = Vector.Count;
+ ///
+ /// Value type to store -s unpacked into multiple -s.
+ ///
+ private struct RGBAUint
+ {
+ 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 >> Color.GreenShift;
+ this.b = p >> Color.BlueShift;
+ this.a = p >> Color.AlphaShift;
+ }
+ }
- internal static void PackToVector4Aligned(
- ArrayPointer source,
- ArrayPointer destination,
+ ///
+ /// SIMD optimized bulk implementation of
+ /// that works only with `count` divisible by .
+ ///
+ /// The to the source colors.
+ /// The to the dstination vectors.
+ /// The number of pixels to convert.
+ ///
+ /// Implementation adapted from:
+ ///
+ /// http://stackoverflow.com/a/5362789
+ ///
+ ///
+ internal static unsafe void ToVector4SimdAligned(
+ BufferPointer sourceColors,
+ BufferPointer destVectors,
int count)
{
+ int vecSize = Vector.Count;
+
DebugGuard.IsTrue(
- count % VectorSize == 0,
+ count % vecSize == 0,
nameof(count),
- "Argument 'count' should divisible by Vector.Count!");
+ "Argument 'count' should divisible by Vector.Count!"
+ );
+
+ Vector bVec = new Vector(256.0f / 255.0f);
+ Vector magicInt = new Vector(1191182336);
+ Vector magicFloat = new Vector(32768.0f);
+ Vector mask = new Vector(255);
+
+ int rawInputSize = count * 4;
+
+ uint* src = (uint*)sourceColors.PointerAtOffset;
+ uint* srcEnd = src + count;
+
+ using (PinnedBuffer tempBuf = new PinnedBuffer(rawInputSize + Vector.Count))
+ {
+ uint* tPtr = (uint*)tempBuf.Pointer;
+ uint[] temp = tempBuf.Array;
+ float[] fTemp = Unsafe.As(temp);
+ RGBAUint* dst = (RGBAUint*)tPtr;
+
+ for (; src < srcEnd; src++, dst++)
+ {
+ dst->Load(*src);
+ }
+
+ for (int i = 0; i < rawInputSize; i += vecSize)
+ {
+ Vector vi = new Vector(temp, i);
+
+ vi &= mask;
+ vi |= magicInt;
+
+ Vector vf = Vector.AsVectorSingle(vi);
+ vf = (vf - magicFloat) * bVec;
+ vf.CopyTo(fTemp, i);
+ }
+
+ // TODO: Replace this with an optimized ArrayPointer.Copy() implementation:
+ uint byteCount = (uint)rawInputSize * sizeof(float);
+
+ if (byteCount > 1024u)
+ {
+ Marshal.Copy(fTemp, 0, destVectors.PointerAtOffset, rawInputSize);
+ }
+ else
+ {
+ Unsafe.CopyBlock((void*)destVectors, tPtr, byteCount);
+ }
+ }
+ }
+
+ ///
+ internal override void ToVector4(BufferPointer sourceColors, BufferPointer destVectors, int count)
+ {
+ int remainder = count % Vector.Count;
+
+ int alignedCount = count - remainder;
+ if (alignedCount > 0)
+ {
+ ToVector4SimdAligned(sourceColors, destVectors, alignedCount);
+ }
+ if (remainder > 0)
+ {
+ sourceColors = sourceColors.Slice(alignedCount);
+ destVectors = destVectors.Slice(alignedCount);
+ base.ToVector4(sourceColors, destVectors, remainder);
+ }
}
}
}
diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs
index 1ad6f9a64..597730937 100644
--- a/src/ImageSharp/Colors/Color.cs
+++ b/src/ImageSharp/Colors/Color.cs
@@ -246,7 +246,7 @@ namespace ImageSharp
}
///
- public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations();
+ public BulkPixelOperations CreateBulkOperations() => new Color.BulkOperations();
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
index 1d7899486..a8b7ceb33 100644
--- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
+++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
@@ -272,6 +272,9 @@
Tests\TestImages.cs
+
+ Tests\TestUtilities\ApproximateFloatComparer.cs
+
Tests\TestUtilities\Attributes\ImageDataAttributeBase.cs
diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
index 80d5952a1..41abd9d4a 100644
--- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
+++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
@@ -10,13 +10,32 @@
public class Color : BulkPixelOperationsTests
{
// For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class:
- public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 };
+ public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 };
+
+ [Fact]
+ public void IsSpecialImplementation()
+ {
+ Assert.IsType(BulkPixelOperations.Instance);
+ }
+
+ [Fact]
+ public void ToVector4SimdAligned()
+ {
+ ImageSharp.Color[] source = CreatePixelTestData(64);
+ Vector4[] expected = CreateExpectedVector4Data(source);
+
+ TestOperation(
+ source,
+ expected,
+ (s, d) => ImageSharp.Color.BulkOperations.ToVector4SimdAligned(s, d, 64)
+ );
+ }
}
public class Argb : BulkPixelOperationsTests
{
// For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class:
- public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 };
+ public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 };
}
[Theory]
@@ -32,42 +51,56 @@
where TColor : struct, IPixel
{
public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 };
-
- [Theory]
- [MemberData(nameof(ArraySizesData))]
- public void PackFromVector4(int count)
+
+ private static BulkPixelOperations Operations => BulkPixelOperations.Instance;
+
+ internal static TColor[] CreateExpectedPixelData(Vector4[] source)
{
- Vector4[] source = CreateVector4TestData(count);
- TColor[] expected = new TColor[count];
+ TColor[] expected = new TColor[source.Length];
- for (int i = 0; i < count; i++)
+ for (int i = 0; i < expected.Length; i++)
{
expected[i].PackFromVector4(source[i]);
}
+ return expected;
+ }
+
+ [Theory]
+ [MemberData(nameof(ArraySizesData))]
+ public void PackFromVector4(int count)
+ {
+ Vector4[] source = CreateVector4TestData(count);
+ TColor[] expected = CreateExpectedPixelData(source);
TestOperation(
source,
expected,
- (ops, s, d) => ops.PackFromVector4(s, d, count)
+ (s, d) => Operations.PackFromVector4(s, d, count)
);
}
- [Theory]
- [MemberData(nameof(ArraySizesData))]
- public void PackToVector4(int count)
+ internal static Vector4[] CreateExpectedVector4Data(TColor[] source)
{
- TColor[] source = CreatePixelTestData(count);
- Vector4[] expected = new Vector4[count];
+ Vector4[] expected = new Vector4[source.Length];
- for (int i = 0; i < count; i++)
+ for (int i = 0; i < expected.Length; i++)
{
expected[i] = source[i].ToVector4();
}
+ return expected;
+ }
+
+ [Theory]
+ [MemberData(nameof(ArraySizesData))]
+ public void ToVector4(int count)
+ {
+ TColor[] source = CreatePixelTestData(count);
+ Vector4[] expected = CreateExpectedVector4Data(source);
TestOperation(
source,
expected,
- (ops, s, d) => ops.ToVector4(s, d, count)
+ (s, d) => Operations.ToVector4(s, d, count)
);
}
@@ -89,13 +122,13 @@
TestOperation(
source,
expected,
- (ops, s, d) => ops.PackFromXyzBytes(s, d, count)
+ (s, d) => Operations.PackFromXyzBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
- public void PackToXyzBytes(int count)
+ public void ToXyzBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 3];
@@ -109,7 +142,7 @@
TestOperation(
source,
expected,
- (ops, s, d) => ops.ToXyzBytes(s, d, count)
+ (s, d) => Operations.ToXyzBytes(s, d, count)
);
}
@@ -130,13 +163,13 @@
TestOperation(
source,
expected,
- (ops, s, d) => ops.PackFromXyzwBytes(s, d, count)
+ (s, d) => Operations.PackFromXyzwBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
- public void PackToXyzwBytes(int count)
+ public void ToXyzwBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 4];
@@ -150,7 +183,7 @@
TestOperation(
source,
expected,
- (ops, s, d) => ops.ToXyzwBytes(s, d, count)
+ (s, d) => Operations.ToXyzwBytes(s, d, count)
);
}
@@ -171,13 +204,13 @@
TestOperation(
source,
expected,
- (ops, s, d) => ops.PackFromZyxBytes(s, d, count)
+ (s, d) => Operations.PackFromZyxBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
- public void PackToZyxBytes(int count)
+ public void ToZyxBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 3];
@@ -191,7 +224,7 @@
TestOperation(
source,
expected,
- (ops, s, d) => ops.ToZyxBytes(s, d, count)
+ (s, d) => Operations.ToZyxBytes(s, d, count)
);
}
@@ -212,13 +245,13 @@
TestOperation(
source,
expected,
- (ops, s, d) => ops.PackFromZyxwBytes(s, d, count)
+ (s, d) => Operations.PackFromZyxwBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
- public void PackToZyxwBytes(int count)
+ public void ToZyxwBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 4];
@@ -232,7 +265,7 @@
TestOperation(
source,
expected,
- (ops, s, d) => ops.ToZyxwBytes(s, d, count)
+ (s, d) => Operations.ToZyxwBytes(s, d, count)
);
}
@@ -262,33 +295,51 @@
this.ExpectedDestBuffer.Dispose();
}
+ private const float Tolerance = 0.0001f;
+
public void Verify()
{
int count = this.ExpectedDestBuffer.Count;
- TDest[] expected = this.ExpectedDestBuffer.Array;
- TDest[] actual = this.ActualDestBuffer.Array;
- for (int i = 0; i < count; i++)
+
+ if (typeof(TDest) == typeof(Vector4))
{
- Assert.Equal(expected[i], actual[i]);
+ Vector4[] expected = this.ExpectedDestBuffer.Array as Vector4[];
+ Vector4[] actual = this.ActualDestBuffer.Array as Vector4[];
+
+ for (int i = 0; i < count; i++)
+ {
+ // ReSharper disable PossibleNullReferenceException
+ Assert.Equal(expected[i], actual[i], new ApproximateFloatComparer(0.001f));
+ // ReSharper restore PossibleNullReferenceException
+ }
+ }
+ else
+ {
+ TDest[] expected = this.ExpectedDestBuffer.Array;
+ TDest[] actual = this.ActualDestBuffer.Array;
+ for (int i = 0; i < count; i++)
+ {
+ Assert.Equal(expected[i], actual[i]);
+ }
}
}
}
- private static void TestOperation(
+ internal static void TestOperation(
TSource[] source,
TDest[] expected,
- Action, BufferPointer, BufferPointer> action)
+ Action, BufferPointer> action)
where TSource : struct
where TDest : struct
{
using (var buffers = new TestBuffers(source, expected))
{
- action(BulkPixelOperations.Instance, buffers.Source, buffers.ActualDest);
+ action(buffers.Source, buffers.ActualDest);
buffers.Verify();
}
}
- private static Vector4[] CreateVector4TestData(int length)
+ internal static Vector4[] CreateVector4TestData(int length)
{
Vector4[] result = new Vector4[length];
Random rnd = new Random(42); // Deterministic random values
@@ -300,7 +351,7 @@
return result;
}
- private static TColor[] CreatePixelTestData(int length)
+ internal static TColor[] CreatePixelTestData(int length)
{
TColor[] result = new TColor[length];
@@ -315,7 +366,7 @@
return result;
}
- private static byte[] CreateByteTestData(int length)
+ internal static byte[] CreateByteTestData(int length)
{
byte[] result = new byte[length];
Random rnd = new Random(42); // Deterministic random values
@@ -326,8 +377,8 @@
}
return result;
}
-
- private static Vector4 GetVector(Random rnd)
+
+ internal static Vector4 GetVector(Random rnd)
{
return new Vector4(
(float)rnd.NextDouble(),
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs
index 736225680..252b01138 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs
@@ -4,14 +4,13 @@
//
using System.Text;
-using ImageSharp.Formats;
+
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests
{
using System;
- using System.Collections.Generic;
using System.Diagnostics;
using ImageSharp.Formats.Jpg;
@@ -99,28 +98,6 @@ namespace ImageSharp.Tests
this.Output.WriteLine(bld.ToString());
}
- internal struct ApproximateFloatComparer : IEqualityComparer
- {
- private readonly float Eps;
-
- public ApproximateFloatComparer(float eps = 1f)
- {
- this.Eps = eps;
- }
-
- public bool Equals(float x, float y)
- {
- float d = x - y;
-
- return d > -this.Eps && d < this.Eps;
- }
-
- public int GetHashCode(float obj)
- {
- throw new InvalidOperationException();
- }
- }
-
protected void Print(string msg)
{
Debug.WriteLine(msg);
diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
new file mode 100644
index 000000000..333b645de
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
@@ -0,0 +1,38 @@
+namespace ImageSharp.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Numerics;
+
+ internal struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer
+ {
+ private readonly float Eps;
+
+ public ApproximateFloatComparer(float eps = 1f)
+ {
+ this.Eps = eps;
+ }
+
+ public bool Equals(float x, float y)
+ {
+ float d = x - y;
+
+ return d > -this.Eps && d < this.Eps;
+ }
+
+ public int GetHashCode(float obj)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool Equals(Vector4 a, Vector4 b)
+ {
+ return this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W);
+ }
+
+ public int GetHashCode(Vector4 obj)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+}
\ No newline at end of file