Browse Source

fix accuracy issues

af/merge-core
Anton Firszov 8 years ago
parent
commit
f31c95e25d
  1. 20
      src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
  2. 57
      src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
  3. 70
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  4. 9
      tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs

20
src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs

@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp
{ {
Guard.IsTrue( Guard.IsTrue(
dest.Length % Vector<byte>.Count == 0, dest.Length % Vector<byte>.Count == 0,
nameof(source), nameof(dest),
"dest.Length should be divisable by Vector<byte>.Count!"); "dest.Length should be divisable by Vector<byte>.Count!");
int n = dest.Length / Vector<byte>.Count; int n = dest.Length / Vector<byte>.Count;
@ -93,8 +93,6 @@ namespace SixLabors.ImageSharp
ref Vector<float> sourceBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source)); ref Vector<float> sourceBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
ref Vector<byte> destBase = ref Unsafe.As<byte, Vector<byte>>(ref MemoryMarshal.GetReference(dest)); ref Vector<byte> destBase = ref Unsafe.As<byte, Vector<byte>>(ref MemoryMarshal.GetReference(dest));
Vector<float> scale = new Vector<float>(255);
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
{ {
ref Vector<float> s = ref Unsafe.Add(ref sourceBase, i * 4); ref Vector<float> s = ref Unsafe.Add(ref sourceBase, i * 4);
@ -104,10 +102,10 @@ namespace SixLabors.ImageSharp
Vector<float> f2 = Unsafe.Add(ref s, 2); Vector<float> f2 = Unsafe.Add(ref s, 2);
Vector<float> f3 = Unsafe.Add(ref s, 3); Vector<float> f3 = Unsafe.Add(ref s, 3);
Vector<uint> w0 = ConvertToUInt32(f0, scale); Vector<uint> w0 = ConvertToUInt32(f0);
Vector<uint> w1 = ConvertToUInt32(f1, scale); Vector<uint> w1 = ConvertToUInt32(f1);
Vector<uint> w2 = ConvertToUInt32(f2, scale); Vector<uint> w2 = ConvertToUInt32(f2);
Vector<uint> w3 = ConvertToUInt32(f3, scale); Vector<uint> w3 = ConvertToUInt32(f3);
Vector<ushort> u0 = Vector.Narrow(w0, w1); Vector<ushort> u0 = Vector.Narrow(w0, w1);
Vector<ushort> u1 = Vector.Narrow(w2, w3); Vector<ushort> u1 = Vector.Narrow(w2, w3);
@ -119,10 +117,12 @@ namespace SixLabors.ImageSharp
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector<uint> ConvertToUInt32(Vector<float> vf, Vector<float> scale) private static Vector<uint> ConvertToUInt32(Vector<float> vf)
{ {
vf = Vector.Min(Vector.Max(vf, Vector<float>.Zero), Vector<float>.One); Vector<float> maxBytes = new Vector<float>(255f);
vf *= scale; vf *= maxBytes;
vf += new Vector<float>(0.5f);
vf = Vector.Min(Vector.Max(vf, Vector<float>.Zero), maxBytes);
Vector<int> vi = Vector.ConvertToInt32(vf); Vector<int> vi = Vector.ConvertToInt32(vf);
return Vector.AsVectorUInt32(vi); return Vector.AsVectorUInt32(vi);
} }

57
src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs

@ -52,22 +52,13 @@ namespace SixLabors.ImageSharp.PixelFormats
return; return;
} }
int remainder = count % 2; if (SimdUtils.ExtendedIntrinsics.IsAvailable)
int alignedCount = count - remainder;
if (alignedCount > 0)
{ {
ReadOnlySpan<float> rawSrc = MemoryMarshal.Cast<Vector4, float>(sourceVectors.Slice(0, alignedCount)); ConvertFromVector4ExtendedIntrinsics(sourceVectors, destinationColors, count);
Span<byte> rawDest = MemoryMarshal.Cast<Rgba32, byte>(destinationColors);
SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(rawSrc, rawDest);
} }
else
if (remainder > 0)
{ {
// actually: remainder == 1 ConvertFromVector4StandardIntrinsics(sourceVectors, destinationColors, count);
int lastIdx = count - 1;
destinationColors[lastIdx].PackFromVector4(sourceVectors[lastIdx]);
} }
} }
@ -144,6 +135,46 @@ namespace SixLabors.ImageSharp.PixelFormats
destinationVectors[lastIdx] = sourceColors[lastIdx].ToVector4(); destinationVectors[lastIdx] = sourceColors[lastIdx].ToVector4();
} }
} }
private static void ConvertFromVector4ExtendedIntrinsics(ReadOnlySpan<Vector4> sourceVectors, Span<Rgba32> destinationColors, int count)
{
int remainder = count % 8;
int alignedCount = count - remainder;
if (alignedCount > 0)
{
ReadOnlySpan<float> rawSrc = MemoryMarshal.Cast<Vector4, float>(sourceVectors);
Span<byte> rawDest = MemoryMarshal.Cast<Rgba32, byte>(destinationColors.Slice(0, alignedCount));
SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(rawSrc, rawDest);
}
if (remainder > 0)
{
PackFromVector4Common(sourceVectors.Slice(alignedCount), destinationColors.Slice(alignedCount), remainder);
}
}
private static void ConvertFromVector4StandardIntrinsics(ReadOnlySpan<Vector4> sourceVectors, Span<Rgba32> destinationColors, int count)
{
int remainder = count % 2;
int alignedCount = count - remainder;
if (alignedCount > 0)
{
ReadOnlySpan<float> rawSrc = MemoryMarshal.Cast<Vector4, float>(sourceVectors.Slice(0, alignedCount));
Span<byte> rawDest = MemoryMarshal.Cast<Rgba32, byte>(destinationColors);
SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(rawSrc, rawDest);
}
if (remainder > 0)
{
// actually: remainder == 1
int lastIdx = count - 1;
destinationColors[lastIdx].PackFromVector4(sourceVectors[lastIdx]);
}
}
} }
} }
} }

