From decb84078fc4cb8c4988ffd40e5bb2cb97aa3e5b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Mar 2017 00:45:13 +0100 Subject: [PATCH] implemented Color.BulkOperations.ToVector4() --- src/ImageSharp/Colors/Color.BulkOperations.cs | 117 ++++++++++++++- src/ImageSharp/Colors/Color.cs | 2 +- .../ImageSharp.Sandbox46.csproj | 3 + .../Colors/BulkPixelOperationsTests.cs | 133 ++++++++++++------ .../Formats/Jpg/JpegUtilityTestFixture.cs | 25 +--- .../TestUtilities/ApproximateFloatComparer.cs | 38 +++++ 6 files changed, 246 insertions(+), 72 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs 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