diff --git a/src/ImageSharp/Common/Extensions/SimdUtils.cs b/src/ImageSharp/Common/Extensions/SimdUtils.cs
deleted file mode 100644
index 7b77fefcac..0000000000
--- a/src/ImageSharp/Common/Extensions/SimdUtils.cs
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Diagnostics;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace SixLabors.ImageSharp
-{
- ///
- /// Various extension and utility methods for and utilizing SIMD capabilities
- ///
- internal static class SimdUtils
- {
- ///
- /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte.
- ///
- public static bool IsAvx2CompatibleArchitecture => Vector.Count == 8 && Vector.Count == 8;
-
- internal static void GuardAvx2(string operation)
- {
- if (!IsAvx2CompatibleArchitecture)
- {
- throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!");
- }
- }
-
- ///
- /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics.
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static Vector4 PseudoRound(this Vector4 v)
- {
- var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1));
-
- return v + (sign * 0.5f);
- }
-
- ///
- /// Rounds all values in 'v' to the nearest integer following semantics.
- /// Source:
- ///
- /// https://github.com/g-truc/glm/blob/master/glm/simd/common.h#L110
- ///
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static Vector FastRound(this Vector x)
- {
- Vector magic0 = new Vector(int.MinValue); // 0x80000000
- Vector sgn0 = Vector.AsVectorSingle(magic0);
- Vector and0 = Vector.BitwiseAnd(sgn0, x);
- Vector or0 = Vector.BitwiseOr(and0, new Vector(8388608.0f));
- Vector add0 = Vector.Add(x, or0);
- Vector sub0 = Vector.Subtract(add0, or0);
- return sub0;
- }
-
- ///
- /// Convert 'source.Length' values normalized into [0..1] from 'source' into 'dest' buffer of values.
- /// The values gonna be scaled up into [0-255] and rounded.
- /// Based on:
- ///
- /// http://lolengine.net/blog/2011/3/20/understanding-fast-float-integer-conversions
- ///
- ///
- internal static void BulkConvertNormalizedFloatToByte(ReadOnlySpan source, Span dest)
- {
- GuardAvx2(nameof(BulkConvertNormalizedFloatToByte));
-
- DebugGuard.IsTrue((source.Length % Vector.Count) == 0, nameof(source), "source.Length should be divisable by Vector.Count!");
-
- if (source.Length == 0)
- {
- return;
- }
-
- ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
- int n = source.Length / 8;
-
- Vector magick = new Vector(32768.0f);
- Vector scale = new Vector(255f) / new Vector(256f);
-
- // need to copy to a temporary struct, because
- // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x)
- // does not work. TODO: This might be a CoreClr bug, need to ask/report
- var temp = default(Octet.OfUInt32);
- ref Vector tempRef = ref Unsafe.As>(ref temp);
-
- for (int i = 0; i < n; i++)
- {
- // union { float f; uint32_t i; } u;
- // u.f = 32768.0f + x * (255.0f / 256.0f);
- // return (uint8_t)u.i;
- Vector x = Unsafe.Add(ref srcBase, i);
- x = (x * scale) + magick;
- tempRef = x;
-
- ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i);
- d.LoadFrom(ref temp);
- }
- }
-
- ///
- /// Same as but clamps overflown values before conversion.
- ///
- internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest)
- {
- GuardAvx2(nameof(BulkConvertNormalizedFloatToByte));
-
- DebugGuard.IsTrue((source.Length % Vector.Count) == 0, nameof(source), "source.Length should be divisable by Vector.Count!");
-
- if (source.Length == 0)
- {
- return;
- }
-
- ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
- int n = source.Length / 8;
-
- Vector magick = new Vector(32768.0f);
- Vector scale = new Vector(255f) / new Vector(256f);
-
- // need to copy to a temporary struct, because
- // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x)
- // does not work. TODO: This might be a CoreClr bug, need to ask/report
- var temp = default(Octet.OfUInt32);
- ref Vector tempRef = ref Unsafe.As>(ref temp);
-
- for (int i = 0; i < n; i++)
- {
- // union { float f; uint32_t i; } u;
- // u.f = 32768.0f + x * (255.0f / 256.0f);
- // return (uint8_t)u.i;
- Vector x = Unsafe.Add(ref srcBase, i);
- x = Vector.Max(x, Vector.Zero);
- x = Vector.Min(x, Vector.One);
-
- x = (x * scale) + magick;
- tempRef = x;
-
- ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i);
- d.LoadFrom(ref temp);
- }
- }
-
- // TODO: Replace these with T4-d library level tuples!
- internal static class Octet
- {
- [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))]
- public struct OfUInt32
- {
- [FieldOffset(0 * sizeof(uint))]
- public uint V0;
-
- [FieldOffset(1 * sizeof(uint))]
- public uint V1;
-
- [FieldOffset(2 * sizeof(uint))]
- public uint V2;
-
- [FieldOffset(3 * sizeof(uint))]
- public uint V3;
-
- [FieldOffset(4 * sizeof(uint))]
- public uint V4;
-
- [FieldOffset(5 * sizeof(uint))]
- public uint V5;
-
- [FieldOffset(6 * sizeof(uint))]
- public uint V6;
-
- [FieldOffset(7 * sizeof(uint))]
- public uint V7;
-
- public override string ToString()
- {
- return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]";
- }
- }
-
- [StructLayout(LayoutKind.Explicit, Size = 8)]
- public struct OfByte
- {
- [FieldOffset(0)]
- public byte V0;
-
- [FieldOffset(1)]
- public byte V1;
-
- [FieldOffset(2)]
- public byte V2;
-
- [FieldOffset(3)]
- public byte V3;
-
- [FieldOffset(4)]
- public byte V4;
-
- [FieldOffset(5)]
- public byte V5;
-
- [FieldOffset(6)]
- public byte V6;
-
- [FieldOffset(7)]
- public byte V7;
-
- public override string ToString()
- {
- return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]";
- }
-
- public void LoadFrom(ref OfUInt32 i)
- {
- this.V0 = (byte)i.V0;
- this.V1 = (byte)i.V1;
- this.V2 = (byte)i.V2;
- this.V3 = (byte)i.V3;
- this.V4 = (byte)i.V4;
- this.V5 = (byte)i.V5;
- this.V6 = (byte)i.V6;
- this.V7 = (byte)i.V7;
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index 35769d96a7..02a2e9ee55 100644
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs
@@ -39,6 +39,28 @@ public static int LeastCommonMultiple(int a, int b)
return (a / GreatestCommonDivisor(a, b)) * b;
}
+ ///
+ /// Calculates % 4
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static int Modulo4(int x) => x & 3;
+
+ ///
+ /// Calculates % 8
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static int Modulo8(int x) => x & 7;
+
+ ///
+ /// Fast (x mod m) calculator, with the restriction that
+ /// should be power of 2.
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static int ModuloP2(int x, int m)
+ {
+ return x & (m - 1);
+ }
+
///
/// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation.
///
@@ -46,7 +68,7 @@ public static int LeastCommonMultiple(int a, int b)
/// A number that is greater than , but less than or equal to
///
/// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static int FastAbs(int x)
{
int y = x >> 31;
@@ -58,7 +80,7 @@ public static int FastAbs(int x)
///
/// A single-precision floating-point number
/// The number raised to the power of 2.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static float Pow2(float x) => x * x;
///
@@ -66,7 +88,7 @@ public static int FastAbs(int x)
///
/// A single-precision floating-point number
/// The number raised to the power of 3.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static float Pow3(float x) => x * x * x;
///
@@ -77,7 +99,7 @@ public static int FastAbs(int x)
///
/// The
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static int GetBitsNeededForColorDepth(int colors) => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2)));
///
@@ -85,7 +107,7 @@ public static int FastAbs(int x)
///
/// The bit depth.
/// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static int GetColorCountForBitDepth(int bitDepth) => 1 << bitDepth;
///
@@ -94,7 +116,7 @@ public static int FastAbs(int x)
/// The x provided to G(x).
/// The spread of the blur.
/// The Gaussian G(x)
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static float Gaussian(float x, float sigma)
{
const float Numerator = 1.0f;
@@ -117,7 +139,7 @@ public static float Gaussian(float x, float sigma)
///
/// The sine cardinal of .
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static float SinC(float f)
{
if (MathF.Abs(f) > Constants.Epsilon)
@@ -140,7 +162,7 @@ public static float SinC(float f)
///
/// The .
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static float GetBcValue(float x, float b, float c)
{
if (x < 0F)
@@ -176,7 +198,7 @@ public static float GetBcValue(float x, float b, float c)
///
/// The bounding .
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) => new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
///
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
new file mode 100644
index 0000000000..0f1ce2ab6a
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
@@ -0,0 +1,215 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Tuples;
+
+// ReSharper disable MemberHidesStaticFromOuterClass
+namespace SixLabors.ImageSharp
+{
+ internal static partial class SimdUtils
+ {
+ ///
+ /// Implementation with 256bit / AVX2 intrinsics NOT depending on newer API-s (Vector.Widen etc.)
+ ///
+ public static class BasicIntrinsics256
+ {
+ public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture;
+
+ ///
+ /// as many elements as possible, slicing them down (keeping the remainder).
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void BulkConvertByteToNormalizedFloatReduce(
+ ref ReadOnlySpan source,
+ ref Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+ if (!IsAvailable)
+ {
+ return;
+ }
+
+ int remainder = ImageMaths.Modulo8(source.Length);
+ int adjustedCount = source.Length - remainder;
+
+ if (adjustedCount > 0)
+ {
+ BulkConvertByteToNormalizedFloat(
+ source.Slice(0, adjustedCount),
+ dest.Slice(0, adjustedCount));
+
+ source = source.Slice(adjustedCount);
+ dest = dest.Slice(adjustedCount);
+ }
+ }
+
+ ///
+ /// as many elements as possible, slicing them down (keeping the remainder).
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
+ ref ReadOnlySpan source,
+ ref Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+ if (!IsAvailable)
+ {
+ return;
+ }
+
+ int remainder = ImageMaths.Modulo8(source.Length);
+ int adjustedCount = source.Length - remainder;
+
+ if (adjustedCount > 0)
+ {
+ BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount));
+
+ source = source.Slice(adjustedCount);
+ dest = dest.Slice(adjustedCount);
+ }
+ }
+
+ ///
+ /// SIMD optimized implementation for .
+ /// Works only with span Length divisible by 8.
+ /// Implementation adapted from:
+ /// http://lolengine.net/blog/2011/3/20/understanding-fast-float-integer-conversions
+ /// http://stackoverflow.com/a/536278
+ ///
+ internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest)
+ {
+ VerifyIsAvx2Compatible(nameof(BulkConvertByteToNormalizedFloat));
+ VerifySpanInput(source, dest, 8);
+
+ var bVec = new Vector(256.0f / 255.0f);
+ var magicFloat = new Vector(32768.0f);
+ var magicInt = new Vector(1191182336); // reinterpreded value of 32768.0f
+ var mask = new Vector(255);
+
+ ref Octet.OfByte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source));
+ ref Octet.OfUInt32 destBaseAsWideOctet = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
+
+ ref Vector destBaseAsFloat = ref Unsafe.As>(ref destBaseAsWideOctet);
+
+ int n = dest.Length / 8;
+
+ for (int i = 0; i < n; i++)
+ {
+ ref Octet.OfByte s = ref Unsafe.Add(ref sourceBase, i);
+ ref Octet.OfUInt32 d = ref Unsafe.Add(ref destBaseAsWideOctet, i);
+ d.LoadFrom(ref s);
+ }
+
+ for (int i = 0; i < n; i++)
+ {
+ ref Vector df = ref Unsafe.Add(ref destBaseAsFloat, i);
+
+ var vi = Vector.AsVectorUInt32(df);
+ vi &= mask;
+ vi |= magicInt;
+
+ var vf = Vector.AsVectorSingle(vi);
+ vf = (vf - magicFloat) * bVec;
+
+ df = vf;
+ }
+ }
+
+ ///
+ /// Implementation of which is faster on older runtimes.
+ ///
+ internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest)
+ {
+ VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByteClampOverflows));
+ VerifySpanInput(source, dest, 8);
+
+ if (source.Length == 0)
+ {
+ return;
+ }
+
+ ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
+ ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
+ int n = source.Length / 8;
+
+ Vector magick = new Vector(32768.0f);
+ Vector scale = new Vector(255f) / new Vector(256f);
+
+ // need to copy to a temporary struct, because
+ // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x)
+ // does not work. TODO: This might be a CoreClr bug, need to ask/report
+ var temp = default(Octet.OfUInt32);
+ ref Vector tempRef = ref Unsafe.As>(ref temp);
+
+ for (int i = 0; i < n; i++)
+ {
+ // union { float f; uint32_t i; } u;
+ // u.f = 32768.0f + x * (255.0f / 256.0f);
+ // return (uint8_t)u.i;
+ Vector x = Unsafe.Add(ref srcBase, i);
+ x = Vector.Max(x, Vector.Zero);
+ x = Vector.Min(x, Vector.One);
+
+ x = (x * scale) + magick;
+ tempRef = x;
+
+ ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i);
+ d.LoadFrom(ref temp);
+ }
+ }
+
+ ///
+ /// Convert all values normalized into [0..1] from 'source'
+ /// into 'dest' buffer of . The values are scaled up into [0-255] and rounded.
+ /// This implementation is SIMD optimized and works only when span Length is divisible by 8.
+ /// Based on:
+ ///
+ /// http://lolengine.net/blog/2011/3/20/understanding-fast-float-integer-conversions
+ ///
+ ///
+ internal static void BulkConvertNormalizedFloatToByte(ReadOnlySpan source, Span dest)
+ {
+ VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByte));
+ VerifySpanInput(source, dest, 8);
+
+ if (source.Length == 0)
+ {
+ return;
+ }
+
+ ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
+ ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
+ int n = source.Length / 8;
+
+ Vector magick = new Vector(32768.0f);
+ Vector scale = new Vector(255f) / new Vector(256f);
+
+ // need to copy to a temporary struct, because
+ // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x)
+ // does not work. TODO: This might be a CoreClr bug, need to ask/report
+ var temp = default(Octet.OfUInt32);
+ ref Vector tempRef = ref Unsafe.As>(ref temp);
+
+ for (int i = 0; i < n; i++)
+ {
+ // union { float f; uint32_t i; } u;
+ // u.f = 32768.0f + x * (255.0f / 256.0f);
+ // return (uint8_t)u.i;
+ Vector x = Unsafe.Add(ref srcBase, i);
+ x = (x * scale) + magick;
+ tempRef = x;
+
+ ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i);
+ d.LoadFrom(ref temp);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
new file mode 100644
index 0000000000..e0d6187dca
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// ReSharper disable MemberHidesStaticFromOuterClass
+namespace SixLabors.ImageSharp
+{
+ internal static partial class SimdUtils
+ {
+ ///
+ /// Implementation methods based on newer API-s (Vector.Widen, Vector.Narrow, Vector.ConvertTo*).
+ /// Only accelerated only on RyuJIT having dotnet/coreclr#10662 merged (.NET Core 2.1+ .NET 4.7.2+)
+ /// See:
+ /// https://github.com/dotnet/coreclr/pull/10662
+ /// API Proposal:
+ /// https://github.com/dotnet/corefx/issues/15957
+ ///
+ public static class ExtendedIntrinsics
+ {
+ public static bool IsAvailable { get; } =
+#if NETCOREAPP2_1
+ // TODO: Also available in .NET 4.7.2, we need to add a build target!
+ Vector.IsHardwareAccelerated;
+#else
+ false;
+#endif
+
+ ///
+ /// as many elements as possible, slicing them down (keeping the remainder).
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void BulkConvertByteToNormalizedFloatReduce(
+ ref ReadOnlySpan source,
+ ref Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+ if (!IsAvailable)
+ {
+ return;
+ }
+
+ int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count);
+ int adjustedCount = source.Length - remainder;
+
+ if (adjustedCount > 0)
+ {
+ BulkConvertByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount));
+
+ source = source.Slice(adjustedCount);
+ dest = dest.Slice(adjustedCount);
+ }
+ }
+
+ ///
+ /// as many elements as possible, slicing them down (keeping the remainder).
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
+ ref ReadOnlySpan source,
+ ref Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+ if (!IsAvailable)
+ {
+ return;
+ }
+
+ int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count);
+ int adjustedCount = source.Length - remainder;
+
+ if (adjustedCount > 0)
+ {
+ BulkConvertNormalizedFloatToByteClampOverflows(
+ source.Slice(0, adjustedCount),
+ dest.Slice(0, adjustedCount));
+
+ source = source.Slice(adjustedCount);
+ dest = dest.Slice(adjustedCount);
+ }
+ }
+
+ ///
+ /// Implementation , which is faster on new RyuJIT runtime.
+ ///
+ internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest)
+ {
+ VerifySpanInput(source, dest, Vector.Count);
+
+ int n = dest.Length / Vector.Count;
+
+ ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
+ ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
+
+ for (int i = 0; i < n; i++)
+ {
+ Vector b = Unsafe.Add(ref sourceBase, i);
+
+ Vector.Widen(b, out Vector s0, out Vector s1);
+ Vector.Widen(s0, out Vector w0, out Vector w1);
+ Vector.Widen(s1, out Vector w2, out Vector w3);
+
+ Vector f0 = ConvertToSingle(w0);
+ Vector f1 = ConvertToSingle(w1);
+ Vector f2 = ConvertToSingle(w2);
+ Vector f3 = ConvertToSingle(w3);
+
+ ref Vector d = ref Unsafe.Add(ref destBase, i * 4);
+ d = f0;
+ Unsafe.Add(ref d, 1) = f1;
+ Unsafe.Add(ref d, 2) = f2;
+ Unsafe.Add(ref d, 3) = f3;
+ }
+ }
+
+ ///
+ /// Implementation of , which is faster on new .NET runtime.
+ ///
+ internal static void BulkConvertNormalizedFloatToByteClampOverflows(
+ ReadOnlySpan source,
+ Span dest)
+ {
+ VerifySpanInput(source, dest, Vector.Count);
+
+ int n = dest.Length / Vector.Count;
+
+ ref Vector sourceBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
+ ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
+
+ for (int i = 0; i < n; i++)
+ {
+ ref Vector s = ref Unsafe.Add(ref sourceBase, i * 4);
+
+ Vector f0 = s;
+ Vector f1 = Unsafe.Add(ref s, 1);
+ Vector f2 = Unsafe.Add(ref s, 2);
+ Vector f3 = Unsafe.Add(ref s, 3);
+
+ Vector w0 = ConvertToUInt32(f0);
+ Vector w1 = ConvertToUInt32(f1);
+ Vector w2 = ConvertToUInt32(f2);
+ Vector w3 = ConvertToUInt32(f3);
+
+ Vector u0 = Vector.Narrow(w0, w1);
+ Vector u1 = Vector.Narrow(w2, w3);
+
+ Vector b = Vector.Narrow(u0, u1);
+
+ Unsafe.Add(ref destBase, i) = b;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector ConvertToUInt32(Vector vf)
+ {
+ Vector maxBytes = new Vector(255f);
+ vf *= maxBytes;
+ vf += new Vector(0.5f);
+ vf = Vector.Min(Vector.Max(vf, Vector.Zero), maxBytes);
+ Vector vi = Vector.ConvertToInt32(vf);
+ return Vector.AsVectorUInt32(vi);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector ConvertToSingle(Vector u)
+ {
+ Vector vi = Vector.AsVectorInt32(u);
+ Vector v = Vector.ConvertToSingle(vi);
+ v *= new Vector(1f / 255f);
+ return v;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs
new file mode 100644
index 0000000000..565ea08f5d
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs
@@ -0,0 +1,151 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// ReSharper disable MemberHidesStaticFromOuterClass
+namespace SixLabors.ImageSharp
+{
+ internal static partial class SimdUtils
+ {
+ ///
+ /// Fallback implementation based on (128bit).
+ /// For , efficient software fallback implementations are present,
+ /// and we hope that even mono's JIT is able to emit SIMD instructions for that type :P
+ ///
+ public static class FallbackIntrinsics128
+ {
+ ///
+ /// as many elements as possible, slicing them down (keeping the remainder).
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void BulkConvertByteToNormalizedFloatReduce(
+ ref ReadOnlySpan source,
+ ref Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+ int remainder = ImageMaths.Modulo4(source.Length);
+ int adjustedCount = source.Length - remainder;
+
+ if (adjustedCount > 0)
+ {
+ BulkConvertByteToNormalizedFloat(
+ source.Slice(0, adjustedCount),
+ dest.Slice(0, adjustedCount));
+
+ source = source.Slice(adjustedCount);
+ dest = dest.Slice(adjustedCount);
+ }
+ }
+
+ ///
+ /// as many elements as possible, slicing them down (keeping the remainder).
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
+ ref ReadOnlySpan source,
+ ref Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+ int remainder = ImageMaths.Modulo4(source.Length);
+ int adjustedCount = source.Length - remainder;
+
+ if (adjustedCount > 0)
+ {
+ BulkConvertNormalizedFloatToByteClampOverflows(
+ source.Slice(0, adjustedCount),
+ dest.Slice(0, adjustedCount));
+
+ source = source.Slice(adjustedCount);
+ dest = dest.Slice(adjustedCount);
+ }
+ }
+
+ ///
+ /// Implementation of using .
+ ///
+ [MethodImpl(InliningOptions.ColdPath)]
+ internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest)
+ {
+ VerifySpanInput(source, dest, 4);
+
+ int count = dest.Length / 4;
+ if (count == 0)
+ {
+ return;
+ }
+
+ ref ByteVector4 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source));
+ ref Vector4 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
+
+ const float Scale = 1f / 255f;
+ Vector4 d = default;
+
+ for (int i = 0; i < count; i++)
+ {
+ ref ByteVector4 s = ref Unsafe.Add(ref sBase, i);
+ d.X = s.X;
+ d.Y = s.Y;
+ d.Z = s.Z;
+ d.W = s.W;
+ d *= Scale;
+ Unsafe.Add(ref dBase, i) = d;
+ }
+ }
+
+ ///
+ /// Implementation of using .
+ ///
+ [MethodImpl(InliningOptions.ColdPath)]
+ internal static void BulkConvertNormalizedFloatToByteClampOverflows(
+ ReadOnlySpan source,
+ Span dest)
+ {
+ VerifySpanInput(source, dest, 4);
+
+ int count = source.Length / 4;
+ if (count == 0)
+ {
+ return;
+ }
+
+ ref Vector4 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source));
+ ref ByteVector4 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
+
+ var half = new Vector4(0.5f);
+ var maxBytes = new Vector4(255f);
+
+ for (int i = 0; i < count; i++)
+ {
+ Vector4 s = Unsafe.Add(ref sBase, i);
+ s *= maxBytes;
+ s += half;
+
+ // I'm not sure if Vector4.Clamp() is properly implemented with intrinsics.
+ s = Vector4.Max(Vector4.Zero, s);
+ s = Vector4.Min(maxBytes, s);
+
+ ref ByteVector4 d = ref Unsafe.Add(ref dBase, i);
+ d.X = (byte)s.X;
+ d.Y = (byte)s.Y;
+ d.Z = (byte)s.Z;
+ d.W = (byte)s.W;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct ByteVector4
+ {
+ public byte X;
+ public byte Y;
+ public byte Z;
+ public byte W;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs
new file mode 100644
index 0000000000..737e620061
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs
@@ -0,0 +1,185 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tuples;
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// Various extension and utility methods for and utilizing SIMD capabilities
+ ///
+ internal static partial class SimdUtils
+ {
+ ///
+ /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte.
+ ///
+ public static bool IsAvx2CompatibleArchitecture { get; } =
+ Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8;
+
+ ///
+ /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics.
+ ///
+ /// The vector
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static Vector4 PseudoRound(this Vector4 v)
+ {
+ var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1));
+
+ return v + (sign * 0.5f);
+ }
+
+ ///
+ /// Rounds all values in 'v' to the nearest integer following semantics.
+ /// Source:
+ ///
+ /// https://github.com/g-truc/glm/blob/master/glm/simd/common.h#L110
+ ///
+ ///
+ /// The vector
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static Vector FastRound(this Vector v)
+ {
+ Vector magic0 = new Vector(int.MinValue); // 0x80000000
+ Vector sgn0 = Vector.AsVectorSingle(magic0);
+ Vector and0 = Vector.BitwiseAnd(sgn0, v);
+ Vector or0 = Vector.BitwiseOr(and0, new Vector(8388608.0f));
+ Vector add0 = Vector.Add(v, or0);
+ Vector sub0 = Vector.Subtract(add0, or0);
+ return sub0;
+ }
+
+ ///
+ /// Converts all input -s to -s normalized into [0..1].
+ /// should be the of the same size as ,
+ /// but there are no restrictions on the span's length.
+ ///
+ /// The source span of bytes
+ /// The destination span of floats
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+#if NETCOREAPP2_1
+ ExtendedIntrinsics.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest);
+#else
+ BasicIntrinsics256.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest);
+#endif
+ FallbackIntrinsics128.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest);
+
+ // Deal with the remainder:
+ if (source.Length > 0)
+ {
+ ConverByteToNormalizedFloatRemainder(source, dest);
+ }
+ }
+
+ ///
+ /// Convert all values normalized into [0..1] from 'source' into 'dest' buffer of .
+ /// The values are scaled up into [0-255] and rounded, overflows are clamped.
+ /// should be the of the same size as ,
+ /// but there are no restrictions on the span's length.
+ ///
+ /// The source span of floats
+ /// The destination span of bytes
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+#if NETCOREAPP2_1
+ ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest);
+#else
+ BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest);
+#endif
+ FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest);
+
+ // Deal with the remainder:
+ if (source.Length > 0)
+ {
+ ConvertNormalizedFloatToByteRemainder(source, dest);
+ }
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private static void ConverByteToNormalizedFloatRemainder(ReadOnlySpan source, Span dest)
+ {
+ ref byte sBase = ref MemoryMarshal.GetReference(source);
+ ref float dBase = ref MemoryMarshal.GetReference(dest);
+
+ // There are at most 3 elements at this point, having a for loop is overkill.
+ // Let's minimize the no. of instructions!
+ switch (source.Length)
+ {
+ case 3:
+ Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2) / 255f;
+ goto case 2;
+ case 2:
+ Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1) / 255f;
+ goto case 1;
+ case 1:
+ dBase = sBase / 255f;
+ break;
+ }
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan source, Span dest)
+ {
+ ref float sBase = ref MemoryMarshal.GetReference(source);
+ ref byte dBase = ref MemoryMarshal.GetReference(dest);
+
+ switch (source.Length)
+ {
+ case 3:
+ Unsafe.Add(ref dBase, 2) = ConvertToByte(Unsafe.Add(ref sBase, 2));
+ goto case 2;
+ case 2:
+ Unsafe.Add(ref dBase, 1) = ConvertToByte(Unsafe.Add(ref sBase, 1));
+ goto case 1;
+ case 1:
+ dBase = ConvertToByte(sBase);
+ break;
+ }
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static byte ConvertToByte(float f) => (byte)ComparableExtensions.Clamp((f * 255f) + 0.5f, 0, 255f);
+
+ [Conditional("DEBUG")]
+ private static void VerifyIsAvx2Compatible(string operation)
+ {
+ if (!IsAvx2CompatibleArchitecture)
+ {
+ throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!");
+ }
+ }
+
+ [Conditional("DEBUG")]
+ private static void VerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+ DebugGuard.IsTrue(
+ ImageMaths.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0,
+ nameof(source),
+ $"length should be divisable by {shouldBeDivisibleBy}!");
+ }
+
+ [Conditional("DEBUG")]
+ private static void VerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+ DebugGuard.IsTrue(
+ ImageMaths.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0,
+ nameof(source),
+ $"length should be divisable by {shouldBeDivisibleBy}!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Tuples/Octet.cs b/src/ImageSharp/Common/Tuples/Octet.cs
new file mode 100644
index 0000000000..539b74e324
--- /dev/null
+++ b/src/ImageSharp/Common/Tuples/Octet.cs
@@ -0,0 +1,109 @@
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Tuples
+{
+ ///
+ /// Contains 8 element value tuples of various types.
+ ///
+ internal static class Octet
+ {
+ ///
+ /// Value tuple of -s
+ ///
+ [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))]
+ public struct OfUInt32
+ {
+ [FieldOffset(0 * sizeof(uint))]
+ public uint V0;
+
+ [FieldOffset(1 * sizeof(uint))]
+ public uint V1;
+
+ [FieldOffset(2 * sizeof(uint))]
+ public uint V2;
+
+ [FieldOffset(3 * sizeof(uint))]
+ public uint V3;
+
+ [FieldOffset(4 * sizeof(uint))]
+ public uint V4;
+
+ [FieldOffset(5 * sizeof(uint))]
+ public uint V5;
+
+ [FieldOffset(6 * sizeof(uint))]
+ public uint V6;
+
+ [FieldOffset(7 * sizeof(uint))]
+ public uint V7;
+
+ public override string ToString()
+ {
+ return $"{nameof(Octet)}.{nameof(OfUInt32)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})";
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void LoadFrom(ref OfByte src)
+ {
+ this.V0 = src.V0;
+ this.V1 = src.V1;
+ this.V2 = src.V2;
+ this.V3 = src.V3;
+ this.V4 = src.V4;
+ this.V5 = src.V5;
+ this.V6 = src.V6;
+ this.V7 = src.V7;
+ }
+ }
+
+ ///
+ /// Value tuple of -s
+ ///
+ [StructLayout(LayoutKind.Explicit, Size = 8)]
+ public struct OfByte
+ {
+ [FieldOffset(0)]
+ public byte V0;
+
+ [FieldOffset(1)]
+ public byte V1;
+
+ [FieldOffset(2)]
+ public byte V2;
+
+ [FieldOffset(3)]
+ public byte V3;
+
+ [FieldOffset(4)]
+ public byte V4;
+
+ [FieldOffset(5)]
+ public byte V5;
+
+ [FieldOffset(6)]
+ public byte V6;
+
+ [FieldOffset(7)]
+ public byte V7;
+
+ public override string ToString()
+ {
+ return $"{nameof(Octet)}.{nameof(OfByte)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})";
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void LoadFrom(ref OfUInt32 src)
+ {
+ this.V0 = (byte)src.V0;
+ this.V1 = (byte)src.V1;
+ this.V2 = (byte)src.V2;
+ this.V3 = (byte)src.V3;
+ this.V4 = (byte)src.V4;
+ this.V5 = (byte)src.V5;
+ this.V6 = (byte)src.V6;
+ this.V7 = (byte)src.V7;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs
index 309d5e2e56..cae283d628 100644
--- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs
+++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs
@@ -2,11 +2,12 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace SixLabors.ImageSharp.Common.Tuples
+namespace SixLabors.ImageSharp.Tuples
{
///
/// Its faster to process multiple Vector4-s together, so let's pair them!
/// On AVX2 this pair should be convertible to of !
+ /// TODO: Investigate defining this as union with an Octet.OfSingle type.
///
[StructLayout(LayoutKind.Sequential)]
internal struct Vector4Pair
@@ -15,8 +16,6 @@ internal struct Vector4Pair
public Vector4 B;
- private static readonly Vector4 Scale = new Vector4(1 / 255f);
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MultiplyInplace(float value)
{
@@ -52,8 +51,9 @@ internal void RoundAndDownscalePreAvx2()
b = b.FastRound();
// Downscale by 1/255
- this.A *= Scale;
- this.B *= Scale;
+ var scale = new Vector4(1 / 255f);
+ this.A *= scale;
+ this.B *= scale;
}
///
@@ -74,7 +74,7 @@ internal void RoundAndDownscaleAvx2()
public override string ToString()
{
- return $"{this.A}, {this.B}";
+ return $"{nameof(Vector4Pair)}({this.A}, {this.B})";
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
index 4b2626c582..1dc72aaf5b 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
@@ -6,7 +6,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Common.Tuples;
+using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
@@ -109,7 +109,7 @@ internal static void ConvertCore(in ComponentValues values, Span result
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
- destination.Collect(ref r, ref g, ref b);
+ destination.Pack(ref r, ref g, ref b);
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
index ab4947e65c..46644258b1 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
@@ -6,7 +6,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Common.Tuples;
+using SixLabors.ImageSharp.Tuples;
// ReSharper disable ImpureMethodCallOnReadonlyValueField
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
@@ -102,7 +102,7 @@ internal static void ConvertCore(in ComponentValues values, Span result
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
- destination.Collect(ref rr, ref gg, ref bb);
+ destination.Pack(ref rr, ref gg, ref bb);
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
index 60abb7fb2c..456636dc39 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
@@ -6,8 +6,8 @@
using System.Linq;
using System.Numerics;
-using SixLabors.ImageSharp.Common.Tuples;
using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Tuples;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
@@ -157,9 +157,9 @@ internal struct Vector4Octet
public Vector4 V0, V1, V2, V3, V4, V5, V6, V7;
///
- /// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order.
+ /// Pack (r0,r1...r7) (g0,g1...g7) (b0,b1...b7) vector values as (r0,g0,b0,1), (r1,g1,b1,1) ...
///
- public void Collect(ref Vector4Pair r, ref Vector4Pair g, ref Vector4Pair b)
+ public void Pack(ref Vector4Pair r, ref Vector4Pair g, ref Vector4Pair b)
{
this.V0.X = r.A.X;
this.V0.Y = g.A.X;
diff --git a/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
index 2629ce3f79..bb42ec7e34 100644
--- a/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
+++ b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
@@ -3,7 +3,6 @@
using System;
using System.Numerics;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.Memory;
@@ -19,100 +18,18 @@ public partial struct Rgba32
///
internal partial class PixelOperations : PixelOperations
{
- ///
- /// 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
- ///
- /// TODO: We can replace this implementation in the future using new Vector API-s:
- ///
- /// https://github.com/dotnet/corefx/issues/15957
- ///
- ///
- internal static void ToVector4SimdAligned(ReadOnlySpan sourceColors, Span destVectors, int count)
- {
- if (!Vector.IsHardwareAccelerated)
- {
- throw new InvalidOperationException(
- "Rgba32.PixelOperations.ToVector4SimdAligned() should not be called when Vector.IsHardwareAccelerated == false!");
- }
-
- DebugGuard.IsTrue(
- count % Vector.Count == 0,
- nameof(count),
- "Argument 'count' should divisible by Vector.Count!");
-
- var bVec = new Vector(256.0f / 255.0f);
- var magicFloat = new Vector(32768.0f);
- var magicInt = new Vector(1191182336); // reinterpreded value of 32768.0f
- var mask = new Vector(255);
-
- int unpackedRawCount = count * 4;
-
- ref uint sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(sourceColors));
- ref UnpackedRGBA destBaseAsUnpacked = ref Unsafe.As(ref MemoryMarshal.GetReference(destVectors));
- ref Vector destBaseAsUInt = ref Unsafe.As>(ref destBaseAsUnpacked);
- ref Vector destBaseAsFloat = ref Unsafe.As>(ref destBaseAsUnpacked);
-
- for (int i = 0; i < count; i++)
- {
- uint sVal = Unsafe.Add(ref sourceBase, i);
- ref UnpackedRGBA dst = ref Unsafe.Add(ref destBaseAsUnpacked, i);
-
- // This call is the bottleneck now:
- dst.Load(sVal);
- }
-
- int numOfVectors = unpackedRawCount / Vector.Count;
-
- for (int i = 0; i < numOfVectors; i++)
- {
- Vector vi = Unsafe.Add(ref destBaseAsUInt, i);
-
- vi &= mask;
- vi |= magicInt;
-
- var vf = Vector.AsVectorSingle(vi);
- vf = (vf - magicFloat) * bVec;
-
- Unsafe.Add(ref destBaseAsFloat, i) = vf;
- }
- }
-
///
internal override void ToVector4(ReadOnlySpan sourceColors, Span destinationVectors, int count)
{
Guard.MustBeSizedAtLeast(sourceColors, count, nameof(sourceColors));
Guard.MustBeSizedAtLeast(destinationVectors, count, nameof(destinationVectors));
- if (count < 256 || !Vector.IsHardwareAccelerated)
- {
- // Doesn't worth to bother with SIMD:
- base.ToVector4(sourceColors, destinationVectors, count);
- return;
- }
-
- int remainder = count % Vector.Count;
- int alignedCount = count - remainder;
+ sourceColors = sourceColors.Slice(0, count);
+ destinationVectors = destinationVectors.Slice(0, count);
- if (alignedCount > 0)
- {
- ToVector4SimdAligned(sourceColors, destinationVectors, alignedCount);
- }
-
- if (remainder > 0)
- {
- sourceColors = sourceColors.Slice(alignedCount);
- destinationVectors = destinationVectors.Slice(alignedCount);
- base.ToVector4(sourceColors, destinationVectors, remainder);
- }
+ SimdUtils.BulkConvertByteToNormalizedFloat(
+ MemoryMarshal.Cast(sourceColors),
+ MemoryMarshal.Cast(destinationVectors));
}
///
@@ -120,29 +37,12 @@ internal override void PackFromVector4(ReadOnlySpan sourceVectors, Span
{
GuardSpans(sourceVectors, nameof(sourceVectors), destinationColors, nameof(destinationColors), count);
- if (!SimdUtils.IsAvx2CompatibleArchitecture)
- {
- base.PackFromVector4(sourceVectors, destinationColors, count);
- return;
- }
-
- int remainder = count % 2;
- int alignedCount = count - remainder;
+ sourceVectors = sourceVectors.Slice(0, count);
+ destinationColors = destinationColors.Slice(0, count);
- if (alignedCount > 0)
- {
- ReadOnlySpan flatSrc = MemoryMarshal.Cast(sourceVectors.Slice(0, alignedCount));
- Span flatDest = MemoryMarshal.Cast(destinationColors);
-
- SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(flatSrc, flatDest);
- }
-
- if (remainder > 0)
- {
- // actually: remainder == 1
- int lastIdx = count - 1;
- destinationColors[lastIdx].PackFromVector4(sourceVectors[lastIdx]);
- }
+ SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(
+ MemoryMarshal.Cast(sourceVectors),
+ MemoryMarshal.Cast(destinationColors));
}
///
@@ -172,30 +72,6 @@ internal override void ToRgba32(ReadOnlySpan sourcePixels, Span
sourcePixels.Slice(0, count).CopyTo(dest);
}
-
- ///
- /// Value type to store -s unpacked into multiple -s.
- ///
- [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 >> GreenShift;
- this.b = p >> BlueShift;
- this.a = p >> AlphaShift;
- }
- }
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs
index a5fa59ba07..eaa52a9750 100644
--- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs
+++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs
@@ -3,6 +3,7 @@
// ReSharper disable InconsistentNaming
+using System;
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -19,11 +20,14 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
public abstract class PackFromVector4
where TPixel : struct, IPixel
{
- private IMemoryOwner source;
+ protected IMemoryOwner source;
- private IMemoryOwner destination;
+ protected IMemoryOwner destination;
- [Params(16, 128, 512)]
+ [Params(
+ 64,
+ 2048
+ )]
public int Count { get; set; }
[GlobalSetup]
@@ -40,7 +44,7 @@ public void Cleanup()
this.source.Dispose();
}
- [Benchmark(Baseline = true)]
+ //[Benchmark]
public void PerElement()
{
ref Vector4 s = ref MemoryMarshal.GetReference(this.source.GetSpan());
@@ -53,13 +57,13 @@ public void PerElement()
}
[Benchmark]
- public void CommonBulk()
+ public void PixelOperations_Base()
{
new PixelOperations().PackFromVector4(this.source.GetSpan(), this.destination.GetSpan(), this.Count);
}
[Benchmark]
- public void OptimizedBulk()
+ public void PixelOperations_Specialized()
{
PixelOperations.Instance.PackFromVector4(this.source.GetSpan(), this.destination.GetSpan(), this.Count);
}
@@ -67,6 +71,58 @@ public void OptimizedBulk()
public class PackFromVector4_Rgba32 : PackFromVector4
{
+ [Benchmark]
+ public void FallbackIntrinsics128()
+ {
+ Span sBytes = MemoryMarshal.Cast(this.source.GetSpan());
+ Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan());
+
+ SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats);
+ }
+
+ [Benchmark(Baseline = true)]
+ public void BasicIntrinsics256()
+ {
+ Span sBytes = MemoryMarshal.Cast(this.source.GetSpan());
+ Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan());
+
+ SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats);
+ }
+
+ [Benchmark]
+ public void ExtendedIntrinsic()
+ {
+ Span sBytes = MemoryMarshal.Cast(this.source.GetSpan());
+ Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan());
+
+ SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats);
+ }
+ // RESULTS (2018 October):
+ // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
+ // ---------------------------- |-------- |------ |-------------:|-------------:|------------:|-------:|---------:|-------:|----------:|
+ // FallbackIntrinsics128 | Clr | 64 | 340.38 ns | 22.319 ns | 1.2611 ns | 1.41 | 0.01 | - | 0 B |
+ // BasicIntrinsics256 | Clr | 64 | 240.79 ns | 11.421 ns | 0.6453 ns | 1.00 | 0.00 | - | 0 B |
+ // ExtendedIntrinsic | Clr | 64 | 199.09 ns | 124.239 ns | 7.0198 ns | 0.83 | 0.02 | - | 0 B |
+ // PixelOperations_Base | Clr | 64 | 647.99 ns | 24.003 ns | 1.3562 ns | 2.69 | 0.01 | 0.0067 | 24 B |
+ // PixelOperations_Specialized | Clr | 64 | 259.79 ns | 13.391 ns | 0.7566 ns | 1.08 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized!
+ // | | | | | | | | | |
+ // FallbackIntrinsics128 | Core | 64 | 234.64 ns | 12.320 ns | 0.6961 ns | 1.58 | 0.00 | - | 0 B |
+ // BasicIntrinsics256 | Core | 64 | 148.87 ns | 2.794 ns | 0.1579 ns | 1.00 | 0.00 | - | 0 B |
+ // ExtendedIntrinsic | Core | 64 | 94.06 ns | 10.015 ns | 0.5659 ns | 0.63 | 0.00 | - | 0 B |
+ // PixelOperations_Base | Core | 64 | 573.52 ns | 31.865 ns | 1.8004 ns | 3.85 | 0.01 | 0.0067 | 24 B |
+ // PixelOperations_Specialized | Core | 64 | 117.21 ns | 13.264 ns | 0.7494 ns | 0.79 | 0.00 | - | 0 B |
+ // | | | | | | | | | |
+ // FallbackIntrinsics128 | Clr | 2048 | 6,735.93 ns | 2,139.340 ns | 120.8767 ns | 1.71 | 0.03 | - | 0 B |
+ // BasicIntrinsics256 | Clr | 2048 | 3,929.29 ns | 334.027 ns | 18.8731 ns | 1.00 | 0.00 | - | 0 B |
+ // ExtendedIntrinsic | Clr | 2048 | 2,226.01 ns | 130.525 ns | 7.3749 ns |!! 0.57 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock!
+ // PixelOperations_Base | Clr | 2048 | 16,760.84 ns | 367.800 ns | 20.7814 ns | 4.27 | 0.02 | - | 24 B | <--- Extra copies using "Vector4 TPixel.ToVector4()"
+ // PixelOperations_Specialized | Clr | 2048 | 3,986.03 ns | 237.238 ns | 13.4044 ns | 1.01 | 0.00 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :(
+ // | | | | | | | | | |
+ // FallbackIntrinsics128 | Core | 2048 | 6,644.65 ns | 2,677.090 ns | 151.2605 ns | 1.69 | 0.05 | - | 0 B |
+ // BasicIntrinsics256 | Core | 2048 | 3,923.70 ns | 1,971.760 ns | 111.4081 ns | 1.00 | 0.00 | - | 0 B |
+ // ExtendedIntrinsic | Core | 2048 | 2,092.32 ns | 375.657 ns | 21.2253 ns |!! 0.53 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock!
+ // PixelOperations_Base | Core | 2048 | 16,875.73 ns | 1,271.957 ns | 71.8679 ns | 4.30 | 0.10 | - | 24 B |
+ // PixelOperations_Specialized | Core | 2048 | 2,129.92 ns | 262.888 ns | 14.8537 ns |!! 0.54 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock!
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
index 50fac25139..2cbe549e4a 100644
--- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
+++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
@@ -6,8 +6,14 @@
using System.Buffers;
using System;
using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Attributes.Jobs;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Environments;
+using BenchmarkDotNet.Jobs;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -17,11 +23,17 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
public abstract class ToVector4
where TPixel : struct, IPixel
{
- private IMemoryOwner source;
+ protected IMemoryOwner source;
- private IMemoryOwner destination;
+ protected IMemoryOwner destination;
- [Params(64, 300, 1024)]
+ [Params(
+ 64,
+ //256,
+ //512,
+ //1024,
+ 2048
+ )]
public int Count { get; set; }
[GlobalSetup]
@@ -38,7 +50,7 @@ public void Cleanup()
this.destination.Dispose();
}
- [Benchmark(Baseline = true)]
+ //[Benchmark]
public void PerElement()
{
Span s = this.source.GetSpan();
@@ -46,25 +58,163 @@ public void PerElement()
for (int i = 0; i < this.Count; i++)
{
- TPixel c = s[i];
- d[i] = c.ToVector4();
+ d[i] = s[i].ToVector4();
}
}
[Benchmark]
- public void CommonBulk()
+ public void PixelOperations_Base()
{
new PixelOperations().ToVector4(this.source.GetSpan(), this.destination.GetSpan(), this.Count);
}
[Benchmark]
- public void OptimizedBulk()
+ public void PixelOperations_Specialized()
{
PixelOperations.Instance.ToVector4(this.source.GetSpan(), this.destination.GetSpan(), this.Count);
}
}
+ [Config(typeof(Config.ShortClr))]
public class ToVector4_Rgba32 : ToVector4
{
+ [Benchmark]
+ public void FallbackIntrinsics128()
+ {
+ Span sBytes = MemoryMarshal.Cast(this.source.GetSpan());
+ Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan());
+
+ SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(sBytes, dFloats);
+ }
+
+ [Benchmark(Baseline = true)]
+ public void BasicIntrinsics256()
+ {
+ Span