Browse Source

implemented Color.BulkOperations.ToVector4()

af/merge-core
Anton Firszov 9 years ago
parent
commit
793fdf41d6
  1. 117
      src/ImageSharp/Colors/Color.BulkOperations.cs
  2. 2
      src/ImageSharp/Colors/Color.cs
  3. 3
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  4. 133
      tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
  5. 25
      tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs
  6. 38
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

117
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
{
/// <summary>
/// <see cref="BulkPixelOperations{TColor}"/> implementation optimized for <see cref="Color"/>.
/// </summary>
internal class BulkOperations : BulkPixelOperations<Color>
{
private static readonly int VectorSize = Vector<uint>.Count;
/// <summary>
/// Value type to store <see cref="Color"/>-s unpacked into multiple <see cref="uint"/>-s.
/// </summary>
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<Color> source,
ArrayPointer<Vector4> destination,
/// <summary>
/// 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="BufferPointer{T}"/> to the source colors.</param>
/// <param name="destVectors">The <see cref="BufferPointer{T}"/> to the dstination vectors.</param>
/// <param name="count">The number of pixels to convert.</param>
/// <remarks>
/// Implementation adapted from:
/// <see>
/// <cref>http://stackoverflow.com/a/5362789</cref>
/// </see>
/// </remarks>
internal static unsafe void ToVector4SimdAligned(
BufferPointer<Color> sourceColors,
BufferPointer<Vector4> destVectors,
int count)
{
int vecSize = Vector<uint>.Count;
DebugGuard.IsTrue(
count % VectorSize == 0,
count % vecSize == 0,
nameof(count),
"Argument 'count' should divisible by Vector<uint>.Count!");
"Argument 'count' should divisible by Vector<uint>.Count!"
);
Vector<float> bVec = new Vector<float>(256.0f / 255.0f);
Vector<uint> magicInt = new Vector<uint>(1191182336);
Vector<float> magicFloat = new Vector<float>(32768.0f);
Vector<uint> mask = new Vector<uint>(255);
int rawInputSize = count * 4;
uint* src = (uint*)sourceColors.PointerAtOffset;
uint* srcEnd = src + count;
using (PinnedBuffer<uint> tempBuf = new PinnedBuffer<uint>(rawInputSize + Vector<uint>.Count))
{
uint* tPtr = (uint*)tempBuf.Pointer;
uint[] temp = tempBuf.Array;
float[] fTemp = Unsafe.As<float[]>(temp);
RGBAUint* dst = (RGBAUint*)tPtr;
for (; src < srcEnd; src++, dst++)
{
dst->Load(*src);
}
for (int i = 0; i < rawInputSize; i += vecSize)
{
Vector<uint> vi = new Vector<uint>(temp, i);
vi &= mask;
vi |= magicInt;
Vector<float> 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);
}
}
}
/// <inheritdoc />
internal override void ToVector4(BufferPointer<Color> sourceColors, BufferPointer<Vector4> destVectors, int count)
{
int remainder = count % Vector<uint>.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);
}
}
}
}

2
src/ImageSharp/Colors/Color.cs

@ -246,7 +246,7 @@ namespace ImageSharp
}
/// <inheritdoc />
public BulkPixelOperations<Color> CreateBulkOperations() => new BulkPixelOperations<Color>();
public BulkPixelOperations<Color> CreateBulkOperations() => new Color.BulkOperations();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]

3
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -272,6 +272,9 @@
<Compile Include="..\ImageSharp.Tests\TestImages.cs">
<Link>Tests\TestImages.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\TestUtilities\ApproximateFloatComparer.cs">
<Link>Tests\TestUtilities\ApproximateFloatComparer.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\TestUtilities\Attributes\ImageDataAttributeBase.cs">
<Link>Tests\TestUtilities\Attributes\ImageDataAttributeBase.cs</Link>
</Compile>

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

@ -10,13 +10,32 @@
public class Color : BulkPixelOperationsTests<ImageSharp.Color>
{
// 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 TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
[Fact]
public void IsSpecialImplementation()
{
Assert.IsType<ImageSharp.Color.BulkOperations>(BulkPixelOperations<ImageSharp.Color>.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<ImageSharp.Argb>
{
// 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 TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
}
[Theory]
@ -32,42 +51,56 @@
where TColor : struct, IPixel<TColor>
{
public static TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromVector4(int count)
private static BulkPixelOperations<TColor> Operations => BulkPixelOperations<TColor>.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<TSource, TDest>(
internal static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
Action<BulkPixelOperations<TColor>, BufferPointer<TSource>, BufferPointer<TDest>> action)
Action<BufferPointer<TSource>, BufferPointer<TDest>> action)
where TSource : struct
where TDest : struct
{
using (var buffers = new TestBuffers<TSource, TDest>(source, expected))
{
action(BulkPixelOperations<TColor>.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(),

25
tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs

@ -4,14 +4,13 @@
// </copyright>
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<float>
{
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);

38
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<float>, IEqualityComparer<Vector4>
{
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();
}
}
}
Loading…
Cancel
Save