70
tests/ImageSharp.Tests/Common/SimdUtilsTests.cs

@ -160,31 +160,6 @@ namespace SixLabors.ImageSharp.Tests.Common
Assert.Equal(expected, dest); Assert.Equal(expected, dest);
} }
private static float Clamp255(float x) => Math.Min(255f, Math.Max(0f, x));
[Theory]
[InlineData(1, 0)]
[InlineData(1, 8)]
[InlineData(2, 16)]
[InlineData(3, 128)]
public void BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count)
{
if (this.SkipOnNonAvx2())
{
return;
}
float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444);
float[] normalized = orig.Select(f => f / 255f).ToArray();
byte[] dest = new byte[count];
SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(normalized, dest);
byte[] expected = orig.Select(f => (byte)Clamp255(f)).ToArray();
Assert.Equal(expected, dest);
}
[Theory] [Theory]
[InlineData(1, 0)] [InlineData(1, 0)]
@ -222,23 +197,44 @@ namespace SixLabors.ImageSharp.Tests.Common
Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f));
} }
public static readonly TheoryData<int> BulkConvertNormalizedFloatToByteClampOverflows_Data =
new TheoryData<int>
{
0, 64, 1024
};
[Theory] [Theory]
[InlineData(1, 0)] [MemberData(nameof(BulkConvertNormalizedFloatToByteClampOverflows_Data))]
[InlineData(2, 32)] public void BulkConvertNormalizedFloatToByteClampOverflows(int count)
[InlineData(3, 128)]
public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count)
{ {
float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444); if (this.SkipOnNonAvx2())
float[] normalized = orig.Select(f => f / 255f).ToArray(); {
return;
}
byte[] dest = new byte[count]; float[] source = new Random(count).GenerateRandomFloatArray(count, -0.1f, 1.2f);
byte[] expected = source.Select(NormalizedFloatToByte).ToArray();
byte[] actual = new byte[count];
SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(normalized, dest); SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(source, actual);
byte[] expected = orig.Select(f => (byte)Clamp255(f)).ToArray(); Assert.Equal(expected, actual);
}
Assert.Equal(expected, dest); [Theory]
[MemberData(nameof(BulkConvertNormalizedFloatToByteClampOverflows_Data))]
public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count)
{
float[] source = new Random(count).GenerateRandomFloatArray(count, -0.1f, 1.2f);
byte[] expected = source.Select(NormalizedFloatToByte).ToArray();
byte[] actual = new byte[count];
SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(source, actual);
Assert.Equal(expected, actual);
} }
private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, f * 255f + 0.5f));
[Theory] [Theory]
[InlineData(0)] [InlineData(0)]
@ -265,7 +261,7 @@ namespace SixLabors.ImageSharp.Tests.Common
float[] source = { 0, 7, 42, 255, 0.5f, 1.1f, 2.6f, 16f }; float[] source = { 0, 7, 42, 255, 0.5f, 1.1f, 2.6f, 16f };
var expected = source.Select(f => (byte)Math.Round(f)).ToArray(); byte[] expected = source.Select(f => (byte)Math.Round(f)).ToArray();
source = source.Select(f => f / 255f).ToArray(); source = source.Select(f => f / 255f).ToArray();
@ -299,8 +295,6 @@ namespace SixLabors.ImageSharp.Tests.Common
iiRef = x; iiRef = x;
//Tuple8.OfUInt32 ii = Unsafe.As<Vector<float>, Tuple8.OfUInt32>(ref x);
ref Tuple8.OfByte d = ref MemoryMarshal.Cast<byte, Tuple8.OfByte>(dest)[0]; ref Tuple8.OfByte d = ref MemoryMarshal.Cast<byte, Tuple8.OfByte>(dest)[0];
d.LoadFrom(ref ii); d.LoadFrom(ref ii);

9
tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs

@ -33,19 +33,20 @@ namespace SixLabors.ImageSharp.Tests
return values; return values;
} }
public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, int minVal, int maxValExclusive) public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, float minVal, float maxVal)
{ {
float[] values = new float[length]; float[] values = new float[length];
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
int val = rnd.Next(minVal, maxValExclusive); values[i] = (float) Math.Round(rnd.GetRandomFloat(minVal, maxVal));
values[i] = (float)val;
} }
return values; return values;
} }
public static byte[] GenerateRandomByteArray(this Random rnd, int length) public static byte[] GenerateRandomByteArray(this Random rnd, int length)
{ {
byte[] values = new byte[length]; byte[] values = new byte[length];
@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests
return values; return values;
} }
private static float GetRandomFloat(Random rnd, float minVal, float maxVal) private static float GetRandomFloat(this Random rnd, float minVal, float maxVal)
{ {
return (float)rnd.NextDouble() * (maxVal - minVal) + minVal; return (float)rnd.NextDouble() * (maxVal - minVal) + minVal;
} }

Loading…
Cancel
Save