Browse Source

Use .NET 10 tensor/SIMD primitives directly

Replaced ImageSharp’s custom TensorPrimitives_ and several Vector128/256/512 helper fallbacks with direct System.Numerics and System.Numerics.Tensors APIs across color conversion, codecs, filtering, blending, and convolution paths. Removed the obsolete compatibility helper files, updated package references (including System.Numerics.Tensors and newer System.IO.Hashing), and aligned tests with runtime behavior changes such as inverted float clamp bounds now throwing ArgumentException.
pull/3179/head
James Jackson-South 2 months ago
parent
commit
329bec2638
  1. 5
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs
  2. 13
      src/ImageSharp/Common/Helpers/Numerics.cs
  3. 6
      src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs
  4. 2
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs
  5. 20
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs
  6. 6
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs
  7. 84
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  8. 38
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  9. 93
      src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs
  10. 322
      src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs
  11. 87
      src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs
  12. 900
      src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs
  13. 249
      src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs
  14. 76
      src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs
  15. 332
      src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs
  16. 573
      src/ImageSharp/Common/Helpers/Vector128Utilities.cs
  17. 158
      src/ImageSharp/Common/Helpers/Vector256Utilities.cs
  18. 108
      src/ImageSharp/Common/Helpers/Vector512Utilities.cs
  19. 4
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs
  20. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs
  21. 6
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs
  22. 36
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs
  23. 54
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs
  24. 36
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs
  25. 3
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.Icc.cs
  26. 5
      src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs
  27. 8
      src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs
  28. 30
      src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs
  29. 3
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  30. 4
      src/ImageSharp/Formats/Png/PngCgbiProcessor.cs
  31. 3
      src/ImageSharp/Formats/Webp/AlphaDecoder.cs
  32. 4
      src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs
  33. 16
      src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs
  34. 11
      src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs
  35. 74
      src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
  36. 8
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs
  37. 16
      src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs
  38. 10
      src/ImageSharp/Formats/Webp/WebpCommonUtils.cs
  39. 3
      src/ImageSharp/ImageSharp.csproj
  40. 12
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs
  41. 26
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  42. 22
      src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs
  43. 4
      src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs
  44. 7
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs
  45. 3
      src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs
  46. 3
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
  47. 8
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  48. 57
      tests/ImageSharp.Tests/Common/NumericsTests.cs
  49. 12
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  50. 580
      tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs

5
src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs

@ -4,6 +4,7 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@ -663,7 +664,7 @@ internal static class ColorProfileConverterExtensionsIcc
// Vector4 values are contiguous floats, so flattening preserves the component order
// while allowing one shared tensor traversal to process every channel and SIMD tail.
Span<float> values = MemoryMarshal.Cast<Vector4, float>(source);
TensorPrimitives_.Max(values, 0F, values);
TensorPrimitives.Max(values, 0F, values);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -684,7 +685,7 @@ internal static class ColorProfileConverterExtensionsIcc
{
// Reinterpreting both spans exposes all four components to one multiplication traversal;
// the source and destination retain their original Vector4 boundaries after the operation.
TensorPrimitives_.Multiply(MemoryMarshal.Cast<Vector4, float>(source), scale, MemoryMarshal.Cast<Vector4, float>(destination));
TensorPrimitives.Multiply(MemoryMarshal.Cast<Vector4, float>(source), scale, MemoryMarshal.Cast<Vector4, float>(destination));
}
private class ConversionParams

13
src/ImageSharp/Common/Helpers/Numerics.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@ -329,7 +330,7 @@ internal static class Numerics
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<byte> span, byte min, byte max)
=> TensorPrimitives_.Clamp(span, min, max, span);
=> TensorPrimitives.Clamp(span, min, max, span);
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
@ -339,7 +340,7 @@ internal static class Numerics
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<uint> span, uint min, uint max)
=> TensorPrimitives_.Clamp(span, min, max, span);
=> TensorPrimitives.Clamp(span, min, max, span);
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
@ -349,7 +350,7 @@ internal static class Numerics
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<int> span, int min, int max)
=> TensorPrimitives_.Clamp(span, min, max, span);
=> TensorPrimitives.Clamp(span, min, max, span);
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
@ -359,7 +360,7 @@ internal static class Numerics
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<float> span, float min, float max)
=> TensorPrimitives_.Clamp(span, min, max, span);
=> TensorPrimitives.Clamp(span, min, max, span);
/// <summary>
/// Clamps the span values to the inclusive range of min and max.
@ -369,7 +370,7 @@ internal static class Numerics
/// <param name="max">The maximum inclusive value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp(Span<double> span, double min, double max)
=> TensorPrimitives_.Clamp(span, min, max, span);
=> TensorPrimitives.Clamp(span, min, max, span);
/// <summary>
/// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
@ -1040,5 +1041,5 @@ internal static class Numerics
/// <param name="sum">The sum of the values in <paramref name="span"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Normalize(Span<float> span, float sum)
=> TensorPrimitives_.Divide(span, sum, span);
=> TensorPrimitives.Divide(span, sum, span);
}

6
src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs

@ -47,7 +47,7 @@ internal readonly struct WXYZPad3Shuffle4 : IPad3Shuffle4
// Each four-byte group is an XYZW pixel with opaque W. Selecting [3, 0, 1, 2]
// produces WXYZ, and offsets 4, 8, and 12 repeat that rotation for the next pixels.
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14));
=> Vector128.ShuffleNative(source, Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14));
}
/// <summary>
@ -69,7 +69,7 @@ internal readonly struct WZYXPad3Shuffle4 : IPad3Shuffle4
// Each four-byte group is an XYZW pixel with opaque W. Selecting [3, 2, 1, 0]
// produces WZYX, and offsets 4, 8, and 12 repeat that reversal for the next pixels.
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12));
=> Vector128.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12));
}
/// <summary>
@ -91,5 +91,5 @@ internal readonly struct ZYXWPad3Shuffle4 : IPad3Shuffle4
// Each four-byte group is an XYZW pixel with opaque W. Selecting [2, 1, 0, 3]
// exchanges X and Z to produce ZYXW, with offsets 4, 8, and 12 covering the next pixels.
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15));
=> Vector128.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15));
}

2
src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs

@ -34,5 +34,5 @@ internal readonly struct ZYXShuffle3 : IShuffle3
// Each four-byte group is a temporary XYZW pixel created by the shuffle pipeline.
// Selecting [2, 1, 0, 3] produces ZYXW, and offsets 4, 8, and 12 repeat that
// permutation for the next pixels. The pipeline subsequently discards every W byte.
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15));
=> Vector128.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15));
}

20
src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs

@ -69,7 +69,7 @@ internal readonly struct WXYZShuffle4 : IShuffle4
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Invoke(Vector128<byte> source)
=> Vector128_.ShuffleNative(source, CreateLaneMask());
=> Vector128.ShuffleNative(source, CreateLaneMask());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -85,7 +85,7 @@ internal readonly struct WXYZShuffle4 : IShuffle4
public static Vector512<byte> Invoke(Vector512<byte> source)
// Expand the four-pixel lane permutation across all four 128-bit lanes.
=> Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
=> Vector512.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
/// <summary>
/// Creates the indices that rotate each XYZW pixel to WXYZ within one 128-bit lane.
@ -115,7 +115,7 @@ internal readonly struct WZYXShuffle4 : IShuffle4
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Invoke(Vector128<byte> source)
=> Vector128_.ShuffleNative(source, CreateLaneMask());
=> Vector128.ShuffleNative(source, CreateLaneMask());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -131,7 +131,7 @@ internal readonly struct WZYXShuffle4 : IShuffle4
public static Vector512<byte> Invoke(Vector512<byte> source)
// Expand the four-pixel lane permutation across all four 128-bit lanes.
=> Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
=> Vector512.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
/// <summary>
/// Creates the indices that reverse each XYZW pixel to WZYX within one 128-bit lane.
@ -161,7 +161,7 @@ internal readonly struct YZWXShuffle4 : IShuffle4
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Invoke(Vector128<byte> source)
=> Vector128_.ShuffleNative(source, CreateLaneMask());
=> Vector128.ShuffleNative(source, CreateLaneMask());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -177,7 +177,7 @@ internal readonly struct YZWXShuffle4 : IShuffle4
public static Vector512<byte> Invoke(Vector512<byte> source)
// Expand the four-pixel lane permutation across all four 128-bit lanes.
=> Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
=> Vector512.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
/// <summary>
/// Creates the indices that rotate each XYZW pixel to YZWX within one 128-bit lane.
@ -209,7 +209,7 @@ internal readonly struct ZYXWShuffle4 : IShuffle4
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Invoke(Vector128<byte> source)
=> Vector128_.ShuffleNative(source, CreateLaneMask());
=> Vector128.ShuffleNative(source, CreateLaneMask());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -225,7 +225,7 @@ internal readonly struct ZYXWShuffle4 : IShuffle4
public static Vector512<byte> Invoke(Vector512<byte> source)
// Expand the four-pixel lane permutation across all four 128-bit lanes.
=> Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
=> Vector512.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
/// <summary>
/// Creates the indices that exchange X and Z in each XYZW pixel within one 128-bit lane.
@ -257,7 +257,7 @@ internal readonly struct XWZYShuffle4 : IShuffle4
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Invoke(Vector128<byte> source)
=> Vector128_.ShuffleNative(source, CreateLaneMask());
=> Vector128.ShuffleNative(source, CreateLaneMask());
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -273,7 +273,7 @@ internal readonly struct XWZYShuffle4 : IShuffle4
public static Vector512<byte> Invoke(Vector512<byte> source)
// Expand the four-pixel lane permutation across all four 128-bit lanes.
=> Vector512_.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
=> Vector512.ShuffleNative(source, IShuffle4.ExpandLaneMask(CreateLaneMask()));
/// <summary>
/// Creates the indices that exchange Y and W in each XYZW pixel within one 128-bit lane.

6
src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs

@ -49,7 +49,7 @@ internal readonly struct YZWXShuffle4Slice3 : IShuffle4Slice3
// Each four-byte group is an XYZW pixel. Selecting [1, 2, 3, 0] produces
// YZWX, and offsets 4, 8, and 12 repeat that rotation for the next pixels.
// The surrounding pipeline subsequently removes every fourth byte.
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12));
=> Vector128.ShuffleNative(source, Vector128.Create((byte)1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12));
}
/// <summary>
@ -72,7 +72,7 @@ internal readonly struct WZYXShuffle4Slice3 : IShuffle4Slice3
// Each four-byte group is an XYZW pixel. Selecting [3, 2, 1, 0] produces
// WZYX, and offsets 4, 8, and 12 repeat that reversal for the next pixels.
// The surrounding pipeline subsequently removes every fourth byte.
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12));
=> Vector128.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12));
}
/// <summary>
@ -95,7 +95,7 @@ internal readonly struct ZYXWShuffle4Slice3 : IShuffle4Slice3
// Each four-byte group is an XYZW pixel. Selecting [2, 1, 0, 3] produces
// ZYXW, and offsets 4, 8, and 12 repeat that exchange for the next pixels.
// The surrounding pipeline subsequently removes every fourth byte.
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15));
=> Vector128.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15));
}
/// <summary>

84
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -359,17 +359,17 @@ internal static partial class SimdUtils
ref Vector512<byte> vs0 = ref Unsafe.Add(ref sourceBase, i);
ref Vector512<byte> vd0 = ref Unsafe.Add(ref destinationBase, i);
vd0 = Vector512_.ShuffleNative(vs0, mask);
Unsafe.Add(ref vd0, (nuint)1) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), mask);
Unsafe.Add(ref vd0, (nuint)2) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), mask);
Unsafe.Add(ref vd0, (nuint)3) = Vector512_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), mask);
vd0 = Vector512.ShuffleNative(vs0, mask);
Unsafe.Add(ref vd0, (nuint)1) = Vector512.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), mask);
Unsafe.Add(ref vd0, (nuint)2) = Vector512.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), mask);
Unsafe.Add(ref vd0, (nuint)3) = Vector512.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), mask);
}
if (m > 0)
{
for (nuint i = u; i < n; i++)
{
Unsafe.Add(ref destinationBase, i) = Vector512_.ShuffleNative(Unsafe.Add(ref sourceBase, i), mask);
Unsafe.Add(ref destinationBase, i) = Vector512.ShuffleNative(Unsafe.Add(ref sourceBase, i), mask);
}
}
}
@ -428,17 +428,17 @@ internal static partial class SimdUtils
ref Vector128<byte> vs0 = ref Unsafe.Add(ref sourceBase, i);
ref Vector128<byte> vd0 = ref Unsafe.Add(ref destinationBase, i);
vd0 = Vector128_.ShuffleNative(vs0, mask);
Unsafe.Add(ref vd0, (nuint)1) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), mask);
Unsafe.Add(ref vd0, (nuint)2) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), mask);
Unsafe.Add(ref vd0, (nuint)3) = Vector128_.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), mask);
vd0 = Vector128.ShuffleNative(vs0, mask);
Unsafe.Add(ref vd0, (nuint)1) = Vector128.ShuffleNative(Unsafe.Add(ref vs0, (nuint)1), mask);
Unsafe.Add(ref vd0, (nuint)2) = Vector128.ShuffleNative(Unsafe.Add(ref vs0, (nuint)2), mask);
Unsafe.Add(ref vd0, (nuint)3) = Vector128.ShuffleNative(Unsafe.Add(ref vs0, (nuint)3), mask);
}
if (m > 0)
{
for (nuint i = u; i < n; i++)
{
Unsafe.Add(ref destinationBase, i) = Vector128_.ShuffleNative(Unsafe.Add(ref sourceBase, i), mask);
Unsafe.Add(ref destinationBase, i) = Vector128.ShuffleNative(Unsafe.Add(ref sourceBase, i), mask);
}
}
}
@ -477,15 +477,15 @@ internal static partial class SimdUtils
v2 = Vector128_.AlignRight(v2, v1, 8);
v1 = Vector128_.AlignRight(v1, v0, 12);
v0 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v0, maskPad4Nx16), mask);
v1 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v1, maskPad4Nx16), mask);
v2 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v2, maskPad4Nx16), mask);
v3 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v3, maskPad4Nx16), mask);
v0 = Vector128.ShuffleNative(Vector128.ShuffleNative(v0, maskPad4Nx16), mask);
v1 = Vector128.ShuffleNative(Vector128.ShuffleNative(v1, maskPad4Nx16), mask);
v2 = Vector128.ShuffleNative(Vector128.ShuffleNative(v2, maskPad4Nx16), mask);
v3 = Vector128.ShuffleNative(Vector128.ShuffleNative(v3, maskPad4Nx16), mask);
v0 = Vector128_.ShuffleNative(v0, maskE);
v1 = Vector128_.ShuffleNative(v1, maskSlice4Nx16);
v2 = Vector128_.ShuffleNative(v2, maskE);
v3 = Vector128_.ShuffleNative(v3, maskSlice4Nx16);
v0 = Vector128.ShuffleNative(v0, maskE);
v1 = Vector128.ShuffleNative(v1, maskSlice4Nx16);
v2 = Vector128.ShuffleNative(v2, maskE);
v3 = Vector128.ShuffleNative(v3, maskSlice4Nx16);
v0 = Vector128_.AlignRight(v1, v0, 4);
v3 = Vector128_.AlignRight(v3, v2, 12);
@ -539,10 +539,10 @@ internal static partial class SimdUtils
ref Vector128<byte> vd = ref Unsafe.Add(ref destinationBase, j);
vd = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v0, maskPad4Nx16) | fill, mask);
Unsafe.Add(ref vd, 1) = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v1, maskPad4Nx16) | fill, mask);
Unsafe.Add(ref vd, 2) = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v2, maskPad4Nx16) | fill, mask);
Unsafe.Add(ref vd, 3) = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v3, maskPad4Nx16) | fill, mask);
vd = Vector128.ShuffleNative(Vector128.ShuffleNative(v0, maskPad4Nx16) | fill, mask);
Unsafe.Add(ref vd, 1) = Vector128.ShuffleNative(Vector128.ShuffleNative(v1, maskPad4Nx16) | fill, mask);
Unsafe.Add(ref vd, 2) = Vector128.ShuffleNative(Vector128.ShuffleNative(v2, maskPad4Nx16) | fill, mask);
Unsafe.Add(ref vd, 3) = Vector128.ShuffleNative(Vector128.ShuffleNative(v3, maskPad4Nx16) | fill, mask);
}
}
}
@ -579,10 +579,10 @@ internal static partial class SimdUtils
Vector128<byte> v2 = Unsafe.Add(ref vs, 2);
Vector128<byte> v3 = Unsafe.Add(ref vs, 3);
v0 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v0, mask), maskE);
v1 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v1, mask), maskSlice4Nx16);
v2 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v2, mask), maskE);
v3 = Vector128_.ShuffleNative(Vector128_.ShuffleNative(v3, mask), maskSlice4Nx16);
v0 = Vector128.ShuffleNative(Vector128.ShuffleNative(v0, mask), maskE);
v1 = Vector128.ShuffleNative(Vector128.ShuffleNative(v1, mask), maskSlice4Nx16);
v2 = Vector128.ShuffleNative(Vector128.ShuffleNative(v2, mask), maskE);
v3 = Vector128.ShuffleNative(Vector128.ShuffleNative(v3, mask), maskSlice4Nx16);
v0 = Vector128_.AlignRight(v1, v0, 4);
v3 = Vector128_.AlignRight(v3, v2, 12);
@ -740,10 +740,10 @@ internal static partial class SimdUtils
Vector512<float> f3 = Avx512F.ConvertToVector512Single(i3);
// The residual term restores the correctly rounded byte / 255F result without paying for vector division.
f0 = Vector512_.FusedMultiplyAdd(f0, high, f0 * low);
f1 = Vector512_.FusedMultiplyAdd(f1, high, f1 * low);
f2 = Vector512_.FusedMultiplyAdd(f2, high, f2 * low);
f3 = Vector512_.FusedMultiplyAdd(f3, high, f3 * low);
f0 = Vector512.FusedMultiplyAdd(f0, high, f0 * low);
f1 = Vector512.FusedMultiplyAdd(f1, high, f1 * low);
f2 = Vector512.FusedMultiplyAdd(f2, high, f2 * low);
f3 = Vector512.FusedMultiplyAdd(f3, high, f3 * low);
ref Vector512<float> d = ref Unsafe.Add(ref destinationBase, i * 4);
@ -780,10 +780,10 @@ internal static partial class SimdUtils
Vector256<float> f2 = Avx.ConvertToVector256Single(i2);
Vector256<float> f3 = Avx.ConvertToVector256Single(i3);
f0 = Vector256_.FusedMultiplyAdd(f0, high, f0 * low);
f1 = Vector256_.FusedMultiplyAdd(f1, high, f1 * low);
f2 = Vector256_.FusedMultiplyAdd(f2, high, f2 * low);
f3 = Vector256_.FusedMultiplyAdd(f3, high, f3 * low);
f0 = Vector256.FusedMultiplyAdd(f0, high, f0 * low);
f1 = Vector256.FusedMultiplyAdd(f1, high, f1 * low);
f2 = Vector256.FusedMultiplyAdd(f2, high, f2 * low);
f3 = Vector256.FusedMultiplyAdd(f3, high, f3 * low);
ref Vector256<float> d = ref Unsafe.Add(ref destinationBase, i * 4);
@ -833,10 +833,10 @@ internal static partial class SimdUtils
Vector128<float> f2 = Vector128.ConvertToSingle(i2);
Vector128<float> f3 = Vector128.ConvertToSingle(i3);
f0 = Vector128_.FusedMultiplyAdd(f0, high, f0 * low);
f1 = Vector128_.FusedMultiplyAdd(f1, high, f1 * low);
f2 = Vector128_.FusedMultiplyAdd(f2, high, f2 * low);
f3 = Vector128_.FusedMultiplyAdd(f3, high, f3 * low);
f0 = Vector128.FusedMultiplyAdd(f0, high, f0 * low);
f1 = Vector128.FusedMultiplyAdd(f1, high, f1 * low);
f2 = Vector128.FusedMultiplyAdd(f2, high, f2 * low);
f3 = Vector128.FusedMultiplyAdd(f3, high, f3 * low);
ref Vector128<float> d = ref Unsafe.Add(ref destinationBase, i * 4);
@ -1026,10 +1026,10 @@ internal static partial class SimdUtils
Vector128<int> w2 = Vector128_.ConvertToInt32RoundAwayFromZero(f2);
Vector128<int> w3 = Vector128_.ConvertToInt32RoundAwayFromZero(f3);
w0 = Vector128_.Clamp(w0, min, max);
w1 = Vector128_.Clamp(w1, min, max);
w2 = Vector128_.Clamp(w2, min, max);
w3 = Vector128_.Clamp(w3, min, max);
w0 = Vector128.Clamp(w0, min, max);
w1 = Vector128.Clamp(w1, min, max);
w2 = Vector128.Clamp(w2, min, max);
w3 = Vector128.Clamp(w3, min, max);
Vector128<ushort> u0 = Vector128.Narrow(w0, w1).AsUInt16();
Vector128<ushort> u1 = Vector128.Narrow(w2, w3).AsUInt16();

38
src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs

@ -182,15 +182,15 @@ internal static partial class SimdUtils
v2 = Vector128_.AlignRight(v2, v1, 8);
v1 = Vector128_.AlignRight(v1, v0, 12);
v0 = TShuffle.Invoke(Vector128_.ShuffleNative(v0, padMask));
v1 = TShuffle.Invoke(Vector128_.ShuffleNative(v1, padMask));
v2 = TShuffle.Invoke(Vector128_.ShuffleNative(v2, padMask));
v3 = TShuffle.Invoke(Vector128_.ShuffleNative(v3, padMask));
v0 = TShuffle.Invoke(Vector128.ShuffleNative(v0, padMask));
v1 = TShuffle.Invoke(Vector128.ShuffleNative(v1, padMask));
v2 = TShuffle.Invoke(Vector128.ShuffleNative(v2, padMask));
v3 = TShuffle.Invoke(Vector128.ShuffleNative(v3, padMask));
v0 = Vector128_.ShuffleNative(v0, sliceEndMask);
v1 = Vector128_.ShuffleNative(v1, sliceMask);
v2 = Vector128_.ShuffleNative(v2, sliceEndMask);
v3 = Vector128_.ShuffleNative(v3, sliceMask);
v0 = Vector128.ShuffleNative(v0, sliceEndMask);
v1 = Vector128.ShuffleNative(v1, sliceMask);
v2 = Vector128.ShuffleNative(v2, sliceEndMask);
v3 = Vector128.ShuffleNative(v3, sliceMask);
Vector128<byte> destination0 = Vector128_.AlignRight(v1, v0, 4);
Vector128<byte> destination2 = Vector128_.AlignRight(v3, v2, 12);
@ -213,9 +213,9 @@ internal static partial class SimdUtils
// the following pixels. The pad mask ignores those extra bytes before the operator
// runs, and the slice mask packs the four results into the low twelve bytes.
Vector128<byte> result = Vector128.LoadUnsafe(ref sourceBase, (nuint)i);
result = Vector128_.ShuffleNative(result, padMask);
result = Vector128.ShuffleNative(result, padMask);
result = TShuffle.Invoke(result);
result = Vector128_.ShuffleNative(result, sliceMask);
result = Vector128.ShuffleNative(result, sliceMask);
// Store exactly twelve bytes so an in-place shuffle does not overwrite the next
// source triplet captured by the following iteration.
@ -300,10 +300,10 @@ internal static partial class SimdUtils
v1 = Vector128_.AlignRight(v1, v0, 12);
ref Vector128<byte> destination0 = ref Unsafe.Add(ref destinationVectors, destinationVectorIndex);
destination0 = TShuffle.Invoke(Vector128_.ShuffleNative(v0, padMask) | opaqueAlpha);
Unsafe.Add(ref destination0, 1) = TShuffle.Invoke(Vector128_.ShuffleNative(v1, padMask) | opaqueAlpha);
Unsafe.Add(ref destination0, 2) = TShuffle.Invoke(Vector128_.ShuffleNative(v2, padMask) | opaqueAlpha);
Unsafe.Add(ref destination0, 3) = TShuffle.Invoke(Vector128_.ShuffleNative(v3, padMask) | opaqueAlpha);
destination0 = TShuffle.Invoke(Vector128.ShuffleNative(v0, padMask) | opaqueAlpha);
Unsafe.Add(ref destination0, 1) = TShuffle.Invoke(Vector128.ShuffleNative(v1, padMask) | opaqueAlpha);
Unsafe.Add(ref destination0, 2) = TShuffle.Invoke(Vector128.ShuffleNative(v2, padMask) | opaqueAlpha);
Unsafe.Add(ref destination0, 3) = TShuffle.Invoke(Vector128.ShuffleNative(v3, padMask) | opaqueAlpha);
}
sourceOffset = (int)(sourceVectorIndex * (uint)Vector128<byte>.Count);
@ -381,10 +381,10 @@ internal static partial class SimdUtils
Vector128<byte> v2 = TShuffle.Invoke(Unsafe.Add(ref source0, 2));
Vector128<byte> v3 = TShuffle.Invoke(Unsafe.Add(ref source0, 3));
v0 = Vector128_.ShuffleNative(v0, sliceEndMask);
v1 = Vector128_.ShuffleNative(v1, sliceMask);
v2 = Vector128_.ShuffleNative(v2, sliceEndMask);
v3 = Vector128_.ShuffleNative(v3, sliceMask);
v0 = Vector128.ShuffleNative(v0, sliceEndMask);
v1 = Vector128.ShuffleNative(v1, sliceMask);
v2 = Vector128.ShuffleNative(v2, sliceEndMask);
v3 = Vector128.ShuffleNative(v3, sliceMask);
Vector128<byte> destination0 = Vector128_.AlignRight(v1, v0, 4);
Vector128<byte> destination2 = Vector128_.AlignRight(v3, v2, 12);
@ -408,7 +408,7 @@ internal static partial class SimdUtils
// One fixed shuffle then compacts four pixels into the low twelve vector bytes.
Vector128<byte> result = TShuffle.Invoke(Vector128.LoadUnsafe(ref sourceBase, (nuint)sourceOffset));
result = Vector128_.ShuffleNative(result, sliceMask);
result = Vector128.ShuffleNative(result, sliceMask);
// The split store writes the exact 12-byte result and remains safe for in-place shrinking.
Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref destinationBase, (nuint)destinationOffset)) = result.GetLower();

93
src/ImageSharp/Common/Helpers/TensorPrimitives_.Add.cs

@ -1,93 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Common.Helpers;
internal static partial class TensorPrimitives_
{
/// <summary>
/// Computes the element-wise sum of the values in <paramref name="x"/> and <paramref name="y"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="x">The first addends.</param>
/// <param name="y">The second addends.</param>
/// <param name="destination">The destination for the sums.</param>
/// <exception cref="ArgumentException"><paramref name="x"/> and <paramref name="y"/> do not have the same length.</exception>
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than the input spans.</exception>
/// <exception cref="ArgumentException">
/// An input and <paramref name="destination"/> overlap without beginning at the same memory location.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add<T>(ReadOnlySpan<T> x, ReadOnlySpan<T> y, Span<T> destination)
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
=> InvokeSpanSpanIntoSpan<T, AddOperator<T>>(x, y, destination);
/// <summary>
/// Computes the element-wise sum of the values in <paramref name="x"/> and the scalar <paramref name="y"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="x">The first addends.</param>
/// <param name="y">The scalar second addend.</param>
/// <param name="destination">The destination for the sums.</param>
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
/// <exception cref="ArgumentException">
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add<T>(ReadOnlySpan<T> x, T y, Span<T> destination)
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
=> InvokeSpanScalarIntoSpan<T, AddOperator<T>>(x, y, destination);
/// <summary>
/// Adds corresponding values.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private readonly struct AddOperator<T> : IBinaryOperator<T>
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
/// <summary>
/// Gets a value indicating whether this operation supports vector execution.
/// </summary>
public static bool Vectorizable => true;
/// <summary>
/// Adds scalar values.
/// </summary>
/// <param name="x">The first addend.</param>
/// <param name="y">The second addend.</param>
/// <returns>The sum.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Invoke(T x, T y) => x + y;
/// <summary>
/// Adds 128-bit vectors.
/// </summary>
/// <param name="x">The first addends.</param>
/// <param name="y">The second addends.</param>
/// <returns>The sums.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x + y;
/// <summary>
/// Adds 256-bit vectors.
/// </summary>
/// <param name="x">The first addends.</param>
/// <param name="y">The second addends.</param>
/// <returns>The sums.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x + y;
/// <summary>
/// Adds 512-bit vectors.
/// </summary>
/// <param name="x">The first addends.</param>
/// <param name="y">The second addends.</param>
/// <returns>The sums.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x + y;
}
}

322
src/ImageSharp/Common/Helpers/TensorPrimitives_.Clamp.cs

@ -1,322 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Common.Helpers;
internal static partial class TensorPrimitives_
{
/// <summary>
/// Computes the element-wise result of clamping <paramref name="x"/> to the inclusive range specified
/// by <paramref name="min"/> and <paramref name="max"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="x">The values to clamp.</param>
/// <param name="min">The inclusive lower bound.</param>
/// <param name="max">The inclusive upper bound.</param>
/// <param name="destination">The destination for the clamped values.</param>
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
/// <exception cref="ArgumentException">
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clamp<T>(ReadOnlySpan<T> x, T min, T max, Span<T> destination)
where T : INumber<T>
=> InvokeSpanScalarScalarIntoSpan<T, ClampOperator<T>>(x, min, max, destination);
/// <summary>
/// Clamps single-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="value">The values to clamp.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<float> ClampSingle(
Vector128<float> value,
Vector128<float> min,
Vector128<float> max)
{
// Unlike the native x86 min/max instructions, the normalized runtime operations propagate a NaN in the
// first operand and select negative zero when equal values have different signs.
Vector128<float> maximum = Vector128.ConditionalSelect(
Vector128.LessThan(min, value)
| ~Vector128.Equals(value, value)
| (Vector128.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()),
value,
min);
return Vector128.ConditionalSelect(
Vector128.LessThan(maximum, max)
| ~Vector128.Equals(maximum, maximum)
| (Vector128.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()),
maximum,
max);
}
/// <summary>
/// Clamps single-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="value">The values to clamp.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> ClampSingle(
Vector256<float> value,
Vector256<float> min,
Vector256<float> max)
{
Vector256<float> maximum = Vector256.ConditionalSelect(
Vector256.LessThan(min, value)
| ~Vector256.Equals(value, value)
| (Vector256.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()),
value,
min);
return Vector256.ConditionalSelect(
Vector256.LessThan(maximum, max)
| ~Vector256.Equals(maximum, maximum)
| (Vector256.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()),
maximum,
max);
}
/// <summary>
/// Clamps single-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="value">The values to clamp.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<float> ClampSingle(
Vector512<float> value,
Vector512<float> min,
Vector512<float> max)
{
Vector512<float> maximum = Vector512.ConditionalSelect(
Vector512.LessThan(min, value)
| ~Vector512.Equals(value, value)
| (Vector512.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()),
value,
min);
return Vector512.ConditionalSelect(
Vector512.LessThan(maximum, max)
| ~Vector512.Equals(maximum, maximum)
| (Vector512.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()),
maximum,
max);
}
/// <summary>
/// Clamps double-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="value">The values to clamp.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<double> ClampDouble(
Vector128<double> value,
Vector128<double> min,
Vector128<double> max)
{
Vector128<double> maximum = Vector128.ConditionalSelect(
Vector128.LessThan(min, value)
| ~Vector128.Equals(value, value)
| (Vector128.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()),
value,
min);
return Vector128.ConditionalSelect(
Vector128.LessThan(maximum, max)
| ~Vector128.Equals(maximum, maximum)
| (Vector128.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()),
maximum,
max);
}
/// <summary>
/// Clamps double-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="value">The values to clamp.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<double> ClampDouble(
Vector256<double> value,
Vector256<double> min,
Vector256<double> max)
{
Vector256<double> maximum = Vector256.ConditionalSelect(
Vector256.LessThan(min, value)
| ~Vector256.Equals(value, value)
| (Vector256.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()),
value,
min);
return Vector256.ConditionalSelect(
Vector256.LessThan(maximum, max)
| ~Vector256.Equals(maximum, maximum)
| (Vector256.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()),
maximum,
max);
}
/// <summary>
/// Clamps double-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="value">The values to clamp.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<double> ClampDouble(
Vector512<double> value,
Vector512<double> min,
Vector512<double> max)
{
Vector512<double> maximum = Vector512.ConditionalSelect(
Vector512.LessThan(min, value)
| ~Vector512.Equals(value, value)
| (Vector512.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()),
value,
min);
return Vector512.ConditionalSelect(
Vector512.LessThan(maximum, max)
| ~Vector512.Equals(maximum, maximum)
| (Vector512.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()),
maximum,
max);
}
/// <summary>
/// Clamps values using the complete runtime tensor contract, including signed-zero correction.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private readonly struct ClampOperator<T> : ITernaryOperator<T>
where T : INumber<T>
{
/// <summary>
/// Gets a value indicating whether this operation supports vector execution.
/// </summary>
public static bool Vectorizable => true;
/// <summary>
/// Clamps a scalar value.
/// </summary>
/// <param name="x">The value.</param>
/// <param name="min">The inclusive lower bound.</param>
/// <param name="max">The inclusive upper bound.</param>
/// <returns>The clamped value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Invoke(T x, T min, T max)
=> Vector128<T>.IsSupported ? T.Min(T.Max(x, min), max) : T.Clamp(x, min, max);
/// <summary>
/// Clamps a 128-bit vector.
/// </summary>
/// <param name="x">The values.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> min, Vector128<T> max)
{
if (typeof(T) == typeof(float))
{
Vector128<float> result = ClampSingle(
Unsafe.As<Vector128<T>, Vector128<float>>(ref x),
Unsafe.As<Vector128<T>, Vector128<float>>(ref min),
Unsafe.As<Vector128<T>, Vector128<float>>(ref max));
return Unsafe.As<Vector128<float>, Vector128<T>>(ref result);
}
if (typeof(T) == typeof(double))
{
Vector128<double> result = ClampDouble(
Unsafe.As<Vector128<T>, Vector128<double>>(ref x),
Unsafe.As<Vector128<T>, Vector128<double>>(ref min),
Unsafe.As<Vector128<T>, Vector128<double>>(ref max));
return Unsafe.As<Vector128<double>, Vector128<T>>(ref result);
}
return Vector128_.Clamp(x, min, max);
}
/// <summary>
/// Clamps a 256-bit vector.
/// </summary>
/// <param name="x">The values.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> min, Vector256<T> max)
{
if (typeof(T) == typeof(float))
{
Vector256<float> result = ClampSingle(
Unsafe.As<Vector256<T>, Vector256<float>>(ref x),
Unsafe.As<Vector256<T>, Vector256<float>>(ref min),
Unsafe.As<Vector256<T>, Vector256<float>>(ref max));
return Unsafe.As<Vector256<float>, Vector256<T>>(ref result);
}
if (typeof(T) == typeof(double))
{
Vector256<double> result = ClampDouble(
Unsafe.As<Vector256<T>, Vector256<double>>(ref x),
Unsafe.As<Vector256<T>, Vector256<double>>(ref min),
Unsafe.As<Vector256<T>, Vector256<double>>(ref max));
return Unsafe.As<Vector256<double>, Vector256<T>>(ref result);
}
return Vector256_.Clamp(x, min, max);
}
/// <summary>
/// Clamps a 512-bit vector.
/// </summary>
/// <param name="x">The values.</param>
/// <param name="min">The inclusive lower bounds.</param>
/// <param name="max">The inclusive upper bounds.</param>
/// <returns>The clamped values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> min, Vector512<T> max)
{
if (typeof(T) == typeof(float))
{
Vector512<float> result = ClampSingle(
Unsafe.As<Vector512<T>, Vector512<float>>(ref x),
Unsafe.As<Vector512<T>, Vector512<float>>(ref min),
Unsafe.As<Vector512<T>, Vector512<float>>(ref max));
return Unsafe.As<Vector512<float>, Vector512<T>>(ref result);
}
if (typeof(T) == typeof(double))
{
Vector512<double> result = ClampDouble(
Unsafe.As<Vector512<T>, Vector512<double>>(ref x),
Unsafe.As<Vector512<T>, Vector512<double>>(ref min),
Unsafe.As<Vector512<T>, Vector512<double>>(ref max));
return Unsafe.As<Vector512<double>, Vector512<T>>(ref result);
}
return Vector512_.Clamp(x, min, max);
}
}
}

87
src/ImageSharp/Common/Helpers/TensorPrimitives_.Divide.cs

@ -1,87 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Common.Helpers;
internal static partial class TensorPrimitives_
{
/// <summary>
/// Computes the element-wise result of dividing the values in <paramref name="x"/> by <paramref name="y"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="x">The dividend values.</param>
/// <param name="y">The divisor.</param>
/// <param name="destination">The destination for the quotient values.</param>
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
/// <exception cref="ArgumentException">
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Divide<T>(ReadOnlySpan<T> x, T y, Span<T> destination)
where T : IDivisionOperators<T, T, T>
=> InvokeSpanScalarIntoSpanForDivision<T, DivideOperator<T>>(x, y, destination);
/// <summary>
/// Determines whether <typeparamref name="T"/> has the same vector division support as <see cref="int"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <returns><see langword="true"/> when <typeparamref name="T"/> is a 32-bit signed native integer type.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsInt32Like<T>()
=> typeof(T) == typeof(int) || (IntPtr.Size == 4 && typeof(T) == typeof(nint));
/// <summary>
/// Divides values by a scalar.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private readonly struct DivideOperator<T> : IBinaryOperator<T>
where T : IDivisionOperators<T, T, T>
{
/// <summary>
/// Gets a value indicating whether this operation supports vector execution.
/// </summary>
public static bool Vectorizable => typeof(T) == typeof(float)
|| typeof(T) == typeof(double)
|| (Vector256.IsHardwareAccelerated && IsInt32Like<T>());
/// <summary>
/// Divides scalar values.
/// </summary>
/// <param name="x">The dividend.</param>
/// <param name="y">The divisor.</param>
/// <returns>The quotient.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Invoke(T x, T y) => x / y;
/// <summary>
/// Divides 128-bit vectors.
/// </summary>
/// <param name="x">The dividends.</param>
/// <param name="y">The divisors.</param>
/// <returns>The quotients.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x / y;
/// <summary>
/// Divides 256-bit vectors.
/// </summary>
/// <param name="x">The dividends.</param>
/// <param name="y">The divisors.</param>
/// <returns>The quotients.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x / y;
/// <summary>
/// Divides 512-bit vectors.
/// </summary>
/// <param name="x">The dividends.</param>
/// <param name="y">The divisors.</param>
/// <returns>The quotients.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x / y;
}
}

900
src/ImageSharp/Common/Helpers/TensorPrimitives_.Helpers.cs

@ -1,900 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Common.Helpers;
/// <summary>
/// Provides compatibility implementations for tensor operations that are not available on every target framework.
/// </summary>
/// <remarks>
/// The API shape follows <c>System.Numerics.Tensors.TensorPrimitives</c> so call sites can move to the runtime
/// implementation when ImageSharp no longer supports target frameworks that predate it.
/// </remarks>
internal static partial class TensorPrimitives_
{
/// <summary>
/// Defines an element-wise binary operation.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private interface IBinaryOperator<T>
{
/// <summary>
/// Gets a value indicating whether the operation supports vector execution.
/// </summary>
public static abstract bool Vectorizable { get; }
/// <summary>
/// Applies the operation to scalar values.
/// </summary>
/// <param name="x">The first value.</param>
/// <param name="y">The second value.</param>
/// <returns>The operation result.</returns>
public static abstract T Invoke(T x, T y);
/// <summary>
/// Applies the operation to 128-bit vectors.
/// </summary>
/// <param name="x">The first vector.</param>
/// <param name="y">The second vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector128<T> Invoke(Vector128<T> x, Vector128<T> y);
/// <summary>
/// Applies the operation to 256-bit vectors.
/// </summary>
/// <param name="x">The first vector.</param>
/// <param name="y">The second vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector256<T> Invoke(Vector256<T> x, Vector256<T> y);
/// <summary>
/// Applies the operation to 512-bit vectors.
/// </summary>
/// <param name="x">The first vector.</param>
/// <param name="y">The second vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector512<T> Invoke(Vector512<T> x, Vector512<T> y);
}
/// <summary>
/// Defines an element-wise ternary operation.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private interface ITernaryOperator<T>
{
/// <summary>
/// Gets a value indicating whether the operation supports vector execution.
/// </summary>
public static abstract bool Vectorizable { get; }
/// <summary>
/// Applies the operation to scalar values.
/// </summary>
/// <param name="x">The first value.</param>
/// <param name="y">The second value.</param>
/// <param name="z">The third value.</param>
/// <returns>The operation result.</returns>
public static abstract T Invoke(T x, T y, T z);
/// <summary>
/// Applies the operation to 128-bit vectors.
/// </summary>
/// <param name="x">The first vector.</param>
/// <param name="y">The second vector.</param>
/// <param name="z">The third vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector128<T> Invoke(Vector128<T> x, Vector128<T> y, Vector128<T> z);
/// <summary>
/// Applies the operation to 256-bit vectors.
/// </summary>
/// <param name="x">The first vector.</param>
/// <param name="y">The second vector.</param>
/// <param name="z">The third vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector256<T> Invoke(Vector256<T> x, Vector256<T> y, Vector256<T> z);
/// <summary>
/// Applies the operation to 512-bit vectors.
/// </summary>
/// <param name="x">The first vector.</param>
/// <param name="y">The second vector.</param>
/// <param name="z">The third vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector512<T> Invoke(Vector512<T> x, Vector512<T> y, Vector512<T> z);
}
/// <summary>
/// Validates that an input and destination are either disjoint or begin at the same memory location.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="input">The input values.</param>
/// <param name="destination">The destination values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateInputOutputSpanNonOverlapping<T>(ReadOnlySpan<T> input, Span<T> destination)
{
// Runtime TensorPrimitives permits exact same-start overlap for in-place operation. A shifted overlap is
// rejected because forward SIMD stores could overwrite input elements before a later load consumes them.
if (!Unsafe.AreSame(ref MemoryMarshal.GetReference(input), ref MemoryMarshal.GetReference(destination))
&& input.Overlaps(destination))
{
ThrowInputAndDestinationSpanMustNotOverlap();
}
}
/// <summary>
/// Throws when input spans do not have the same length.
/// </summary>
[DoesNotReturn]
private static void ThrowSpansMustHaveSameLength()
=> throw new ArgumentException("Input span arguments must all have the same length.");
/// <summary>
/// Throws when the destination cannot hold every result.
/// </summary>
[DoesNotReturn]
private static void ThrowDestinationTooShort()
=> throw new ArgumentException("Destination is too short.", "destination");
/// <summary>
/// Throws when an input and destination overlap without beginning at the same memory location.
/// </summary>
[DoesNotReturn]
private static void ThrowInputAndDestinationSpanMustNotOverlap()
=> throw new ArgumentException(
"The destination span may only overlap with an input span if the two spans start at the same memory location.",
"destination");
/// <summary>
/// Performs an element-wise binary operation between two spans.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="x">The first input values.</param>
/// <param name="y">The second input values.</param>
/// <param name="destination">The destination values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeSpanSpanIntoSpan<T, TOperator>(
ReadOnlySpan<T> x,
ReadOnlySpan<T> y,
Span<T> destination)
where TOperator : struct, IBinaryOperator<T>
{
if (x.Length != y.Length)
{
ThrowSpansMustHaveSameLength();
}
if (x.Length > destination.Length)
{
ThrowDestinationTooShort();
}
ValidateInputOutputSpanNonOverlapping(x, destination);
ValidateInputOutputSpanNonOverlapping(y, destination);
ref T xRef = ref MemoryMarshal.GetReference(x);
ref T yRef = ref MemoryMarshal.GetReference(y);
ref T destinationRef = ref MemoryMarshal.GetReference(destination);
nuint length = (uint)x.Length;
// Runtime main selects the widest supported pipeline once one complete vector is available.
// Each pipeline preloads its final inputs when a tail overlaps so same-start in-place operation remains correct.
if (TOperator.Vectorizable
&& Vector512.IsHardwareAccelerated
&& Vector512<T>.IsSupported
&& length >= (uint)Vector512<T>.Count)
{
InvokeVectorized512<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count)
{
InvokeVectorized256<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count)
{
InvokeVectorized128<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length);
return;
}
for (nuint i = 0; i < length; i++)
{
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), Unsafe.Add(ref yRef, i));
}
}
/// <summary>
/// Performs an element-wise binary operation between a span and a scalar.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="x">The input values.</param>
/// <param name="y">The scalar input.</param>
/// <param name="destination">The destination values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeSpanScalarIntoSpan<T, TOperator>(
ReadOnlySpan<T> x,
T y,
Span<T> destination)
where TOperator : struct, IBinaryOperator<T>
{
if (x.Length > destination.Length)
{
ThrowDestinationTooShort();
}
ValidateInputOutputSpanNonOverlapping(x, destination);
ref T xRef = ref MemoryMarshal.GetReference(x);
ref T destinationRef = ref MemoryMarshal.GetReference(destination);
nuint length = (uint)x.Length;
// Runtime main selects the widest supported pipeline once one complete vector is available.
if (TOperator.Vectorizable
&& Vector512.IsHardwareAccelerated
&& Vector512<T>.IsSupported
&& length >= (uint)Vector512<T>.Count)
{
InvokeVectorized512<T, TOperator>(ref xRef, y, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count)
{
InvokeVectorized256<T, TOperator>(ref xRef, y, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count)
{
InvokeVectorized128<T, TOperator>(ref xRef, y, ref destinationRef, length);
return;
}
for (nuint i = 0; i < length; i++)
{
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y);
}
}
/// <summary>
/// Performs element-wise division using the runtime tensor width-selection order.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The division operation to apply.</typeparam>
/// <param name="x">The input values.</param>
/// <param name="y">The scalar divisor.</param>
/// <param name="destination">The destination values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeSpanScalarIntoSpanForDivision<T, TOperator>(
ReadOnlySpan<T> x,
T y,
Span<T> destination)
where TOperator : struct, IBinaryOperator<T>
{
if (x.Length > destination.Length)
{
ThrowDestinationTooShort();
}
ValidateInputOutputSpanNonOverlapping(x, destination);
ref T xRef = ref MemoryMarshal.GetReference(x);
ref T destinationRef = ref MemoryMarshal.GetReference(destination);
nuint length = (uint)x.Length;
// Runtime main selects the widest supported pipeline once one complete vector is available.
if (TOperator.Vectorizable
&& Vector512.IsHardwareAccelerated
&& Vector512<T>.IsSupported
&& length >= (uint)Vector512<T>.Count)
{
InvokeVectorized512<T, TOperator>(ref xRef, y, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count)
{
InvokeVectorized256<T, TOperator>(ref xRef, y, ref destinationRef, length);
return;
}
// Four values fill one 128-bit float vector. Processing exactly one packed prefix before the scalar
// remainder avoids the overlapping second vector that regresses the common seven-element normalization.
if (TOperator.Vectorizable
&& Vector128.IsHardwareAccelerated
&& Vector128<T>.IsSupported
&& length >= (uint)Vector128<T>.Count)
{
nuint vectorCount = (uint)Vector128<T>.Count;
Vector128<T> yVector = Vector128.Create(y);
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef), yVector).StoreUnsafe(ref destinationRef);
for (nuint i = vectorCount; i < length; i++)
{
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y);
}
return;
}
for (nuint i = 0; i < length; i++)
{
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y);
}
}
/// <summary>
/// Performs an element-wise ternary operation between a span and two scalars.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="x">The input values.</param>
/// <param name="y">The first scalar input.</param>
/// <param name="z">The second scalar input.</param>
/// <param name="destination">The destination values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeSpanScalarScalarIntoSpan<T, TOperator>(
ReadOnlySpan<T> x,
T y,
T z,
Span<T> destination)
where TOperator : struct, ITernaryOperator<T>
{
if (x.Length > destination.Length)
{
ThrowDestinationTooShort();
}
ValidateInputOutputSpanNonOverlapping(x, destination);
ref T xRef = ref MemoryMarshal.GetReference(x);
ref T destinationRef = ref MemoryMarshal.GetReference(destination);
nuint length = (uint)x.Length;
// This dispatch mirrors the runtime pipeline: large inputs use the widest available registers while
// short inputs fall through to a width that fits, keeping the operator contract identical at every length.
if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512<T>.IsSupported && length >= (uint)Vector512<T>.Count)
{
InvokeVectorized512<T, TOperator>(ref xRef, y, z, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count)
{
InvokeVectorized256<T, TOperator>(ref xRef, y, z, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count)
{
InvokeVectorized128<T, TOperator>(ref xRef, y, z, ref destinationRef, length);
return;
}
for (nuint i = 0; i < length; i++)
{
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y, z);
}
}
/// <summary>
/// Applies a binary operation between two spans with 128-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first element of the first input.</param>
/// <param name="yRef">The first element of the second input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized128<T, TOperator>(
ref T xRef,
ref T yRef,
ref T destinationRef,
nuint length)
where TOperator : struct, IBinaryOperator<T>
{
nuint vectorCount = (uint)Vector128<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
// When a tail exists, both final inputs are loaded before any stores. This permits either source to also
// be the destination when the tail starts inside the range written by the preceding full vector.
Vector128<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector128.LoadUnsafe(ref xRef, length - vectorCount),
Vector128.LoadUnsafe(ref yRef, length - vectorCount));
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), Vector128.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a binary operation between two spans with 256-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first element of the first input.</param>
/// <param name="yRef">The first element of the second input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized256<T, TOperator>(
ref T xRef,
ref T yRef,
ref T destinationRef,
nuint length)
where TOperator : struct, IBinaryOperator<T>
{
nuint vectorCount = (uint)Vector256<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector256<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector256.LoadUnsafe(ref xRef, length - vectorCount),
Vector256.LoadUnsafe(ref yRef, length - vectorCount));
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), Vector256.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a binary operation between two spans with 512-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first element of the first input.</param>
/// <param name="yRef">The first element of the second input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized512<T, TOperator>(
ref T xRef,
ref T yRef,
ref T destinationRef,
nuint length)
where TOperator : struct, IBinaryOperator<T>
{
nuint vectorCount = (uint)Vector512<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector512<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector512.LoadUnsafe(ref xRef, length - vectorCount),
Vector512.LoadUnsafe(ref yRef, length - vectorCount));
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), Vector512.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a binary operation between a span and a scalar with 128-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="y">The scalar input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized128<T, TOperator>(
ref T xRef,
T y,
ref T destinationRef,
nuint length)
where TOperator : struct, IBinaryOperator<T>
{
nuint vectorCount = (uint)Vector128<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector128<T> yVector = Vector128.Create(y);
// When a tail exists, preloading its final vector is required for in-place operation because it must
// observe the original values before an earlier overlapping store writes them.
Vector128<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector128.LoadUnsafe(ref xRef, length - vectorCount),
yVector);
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a binary operation with 256-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="y">The scalar input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized256<T, TOperator>(
ref T xRef,
T y,
ref T destinationRef,
nuint length)
where TOperator : struct, IBinaryOperator<T>
{
nuint vectorCount = (uint)Vector256<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector256<T> yVector = Vector256.Create(y);
Vector256<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector256.LoadUnsafe(ref xRef, length - vectorCount),
yVector);
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a binary operation with 512-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="y">The scalar input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized512<T, TOperator>(
ref T xRef,
T y,
ref T destinationRef,
nuint length)
where TOperator : struct, IBinaryOperator<T>
{
nuint vectorCount = (uint)Vector512<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector512<T> yVector = Vector512.Create(y);
Vector512<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector512.LoadUnsafe(ref xRef, length - vectorCount),
yVector);
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a ternary operation with 128-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="y">The first scalar input.</param>
/// <param name="z">The second scalar input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized128<T, TOperator>(
ref T xRef,
T y,
T z,
ref T destinationRef,
nuint length)
where TOperator : struct, ITernaryOperator<T>
{
nuint vectorCount = (uint)Vector128<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector128<T> yVector = Vector128.Create(y);
Vector128<T> zVector = Vector128.Create(z);
Vector128<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector128.LoadUnsafe(ref xRef, length - vectorCount),
yVector,
zVector);
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a ternary operation with 256-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="y">The first scalar input.</param>
/// <param name="z">The second scalar input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized256<T, TOperator>(
ref T xRef,
T y,
T z,
ref T destinationRef,
nuint length)
where TOperator : struct, ITernaryOperator<T>
{
nuint vectorCount = (uint)Vector256<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector256<T> yVector = Vector256.Create(y);
Vector256<T> zVector = Vector256.Create(z);
Vector256<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector256.LoadUnsafe(ref xRef, length - vectorCount),
yVector,
zVector);
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a ternary operation with 512-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="y">The first scalar input.</param>
/// <param name="z">The second scalar input.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeVectorized512<T, TOperator>(
ref T xRef,
T y,
T z,
ref T destinationRef,
nuint length)
where TOperator : struct, ITernaryOperator<T>
{
nuint vectorCount = (uint)Vector512<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector512<T> yVector = Vector512.Create(y);
Vector512<T> zVector = Vector512.Create(z);
Vector512<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(
Vector512.LoadUnsafe(ref xRef, length - vectorCount),
yVector,
zVector);
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
}

249
src/ImageSharp/Common/Helpers/TensorPrimitives_.Max.cs

@ -1,249 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Common.Helpers;
internal static partial class TensorPrimitives_
{
/// <summary>
/// Computes the element-wise maximum of the values in <paramref name="x"/> and <paramref name="y"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="x">The values to compare.</param>
/// <param name="y">The value to compare with each element.</param>
/// <param name="destination">The destination for the maximum values.</param>
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
/// <exception cref="ArgumentException">
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Max<T>(ReadOnlySpan<T> x, T y, Span<T> destination)
where T : INumber<T>
=> InvokeSpanScalarIntoSpan<T, MaxOperator<T>>(x, y, destination);
/// <summary>
/// Selects maximum single-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<float> MaxSingle(Vector128<float> x, Vector128<float> y)
{
// The .NET 8 operation already handles ordered unequal values. Correct its second-operand result for a
// first-operand NaN, then use bitwise AND for equal values so positive zero wins regardless of operand order.
Vector128<float> result = Vector128.Max(x, y);
result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result);
return Vector128.ConditionalSelect(
Vector128.Equals(x, y),
x & y,
result);
}
/// <summary>
/// Selects maximum single-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> MaxSingle(Vector256<float> x, Vector256<float> y)
{
Vector256<float> result = Vector256.Max(x, y);
result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result);
return Vector256.ConditionalSelect(
Vector256.Equals(x, y),
x & y,
result);
}
/// <summary>
/// Selects maximum single-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<float> MaxSingle(Vector512<float> x, Vector512<float> y)
{
Vector512<float> result = Vector512.Max(x, y);
result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result);
return Vector512.ConditionalSelect(
Vector512.Equals(x, y),
x & y,
result);
}
/// <summary>
/// Selects maximum double-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<double> MaxDouble(Vector128<double> x, Vector128<double> y)
{
Vector128<double> result = Vector128.Max(x, y);
result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result);
return Vector128.ConditionalSelect(
Vector128.Equals(x, y),
x & y,
result);
}
/// <summary>
/// Selects maximum double-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<double> MaxDouble(Vector256<double> x, Vector256<double> y)
{
Vector256<double> result = Vector256.Max(x, y);
result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result);
return Vector256.ConditionalSelect(
Vector256.Equals(x, y),
x & y,
result);
}
/// <summary>
/// Selects maximum double-precision values with the normalized runtime semantics.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<double> MaxDouble(Vector512<double> x, Vector512<double> y)
{
Vector512<double> result = Vector512.Max(x, y);
result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result);
return Vector512.ConditionalSelect(
Vector512.Equals(x, y),
x & y,
result);
}
/// <summary>
/// Selects the maximum corresponding values.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private readonly struct MaxOperator<T> : IBinaryOperator<T>
where T : INumber<T>
{
/// <summary>
/// Gets a value indicating whether this operation supports vector execution.
/// </summary>
public static bool Vectorizable => true;
/// <summary>
/// Selects the maximum scalar value.
/// </summary>
/// <param name="x">The first value.</param>
/// <param name="y">The second value.</param>
/// <returns>The maximum value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Invoke(T x, T y) => T.Max(x, y);
/// <summary>
/// Selects the maximum values from 128-bit vectors.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y)
{
if (typeof(T) == typeof(float))
{
Vector128<float> result = MaxSingle(
Unsafe.As<Vector128<T>, Vector128<float>>(ref x),
Unsafe.As<Vector128<T>, Vector128<float>>(ref y));
return Unsafe.As<Vector128<float>, Vector128<T>>(ref result);
}
if (typeof(T) == typeof(double))
{
Vector128<double> result = MaxDouble(
Unsafe.As<Vector128<T>, Vector128<double>>(ref x),
Unsafe.As<Vector128<T>, Vector128<double>>(ref y));
return Unsafe.As<Vector128<double>, Vector128<T>>(ref result);
}
return Vector128.Max(x, y);
}
/// <summary>
/// Selects the maximum values from 256-bit vectors.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y)
{
if (typeof(T) == typeof(float))
{
Vector256<float> result = MaxSingle(
Unsafe.As<Vector256<T>, Vector256<float>>(ref x),
Unsafe.As<Vector256<T>, Vector256<float>>(ref y));
return Unsafe.As<Vector256<float>, Vector256<T>>(ref result);
}
if (typeof(T) == typeof(double))
{
Vector256<double> result = MaxDouble(
Unsafe.As<Vector256<T>, Vector256<double>>(ref x),
Unsafe.As<Vector256<T>, Vector256<double>>(ref y));
return Unsafe.As<Vector256<double>, Vector256<T>>(ref result);
}
return Vector256.Max(x, y);
}
/// <summary>
/// Selects the maximum values from 512-bit vectors.
/// </summary>
/// <param name="x">The first values.</param>
/// <param name="y">The second values.</param>
/// <returns>The maximum values.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y)
{
if (typeof(T) == typeof(float))
{
Vector512<float> result = MaxSingle(
Unsafe.As<Vector512<T>, Vector512<float>>(ref x),
Unsafe.As<Vector512<T>, Vector512<float>>(ref y));
return Unsafe.As<Vector512<float>, Vector512<T>>(ref result);
}
if (typeof(T) == typeof(double))
{
Vector512<double> result = MaxDouble(
Unsafe.As<Vector512<T>, Vector512<double>>(ref x),
Unsafe.As<Vector512<T>, Vector512<double>>(ref y));
return Unsafe.As<Vector512<double>, Vector512<T>>(ref result);
}
return Vector512.Max(x, y);
}
}
}

76
src/ImageSharp/Common/Helpers/TensorPrimitives_.Multiply.cs

@ -1,76 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Common.Helpers;
internal static partial class TensorPrimitives_
{
/// <summary>
/// Computes the element-wise product of the values in <paramref name="x"/> and <paramref name="y"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="x">The multiplicands.</param>
/// <param name="y">The multiplier.</param>
/// <param name="destination">The destination for the products.</param>
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
/// <exception cref="ArgumentException">
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Multiply<T>(ReadOnlySpan<T> x, T y, Span<T> destination)
where T : IMultiplyOperators<T, T, T>, IMultiplicativeIdentity<T, T>
=> InvokeSpanScalarIntoSpan<T, MultiplyOperator<T>>(x, y, destination);
/// <summary>
/// Multiplies corresponding values.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private readonly struct MultiplyOperator<T> : IBinaryOperator<T>
where T : IMultiplyOperators<T, T, T>, IMultiplicativeIdentity<T, T>
{
/// <summary>
/// Gets a value indicating whether this operation supports vector execution.
/// </summary>
public static bool Vectorizable => true;
/// <summary>
/// Multiplies scalar values.
/// </summary>
/// <param name="x">The multiplicand.</param>
/// <param name="y">The multiplier.</param>
/// <returns>The product.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Invoke(T x, T y) => x * y;
/// <summary>
/// Multiplies 128-bit vectors.
/// </summary>
/// <param name="x">The multiplicands.</param>
/// <param name="y">The multipliers.</param>
/// <returns>The products.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x * y;
/// <summary>
/// Multiplies 256-bit vectors.
/// </summary>
/// <param name="x">The multiplicands.</param>
/// <param name="y">The multipliers.</param>
/// <returns>The products.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x * y;
/// <summary>
/// Multiplies 512-bit vectors.
/// </summary>
/// <param name="x">The multiplicands.</param>
/// <param name="y">The multipliers.</param>
/// <returns>The products.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x * y;
}
}

332
src/ImageSharp/Common/Helpers/TensorPrimitives_.Negate.cs

@ -1,332 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Common.Helpers;
internal static partial class TensorPrimitives_
{
/// <summary>
/// Defines an element-wise unary operation.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private interface IUnaryOperator<T>
{
/// <summary>
/// Gets a value indicating whether the operation supports vector execution.
/// </summary>
public static abstract bool Vectorizable { get; }
/// <summary>
/// Applies the operation to a scalar value.
/// </summary>
/// <param name="x">The input value.</param>
/// <returns>The operation result.</returns>
public static abstract T Invoke(T x);
/// <summary>
/// Applies the operation to a 128-bit vector.
/// </summary>
/// <param name="x">The input vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector128<T> Invoke(Vector128<T> x);
/// <summary>
/// Applies the operation to a 256-bit vector.
/// </summary>
/// <param name="x">The input vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector256<T> Invoke(Vector256<T> x);
/// <summary>
/// Applies the operation to a 512-bit vector.
/// </summary>
/// <param name="x">The input vector.</param>
/// <returns>The operation result.</returns>
public static abstract Vector512<T> Invoke(Vector512<T> x);
}
/// <summary>
/// Computes the element-wise negation of the values in <paramref name="x"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="x">The values to negate.</param>
/// <param name="destination">The destination for the negated values.</param>
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
/// <exception cref="ArgumentException">
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Negate<T>(ReadOnlySpan<T> x, Span<T> destination)
where T : IUnaryNegationOperators<T, T>
=> InvokeSpanIntoSpan<T, NegateOperator<T>>(x, destination);
/// <summary>
/// Performs an element-wise unary operation over a span.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="x">The input values.</param>
/// <param name="destination">The destination values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeSpanIntoSpan<T, TOperator>(ReadOnlySpan<T> x, Span<T> destination)
where TOperator : struct, IUnaryOperator<T>
{
if (x.Length > destination.Length)
{
ThrowDestinationTooShort();
}
ValidateInputOutputSpanNonOverlapping(x, destination);
ref T xRef = ref MemoryMarshal.GetReference(x);
ref T destinationRef = ref MemoryMarshal.GetReference(destination);
nuint length = (uint)x.Length;
// Runtime main selects the widest supported pipeline once one complete vector is available.
if (TOperator.Vectorizable
&& Vector512.IsHardwareAccelerated
&& Vector512<T>.IsSupported
&& length >= (uint)Vector512<T>.Count)
{
InvokeUnaryVectorized512<T, TOperator>(ref xRef, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count)
{
InvokeUnaryVectorized256<T, TOperator>(ref xRef, ref destinationRef, length);
return;
}
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count)
{
InvokeUnaryVectorized128<T, TOperator>(ref xRef, ref destinationRef, length);
return;
}
for (nuint i = 0; i < length; i++)
{
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i));
}
}
/// <summary>
/// Applies a unary operation with 128-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeUnaryVectorized128<T, TOperator>(ref T xRef, ref T destinationRef, nuint length)
where TOperator : struct, IUnaryOperator<T>
{
nuint vectorCount = (uint)Vector128<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
// The final vector overlaps the preceding store when the length is not a vector multiple. Loading it
// before any stores preserves same-start in-place operation because it captures the original tail.
Vector128<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, length - vectorCount));
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a unary operation with 256-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeUnaryVectorized256<T, TOperator>(ref T xRef, ref T destinationRef, nuint length)
where TOperator : struct, IUnaryOperator<T>
{
nuint vectorCount = (uint)Vector256<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector256<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, length - vectorCount));
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Applies a unary operation with 512-bit vectors.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <typeparam name="TOperator">The operation to apply.</typeparam>
/// <param name="xRef">The first input element.</param>
/// <param name="destinationRef">The first destination element.</param>
/// <param name="length">The number of elements to process.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeUnaryVectorized512<T, TOperator>(ref T xRef, ref T destinationRef, nuint length)
where TOperator : struct, IUnaryOperator<T>
{
nuint vectorCount = (uint)Vector512<T>.Count;
nuint vectorsPerLoop = vectorCount * 8;
nuint index = 0;
Vector512<T> end = default;
if ((length % vectorCount) != 0)
{
end = TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, length - vectorCount));
}
while ((length - index) >= vectorsPerLoop)
{
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6));
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7));
index += vectorsPerLoop;
}
while ((length - index) >= vectorCount)
{
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index);
index += vectorCount;
}
if (index != length)
{
end.StoreUnsafe(ref destinationRef, length - vectorCount);
}
}
/// <summary>
/// Implements element-wise negation for scalar and SIMD inputs.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
private readonly struct NegateOperator<T> : IUnaryOperator<T>
where T : IUnaryNegationOperators<T, T>
{
/// <inheritdoc />
public static bool Vectorizable => true;
/// <inheritdoc />
public static T Invoke(T x) => -x;
/// <inheritdoc />
public static Vector128<T> Invoke(Vector128<T> x)
{
if (typeof(T) == typeof(float))
{
// IEEE-754 negation toggles the sign bit. Expressing that operation explicitly avoids the
// subtraction-based ARM64 code generated by .NET 8 for generic vector negation, which loses
// the sign when +0F is negated and therefore differs from both scalar and runtime-main behavior.
return x ^ Vector128.Create(-0F).As<float, T>();
}
if (typeof(T) == typeof(double))
{
// Double-precision values use the same sign-bit representation, with the sign in bit 63.
return x ^ Vector128.Create(-0D).As<double, T>();
}
return -x;
}
/// <inheritdoc />
public static Vector256<T> Invoke(Vector256<T> x)
{
if (typeof(T) == typeof(float))
{
// Keep the operation bitwise at every width so ARM64 preserves signed zero exactly.
return x ^ Vector256.Create(-0F).As<float, T>();
}
if (typeof(T) == typeof(double))
{
return x ^ Vector256.Create(-0D).As<double, T>();
}
return -x;
}
/// <inheritdoc />
public static Vector512<T> Invoke(Vector512<T> x)
{
if (typeof(T) == typeof(float))
{
// Vector512 can be hardware accelerated directly or decomposed by the runtime; the explicit
// bit operation provides identical IEEE-754 behavior in either case.
return x ^ Vector512.Create(-0F).As<float, T>();
}
if (typeof(T) == typeof(double))
{
return x ^ Vector512.Create(-0D).As<double, T>();
}
return -x;
}
}
}

573
src/ImageSharp/Common/Helpers/Vector128Utilities.cs

@ -74,7 +74,7 @@ internal static class Vector128_
(control >> 4) & 0x3,
(control >> 6) & 0x3);
return Vector128.Shuffle(vector, indices);
return Vector128.ShuffleNative(vector, indices);
}
/// <summary>
@ -93,7 +93,7 @@ internal static class Vector128_
(control >> 4) & 0x3,
(control >> 6) & 0x3);
return Vector128.Shuffle(vector, indices);
return Vector128.ShuffleNative(vector, indices);
}
/// <summary>
@ -124,7 +124,7 @@ internal static class Vector128_
(short)(((control >> 4) & 0x3) + 4),
(short)(((control >> 6) & 0x3) + 4));
return Vector128.Shuffle(value, indices);
return Vector128.ShuffleNative(value, indices);
}
/// <summary>
@ -155,33 +155,7 @@ internal static class Vector128_
6,
7);
return Vector128.Shuffle(value, indices);
}
/// <summary>
/// Creates a new vector by selecting values from an input vector using a set of indices.
/// </summary>
/// <param name="vector">
/// The input vector from which values are selected.</param>
/// <param name="indices">
/// The per-element indices used to select a value from <paramref name="vector" />.
/// </param>
/// <returns>
/// A new vector containing the values from <paramref name="vector" /> selected by the given <paramref name="indices" />.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> ShuffleNative(Vector128<byte> vector, Vector128<byte> indices)
{
// For x64 we use the SSSE3 shuffle intrinsic to avoid additional instructions. 3 vs 1.
if (Ssse3.IsSupported)
{
return Ssse3.Shuffle(vector, indices);
}
// For ARM and WASM, codegen will be optimal.
// We don't throw for x86/x64 so we should never use this method without
// checking for support.
return Vector128.Shuffle(vector, indices);
return Vector128.ShuffleNative(value, indices);
}
/// <summary>
@ -230,27 +204,6 @@ internal static class Vector128_
return Vector128.Shuffle(value, Vector128.Create((byte)0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) - Vector128.Create(numBytes));
}
/// <summary>
/// Shift packed 16-bit integers in <paramref name="value"/> left by <paramref name="value"/> while
/// shifting in zeros, and store the results
/// </summary>
/// <param name="value">The vector containing packed 16-bit integers to shift.</param>
/// <param name="count">The number of bits to shift left.</param>
/// <returns>
/// A vector containing the packed 16-bit integers shifted left by <paramref name="count"/>, with zeros shifted in.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> ShiftLeftLogical(Vector128<short> value, [ConstantExpected] byte count)
{
// Zero lanes where count >= 16 to match SSE2
if (count >= 16)
{
return Vector128<short>.Zero;
}
return value << count;
}
/// <summary>
/// Right aligns elements of two source 128-bit values depending on bits in a mask.
/// </summary>
@ -332,92 +285,6 @@ internal static class Vector128_
return Vector128.ConvertToInt32(vector + fallbackAdjustment);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The vector with each value rounded to the nearest integer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<float> RoundToNearestInteger(Vector128<float> vector)
{
if (Sse41.IsSupported)
{
return Sse41.RoundToNearestInteger(vector);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.RoundToNearest(vector);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.RoundToNearest(vector);
}
Vector128<float> sign = vector & Vector128.Create(-0F);
Vector128<float> val_2p23_f32 = sign | Vector128.Create(8388608F);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return val_2p23_f32 | sign;
}
/// <summary>
/// Computes an estimate of (<paramref name="left"/> * <paramref name="right"/>) + <paramref name="addend"/>.
/// </summary>
/// <param name="left">The first vector to multiply.</param>
/// <param name="right">The second vector to multiply.</param>
/// <param name="addend">The vector to add to the product.</param>
/// <returns>An estimate of the multiplication and addition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<float> MultiplyAddEstimate(Vector128<float> left, Vector128<float> right, Vector128<float> addend)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(left, right, addend);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.FusedMultiplyAdd(addend, left, right);
}
return (left * right) + addend;
}
/// <summary>
/// Computes (<paramref name="left"/> * <paramref name="right"/>) + <paramref name="addend"/>, rounded as one ternary operation.
/// </summary>
/// <param name="left">The first vector to multiply.</param>
/// <param name="right">The second vector to multiply.</param>
/// <param name="addend">The vector to add to the product.</param>
/// <returns>The fused multiplication and addition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<float> FusedMultiplyAdd(Vector128<float> left, Vector128<float> right, Vector128<float> addend)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(left, right, addend);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.FusedMultiplyAdd(addend, left, right);
}
// WebAssembly SIMD has no exact fused multiply-add, so match the runtime fallback by preserving fused rounding per element.
Vector64<float> lower = Vector64.Create(
MathF.FusedMultiplyAdd(left.GetElement(0), right.GetElement(0), addend.GetElement(0)),
MathF.FusedMultiplyAdd(left.GetElement(1), right.GetElement(1), addend.GetElement(1)));
Vector64<float> upper = Vector64.Create(
MathF.FusedMultiplyAdd(left.GetElement(2), right.GetElement(2), addend.GetElement(2)),
MathF.FusedMultiplyAdd(left.GetElement(3), right.GetElement(3), addend.GetElement(3)));
return Vector128.Create(lower, upper);
}
/// <summary>
/// Packs signed 16-bit integers to unsigned 8-bit integers and saturates.
/// </summary>
@ -443,8 +310,8 @@ internal static class Vector128_
Vector128<short> min = Vector128.Create((short)byte.MinValue);
Vector128<short> max = Vector128.Create((short)byte.MaxValue);
Vector128<ushort> lefClamped = Clamp(left, min, max).AsUInt16();
Vector128<ushort> rightClamped = Clamp(right, min, max).AsUInt16();
Vector128<ushort> lefClamped = Vector128.Clamp(left, min, max).AsUInt16();
Vector128<ushort> rightClamped = Vector128.Clamp(right, min, max).AsUInt16();
return Vector128.Narrow(lefClamped, rightClamped);
}
@ -474,85 +341,11 @@ internal static class Vector128_
Vector128<int> min = Vector128.Create((int)ushort.MinValue);
Vector128<int> max = Vector128.Create((int)ushort.MaxValue);
Vector128<uint> lefClamped = Clamp(left, min, max).AsUInt32();
Vector128<uint> rightClamped = Clamp(right, min, max).AsUInt32();
Vector128<uint> lefClamped = Vector128.Clamp(left, min, max).AsUInt32();
Vector128<uint> rightClamped = Vector128.Clamp(right, min, max).AsUInt32();
return Vector128.Narrow(lefClamped, rightClamped);
}
/// <summary>
/// Packs signed 32-bit integers to signed 16-bit integers and saturates.
/// </summary>
/// <param name="left">The left hand source vector.</param>
/// <param name="right">The right hand source vector.</param>
/// <returns>The <see cref="Vector128{Int16}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> PackSignedSaturate(Vector128<int> left, Vector128<int> right)
{
if (Sse2.IsSupported)
{
return Sse2.PackSignedSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.ExtractNarrowingSaturateUpper(AdvSimd.ExtractNarrowingSaturateLower(left), right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.ConvertNarrowingSaturateSigned(left, right);
}
Vector128<int> min = Vector128.Create((int)short.MinValue);
Vector128<int> max = Vector128.Create((int)short.MaxValue);
Vector128<int> lefClamped = Clamp(left, min, max);
Vector128<int> rightClamped = Clamp(right, min, max);
return Vector128.Narrow(lefClamped, rightClamped);
}
/// <summary>
/// Packs signed 16-bit integers to signed 8-bit integers and saturates.
/// </summary>
/// <param name="left">The left hand source vector.</param>
/// <param name="right">The right hand source vector.</param>
/// <returns>The <see cref="Vector128{Int16}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<sbyte> PackSignedSaturate(Vector128<short> left, Vector128<short> right)
{
if (Sse2.IsSupported)
{
return Sse2.PackSignedSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.ExtractNarrowingSaturateUpper(AdvSimd.ExtractNarrowingSaturateLower(left), right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.ConvertNarrowingSaturateSigned(left, right);
}
Vector128<short> min = Vector128.Create((short)sbyte.MinValue);
Vector128<short> max = Vector128.Create((short)sbyte.MaxValue);
Vector128<short> lefClamped = Clamp(left, min, max);
Vector128<short> rightClamped = Clamp(right, min, max);
return Vector128.Narrow(lefClamped, rightClamped);
}
/// <summary>
/// Restricts a vector between a minimum and a maximum value.
/// </summary>
/// <typeparam name="T">The type of the elements in the vector.</typeparam>
/// <param name="value">The vector to restrict.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The restricted <see cref="Vector128{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<T> Clamp<T>(Vector128<T> value, Vector128<T> min, Vector128<T> max)
=> Vector128.Min(Vector128.Max(value, min), max);
/// <summary>
/// Multiply packed signed 16-bit integers in <paramref name="left"/> and <paramref name="right"/>, producing
/// intermediate signed 32-bit integers. Horizontally add adjacent pairs of intermediate 32-bit integers, and
@ -847,7 +640,7 @@ internal static class Vector128_
}
Vector128<int> unpacked = Vector128.Create(left.GetUpper(), right.GetUpper());
return Vector128.Shuffle(unpacked, Vector128.Create(0, 2, 1, 3));
return Vector128.ShuffleNative(unpacked, Vector128.Create(0, 2, 1, 3));
}
/// <summary>
@ -878,7 +671,7 @@ internal static class Vector128_
}
Vector128<int> unpacked = Vector128.Create(left.GetLower(), right.GetLower());
return Vector128.Shuffle(unpacked, Vector128.Create(0, 2, 1, 3));
return Vector128.ShuffleNative(unpacked, Vector128.Create(0, 2, 1, 3));
}
/// <summary>
@ -909,7 +702,7 @@ internal static class Vector128_
}
Vector128<short> unpacked = Vector128.Create(left.GetUpper(), right.GetUpper());
return Vector128.Shuffle(unpacked, Vector128.Create(0, 4, 1, 5, 2, 6, 3, 7));
return Vector128.ShuffleNative(unpacked, Vector128.Create(0, 4, 1, 5, 2, 6, 3, 7));
}
/// <summary>
@ -940,7 +733,7 @@ internal static class Vector128_
}
Vector128<short> unpacked = Vector128.Create(left.GetLower(), right.GetLower());
return Vector128.Shuffle(unpacked, Vector128.Create(0, 4, 1, 5, 2, 6, 3, 7));
return Vector128.ShuffleNative(unpacked, Vector128.Create(0, 4, 1, 5, 2, 6, 3, 7));
}
/// <summary>
@ -971,7 +764,7 @@ internal static class Vector128_
}
Vector128<byte> unpacked = Vector128.Create(left.GetUpper(), right.GetUpper());
return Vector128.Shuffle(unpacked, Vector128.Create((byte)0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
return Vector128.ShuffleNative(unpacked, Vector128.Create((byte)0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
}
/// <summary>
@ -1002,7 +795,7 @@ internal static class Vector128_
}
Vector128<byte> unpacked = Vector128.Create(left.GetLower(), right.GetLower());
return Vector128.Shuffle(unpacked, Vector128.Create((byte)0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
return Vector128.ShuffleNative(unpacked, Vector128.Create((byte)0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
}
/// <summary>
@ -1033,7 +826,7 @@ internal static class Vector128_
}
Vector128<sbyte> unpacked = Vector128.Create(left.GetUpper(), right.GetUpper());
return Vector128.Shuffle(unpacked, Vector128.Create(0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
return Vector128.ShuffleNative(unpacked, Vector128.Create(0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
}
/// <summary>
@ -1064,340 +857,6 @@ internal static class Vector128_
}
Vector128<sbyte> unpacked = Vector128.Create(left.GetLower(), right.GetLower());
return Vector128.Shuffle(unpacked, Vector128.Create(0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
}
/// <summary>
/// Subtract packed signed 16-bit integers in <paramref name="right"/> from packed signed 16-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed signed 16-bit integers to subtract from.
/// </param>
/// <param name="right">
/// The second vector containing packed signed 16-bit integers to subtract.
/// </param>
/// <returns>
/// A vector containing the results of subtracting packed signed 16-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> SubtractSaturate(Vector128<short> left, Vector128<short> right)
{
if (Sse2.IsSupported)
{
return Sse2.SubtractSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.SubtractSaturate(left, right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.SubtractSaturate(left, right);
}
// Widen inputs to 32-bit signed
(Vector128<int> leftLo, Vector128<int> leftHi) = Vector128.Widen(left);
(Vector128<int> rightLo, Vector128<int> rightHi) = Vector128.Widen(right);
// Subtract
Vector128<int> diffLo = leftLo - rightLo;
Vector128<int> diffHi = leftHi - rightHi;
// Clamp to signed 16-bit range
Vector128<int> min = Vector128.Create((int)short.MinValue);
Vector128<int> max = Vector128.Create((int)short.MaxValue);
diffLo = Clamp(diffLo, min, max);
diffHi = Clamp(diffHi, min, max);
// Narrow back to 16 bit signed.
return Vector128.Narrow(diffLo, diffHi);
}
/// <summary>
/// Subtract packed unsigned 16-bit integers in <paramref name="right"/> from packed unsigned 16-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed unsigned 16-bit integers to subtract from.
/// </param>
/// <param name="right">
/// The second vector containing packed unsigned 16-bit integers to subtract.
/// </param>
/// <returns>
/// A vector containing the results of subtracting packed unsigned 16-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<ushort> SubtractSaturate(Vector128<ushort> left, Vector128<ushort> right)
{
if (Sse2.IsSupported)
{
return Sse2.SubtractSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.SubtractSaturate(left, right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.SubtractSaturate(left, right);
}
// Widen inputs to 32-bit signed
(Vector128<uint> leftLo, Vector128<uint> leftHi) = Vector128.Widen(left);
(Vector128<uint> rightLo, Vector128<uint> rightHi) = Vector128.Widen(right);
// Subtract
Vector128<uint> diffLo = leftLo - rightLo;
Vector128<uint> diffHi = leftHi - rightHi;
// Clamp to signed 16-bit range
Vector128<uint> min = Vector128.Create((uint)ushort.MinValue);
Vector128<uint> max = Vector128.Create((uint)ushort.MaxValue);
diffLo = Clamp(diffLo, min, max);
diffHi = Clamp(diffHi, min, max);
// Narrow back to 16 bit signed.
return Vector128.Narrow(diffLo, diffHi);
}
/// <summary>
/// Add packed unsigned 8-bit integers in <paramref name="right"/> to packed unsigned 8-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed unsigned 8-bit integers to add to.
/// </param>
/// <param name="right">
/// The second vector containing packed unsigned 8-bit integers to add.
/// </param>
/// <returns>
/// A vector containing the results of adding packed unsigned 8-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> AddSaturate(Vector128<byte> left, Vector128<byte> right)
{
if (Sse2.IsSupported)
{
return Sse2.AddSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.AddSaturate(left, right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.AddSaturate(left, right);
}
// Widen inputs to 16-bit
(Vector128<ushort> leftLo, Vector128<ushort> leftHi) = Vector128.Widen(left);
(Vector128<ushort> rightLo, Vector128<ushort> rightHi) = Vector128.Widen(right);
// Add
Vector128<ushort> sumLo = leftLo + rightLo;
Vector128<ushort> sumHi = leftHi + rightHi;
// Clamp to signed 8-bit range
Vector128<ushort> max = Vector128.Create((ushort)byte.MaxValue);
sumLo = Clamp(sumLo, Vector128<ushort>.Zero, max);
sumHi = Clamp(sumHi, Vector128<ushort>.Zero, max);
// Narrow back to bytes
return Vector128.Narrow(sumLo, sumHi);
}
/// <summary>
/// Add packed unsigned 16-bit integers in <paramref name="right"/> to packed unsigned 16-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed unsigned 16-bit integers to add to.
/// </param>
/// <param name="right">
/// The second vector containing packed unsigned 16-bit integers to add.
/// </param>
/// <returns>
/// A vector containing the results of adding packed unsigned 16-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<ushort> AddSaturate(Vector128<ushort> left, Vector128<ushort> right)
{
if (Sse2.IsSupported)
{
return Sse2.AddSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.AddSaturate(left, right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.AddSaturate(left, right);
}
// Widen inputs to 32-bit
(Vector128<uint> leftLo, Vector128<uint> leftHi) = Vector128.Widen(left);
(Vector128<uint> rightLo, Vector128<uint> rightHi) = Vector128.Widen(right);
// Add
Vector128<uint> sumLo = leftLo + rightLo;
Vector128<uint> sumHi = leftHi + rightHi;
// Clamp to signed 16-bit range
Vector128<uint> max = Vector128.Create((uint)ushort.MaxValue);
sumLo = Clamp(sumLo, Vector128<uint>.Zero, max);
sumHi = Clamp(sumHi, Vector128<uint>.Zero, max);
// Narrow back to 16 bit unsigned.
return Vector128.Narrow(sumLo, sumHi);
}
/// <summary>
/// Subtract packed unsigned 8-bit integers in <paramref name="right"/> from packed unsigned 8-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed unsigned 8-bit integers to subtract from.
/// </param>
/// <param name="right">
/// The second vector containing packed unsigned 8-bit integers to subtract.
/// </param>
/// <returns>
/// A vector containing the results of subtracting packed unsigned 8-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> SubtractSaturate(Vector128<byte> left, Vector128<byte> right)
{
if (Sse2.IsSupported)
{
return Sse2.SubtractSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.SubtractSaturate(left, right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.SubtractSaturate(left, right);
}
// Subtracting the smaller operand implements the .NET 10 unsigned contract:
// lanes where right exceeds left subtract left from itself and therefore saturate at zero.
return left - Vector128.Min(left, right);
}
/// <summary>
/// Add packed unsigned 8-bit integers in <paramref name="right"/> from packed unsigned 8-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed unsigned 8-bit integers to add to.
/// </param>
/// <param name="right">
/// The second vector containing packed unsigned 8-bit integers to add.
/// </param>
/// <returns>
/// A vector containing the results of adding packed unsigned 8-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<sbyte> AddSaturate(Vector128<sbyte> left, Vector128<sbyte> right)
{
if (Sse2.IsSupported)
{
return Sse2.AddSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.AddSaturate(left, right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.AddSaturate(left, right);
}
// Widen inputs to 16-bit
(Vector128<short> leftLo, Vector128<short> leftHi) = Vector128.Widen(left);
(Vector128<short> rightLo, Vector128<short> rightHi) = Vector128.Widen(right);
// Add
Vector128<short> sumLo = leftLo + rightLo;
Vector128<short> sumHi = leftHi + rightHi;
// Clamp to signed 8-bit range
Vector128<short> min = Vector128.Create((short)sbyte.MinValue);
Vector128<short> max = Vector128.Create((short)sbyte.MaxValue);
sumLo = Clamp(sumLo, min, max);
sumHi = Clamp(sumHi, min, max);
// Narrow back to signed bytes
return Vector128.Narrow(sumLo, sumHi);
}
/// <summary>
/// Subtract packed signed 8-bit integers in <paramref name="right"/> from packed signed 8-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed signed 8-bit integers to subtract from.
/// </param>
/// <param name="right">
/// The second vector containing packed signed 8-bit integers to subtract.
/// </param>
/// <returns>
/// A vector containing the results of subtracting packed signed 8-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<sbyte> SubtractSaturate(Vector128<sbyte> left, Vector128<sbyte> right)
{
if (Sse2.IsSupported)
{
return Sse2.SubtractSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.SubtractSaturate(left, right);
}
if (PackedSimd.IsSupported)
{
return PackedSimd.SubtractSaturate(left, right);
}
// Widen inputs to 16-bit
(Vector128<short> leftLo, Vector128<short> leftHi) = Vector128.Widen(left);
(Vector128<short> rightLo, Vector128<short> rightHi) = Vector128.Widen(right);
// Subtract
Vector128<short> diffLo = leftLo - rightLo;
Vector128<short> diffHi = leftHi - rightHi;
// Clamp to signed 8-bit range
Vector128<short> min = Vector128.Create((short)sbyte.MinValue);
Vector128<short> max = Vector128.Create((short)sbyte.MaxValue);
diffLo = Clamp(diffLo, min, max);
diffHi = Clamp(diffHi, min, max);
// Narrow back to signed bytes
return Vector128.Narrow(diffLo, diffHi);
return Vector128.ShuffleNative(unpacked, Vector128.Create(0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15));
}
}

158
src/ImageSharp/Common/Helpers/Vector256Utilities.cs

@ -31,13 +31,11 @@ internal static class Vector256_
=> Avx.Shuffle(vector, vector, control);
/// <summary>
/// Creates a new vector by selecting values from an input vector using a set of indices.</summary>
/// <param name="vector">
/// The input vector from which values are selected.</param>
/// <param name="indices">
/// The per-element indices used to select a value from <paramref name="vector" />.
/// </param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
/// Creates a new vector by selecting values from each 128-bit input lane using the corresponding indices.
/// </summary>
/// <param name="vector">The input vector from which values are selected.</param>
/// <param name="indices">The per-element indices used to select values within each 128-bit lane.</param>
/// <returns>The shuffled <see cref="Vector256{Byte}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> ShufflePerLane(Vector256<byte> vector, Vector256<byte> indices)
{
@ -46,9 +44,11 @@ internal static class Vector256_
return Avx2.Shuffle(vector, indices);
}
// The .NET 10 fallback treats indices as full-width when AVX2 is unavailable. Reusing
// the low mask for each half preserves the lane-local vpshufb contract on AVX-only CPUs.
Vector128<byte> indicesLo = indices.GetLower();
Vector128<byte> lower = Vector128_.ShuffleNative(vector.GetLower(), indicesLo);
Vector128<byte> upper = Vector128_.ShuffleNative(vector.GetUpper(), indicesLo);
Vector128<byte> lower = Vector128.ShuffleNative(vector.GetLower(), indicesLo);
Vector128<byte> upper = Vector128.ShuffleNative(vector.GetUpper(), indicesLo);
return Vector256.Create(lower, upper);
}
@ -93,70 +93,6 @@ internal static class Vector256_
return Vector256.ConvertToInt32(vector + fallbackAdjustment);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The vector with each value rounded to the nearest integer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> RoundToNearestInteger(Vector256<float> vector)
{
if (Avx.IsSupported)
{
return Avx.RoundToNearestInteger(vector);
}
Vector256<float> sign = vector & Vector256.Create(-0F);
Vector256<float> val_2p23_f32 = sign | Vector256.Create(8388608F);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return val_2p23_f32 | sign;
}
/// <summary>
/// Computes an estimate of (<paramref name="left"/> * <paramref name="right"/>) + <paramref name="addend"/>.
/// </summary>
/// <param name="left">The first vector to multiply.</param>
/// <param name="right">The second vector to multiply.</param>
/// <param name="addend">The vector to add to the product.</param>
/// <returns>An estimate of the multiplication and addition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> MultiplyAddEstimate(Vector256<float> left, Vector256<float> right, Vector256<float> addend)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(left, right, addend);
}
Vector128<float> lower = Vector128_.MultiplyAddEstimate(left.GetLower(), right.GetLower(), addend.GetLower());
Vector128<float> upper = Vector128_.MultiplyAddEstimate(left.GetUpper(), right.GetUpper(), addend.GetUpper());
return Vector256.Create(lower, upper);
}
/// <summary>
/// Computes (<paramref name="left"/> * <paramref name="right"/>) + <paramref name="addend"/>, rounded as one ternary operation.
/// </summary>
/// <param name="left">The first vector to multiply.</param>
/// <param name="right">The second vector to multiply.</param>
/// <param name="addend">The vector to add to the product.</param>
/// <returns>The fused multiplication and addition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> FusedMultiplyAdd(Vector256<float> left, Vector256<float> right, Vector256<float> addend)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(left, right, addend);
}
// Match the runtime fallback by recursively applying the same fused contract to both halves.
Vector128<float> lower = Vector128_.FusedMultiplyAdd(left.GetLower(), right.GetLower(), addend.GetLower());
Vector128<float> upper = Vector128_.FusedMultiplyAdd(left.GetUpper(), right.GetUpper(), addend.GetUpper());
return Vector256.Create(lower, upper);
}
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector256{Single}"/>.
/// </summary>
@ -244,8 +180,8 @@ internal static class Vector256_
Vector256<int> min = Vector256.Create((int)ushort.MinValue);
Vector256<int> max = Vector256.Create((int)ushort.MaxValue);
Vector256<uint> lefClamped = Clamp(left, min, max).AsUInt32();
Vector256<uint> rightClamped = Clamp(right, min, max).AsUInt32();
Vector256<uint> lefClamped = Vector256.Clamp(left, min, max).AsUInt32();
Vector256<uint> rightClamped = Vector256.Clamp(right, min, max).AsUInt32();
return Vector256.Narrow(lefClamped, rightClamped);
}
@ -265,8 +201,8 @@ internal static class Vector256_
Vector256<int> min = Vector256.Create((int)short.MinValue);
Vector256<int> max = Vector256.Create((int)short.MaxValue);
Vector256<int> lefClamped = Clamp(left, min, max);
Vector256<int> rightClamped = Clamp(right, min, max);
Vector256<int> lefClamped = Vector256.Clamp(left, min, max);
Vector256<int> rightClamped = Vector256.Clamp(right, min, max);
return Vector256.Narrow(lefClamped, rightClamped);
}
@ -286,23 +222,11 @@ internal static class Vector256_
Vector256<short> min = Vector256.Create((short)sbyte.MinValue);
Vector256<short> max = Vector256.Create((short)sbyte.MaxValue);
Vector256<short> lefClamped = Clamp(left, min, max);
Vector256<short> rightClamped = Clamp(right, min, max);
Vector256<short> lefClamped = Vector256.Clamp(left, min, max);
Vector256<short> rightClamped = Vector256.Clamp(right, min, max);
return Vector256.Narrow(lefClamped, rightClamped);
}
/// <summary>
/// Restricts a vector between a minimum and a maximum value.
/// </summary>
/// <typeparam name="T">The type of the elements in the vector.</typeparam>
/// <param name="value">The vector to restrict.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The restricted <see cref="Vector256{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<T> Clamp<T>(Vector256<T> value, Vector256<T> min, Vector256<T> max)
=> Vector256.Min(Vector256.Max(value, min), max);
/// <summary>
/// Widens a <see cref="Vector128{Int16}"/> to a <see cref="Vector256{Int32}"/>.
/// </summary>
@ -473,56 +397,4 @@ internal static class Vector256_
return Vector256.Create(lo, hi);
}
/// <summary>
/// Subtract packed signed 16-bit integers in <paramref name="right"/> from packed signed 16-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed signed 16-bit integers to subtract from.
/// </param>
/// <param name="right">
/// The second vector containing packed signed 16-bit integers to subtract.
/// </param>
/// <returns>
/// A vector containing the results of subtracting packed unsigned 16-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> SubtractSaturate(Vector256<short> left, Vector256<short> right)
{
if (Avx2.IsSupported)
{
return Avx2.SubtractSaturate(left, right);
}
// The .NET 10 portable implementation applies the same saturated operation to
// both 128-bit halves, allowing each half to select its native instruction set.
return Vector256.Create(Vector128_.SubtractSaturate(left.GetLower(), right.GetLower()), Vector128_.SubtractSaturate(left.GetUpper(), right.GetUpper()));
}
/// <summary>
/// Subtract packed unsigned 8-bit integers in <paramref name="right"/> from packed unsigned 8-bit integers
/// in <paramref name="left"/> using saturation, and store the results.
/// </summary>
/// <param name="left">
/// The first vector containing packed unsigned 8-bit integers to subtract from.
/// </param>
/// <param name="right">
/// The second vector containing packed unsigned 8-bit integers to subtract.
/// </param>
/// <returns>
/// A vector containing the results of subtracting packed unsigned 8-bit integers
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> SubtractSaturate(Vector256<byte> left, Vector256<byte> right)
{
if (Avx2.IsSupported)
{
return Avx2.SubtractSaturate(left, right);
}
return Vector256.Create(
Vector128_.SubtractSaturate(left.GetLower(), right.GetLower()),
Vector128_.SubtractSaturate(left.GetUpper(), right.GetUpper()));
}
}

108
src/ImageSharp/Common/Helpers/Vector512Utilities.cs

@ -30,25 +30,6 @@ internal static class Vector512_
public static Vector512<float> ShuffleNative(Vector512<float> vector, [ConstantExpected] byte control)
=> Avx512F.Shuffle(vector, vector, control);
/// <summary>
/// Creates a new vector by selecting values from an input vector using a set of indices.
/// </summary>
/// <param name="vector">The input vector from which values are selected.</param>
/// <param name="indices">
/// The per-element indices used to select a value from <paramref name="vector" />.
/// </param>
/// <returns>The <see cref="Vector512{Byte}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> ShuffleNative(Vector512<byte> vector, Vector512<byte> indices)
{
if (Avx512BW.IsSupported)
{
return Avx512BW.Shuffle(vector, indices);
}
return Vector512.Shuffle(vector, indices);
}
/// <summary>
/// Performs a conversion from a 512-bit vector of 16 single-precision floating-point values to a 512-bit vector of 16 signed 32-bit integer values.
/// Rounding is equivalent to <see cref="MidpointRounding.ToEven"/>.
@ -72,83 +53,6 @@ internal static class Vector512_
return Avx512F.ConvertToVector512Int32WithTruncation(vector + half);
}
/// <summary>
/// Rounds all values in <paramref name="vector"/> to the nearest integer
/// following <see cref="MidpointRounding.ToEven"/> semantics.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>The vector with each value rounded to the nearest integer.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> RoundToNearestInteger(Vector512<float> vector)
// imm8 = 0b1000:
// imm8[7:4] = 0b0000 -> preserve 0 fractional bits (round to whole numbers)
// imm8[3:0] = 0b1000 -> _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC (round to nearest even, suppress exceptions)
=> Avx512F.RoundScale(vector, 0b0000_1000);
/// <summary>
/// Computes an estimate of (<paramref name="left"/> * <paramref name="right"/>) + <paramref name="addend"/>.
/// </summary>
/// <param name="left">The first vector to multiply.</param>
/// <param name="right">The second vector to multiply.</param>
/// <param name="addend">The vector to add to the product.</param>
/// <returns>An estimate of the multiplication and addition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> MultiplyAddEstimate(Vector512<float> left, Vector512<float> right, Vector512<float> addend)
{
if (Avx512F.IsSupported)
{
return Avx512F.FusedMultiplyAdd(left, right, addend);
}
Vector256<float> lower = Vector256_.MultiplyAddEstimate(left.GetLower(), right.GetLower(), addend.GetLower());
Vector256<float> upper = Vector256_.MultiplyAddEstimate(left.GetUpper(), right.GetUpper(), addend.GetUpper());
return Vector512.Create(lower, upper);
}
/// <summary>
/// Computes (<paramref name="left"/> * <paramref name="right"/>) + <paramref name="addend"/>, rounded as one ternary operation.
/// </summary>
/// <param name="left">The first vector to multiply.</param>
/// <param name="right">The second vector to multiply.</param>
/// <param name="addend">The vector to add to the product.</param>
/// <returns>The fused multiplication and addition result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> FusedMultiplyAdd(Vector512<float> left, Vector512<float> right, Vector512<float> addend)
{
if (Avx512F.IsSupported)
{
return Avx512F.FusedMultiplyAdd(left, right, addend);
}
// Match the runtime fallback by recursively applying the same fused contract to both halves.
Vector256<float> lower = Vector256_.FusedMultiplyAdd(left.GetLower(), right.GetLower(), addend.GetLower());
Vector256<float> upper = Vector256_.FusedMultiplyAdd(left.GetUpper(), right.GetUpper(), addend.GetUpper());
return Vector512.Create(lower, upper);
}
/// <summary>
/// Subtracts packed unsigned 8-bit integers in <paramref name="right"/> from
/// <paramref name="left"/>, saturating negative lane results to zero.
/// </summary>
/// <param name="left">The vector from which <paramref name="right"/> is subtracted.</param>
/// <param name="right">The vector to subtract from <paramref name="left"/>.</param>
/// <returns>The element-wise saturated differences.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> SubtractSaturate(Vector512<byte> left, Vector512<byte> right)
{
if (Avx512BW.IsSupported)
{
return Avx512BW.SubtractSaturate(left, right);
}
// This mirrors the .NET 10 portable implementation: recursively processing both
// 256-bit halves preserves lane order and lets each half select its available ISA.
return Vector512.Create(Vector256_.SubtractSaturate(left.GetLower(), right.GetLower()), Vector256_.SubtractSaturate(left.GetUpper(), right.GetUpper()));
}
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector512{Single}"/>.
/// </summary>
@ -163,16 +67,4 @@ internal static class Vector512_
Vector512<float> vm0,
Vector512<float> vm1)
=> Avx512F.FusedMultiplyAddNegated(vm0, vm1, va);
/// <summary>
/// Restricts a vector between a minimum and a maximum value.
/// </summary>
/// <typeparam name="T">The type of the elements in the vector.</typeparam>
/// <param name="value">The vector to restrict.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The restricted <see cref="Vector512{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<T> Clamp<T>(Vector512<T> value, Vector512<T> min, Vector512<T> max)
=> Vector512.Min(Vector512.Max(value, min), max);
}

4
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector128.cs

@ -72,7 +72,7 @@ internal partial struct Block8x8F
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector128<float> NormalizeVector128(Vector128<float> value, Vector128<float> off, Vector128<float> max)
=> Vector128_.Clamp(value + off, Vector128<float>.Zero, max);
=> Vector128.Clamp(value + off, Vector128<float>.Zero, max);
private static void MultiplyIntoInt16Vector128(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest)
{
@ -87,7 +87,7 @@ internal partial struct Block8x8F
Vector128<int> left = Vector128_.ConvertToInt32RoundToEven(Unsafe.Add(ref aBase, i + 0) * Unsafe.Add(ref bBase, i + 0));
Vector128<int> right = Vector128_.ConvertToInt32RoundToEven(Unsafe.Add(ref aBase, i + 1) * Unsafe.Add(ref bBase, i + 1));
Unsafe.Add(ref destBase, i / 2) = Vector128_.PackSignedSaturate(left, right);
Unsafe.Add(ref destBase, i / 2) = Vector128.NarrowWithSaturation(left, right);
}
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Vector256.cs

@ -96,7 +96,7 @@ internal partial struct Block8x8F
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector256<float> NormalizeVector256(Vector256<float> value, Vector256<float> off, Vector256<float> max)
=> Vector256_.Clamp(value + off, Vector256<float>.Zero, max);
=> Vector256.Clamp(value + off, Vector256<float>.Zero, max);
private static void MultiplyIntoInt16Vector256(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest)
{

6
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs

@ -86,7 +86,7 @@ internal abstract partial class JpegColorConverterBase
public static void ConvertFromRgb(Vector128<float> r, Vector128<float> g, Vector128<float> b, Vector128<float> maximumValue, Vector128<float> halfValue, Vector128<float> scale, out Vector128<float> c0, out Vector128<float> c1, out Vector128<float> c2, out Vector128<float> c3)
{
// The nested estimate gives each pixel the same multiply-add grouping as the scalar Rec.601 formula.
c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b));
c0 = Vector128.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b));
c1 = default;
c2 = default;
c3 = default;
@ -97,7 +97,7 @@ internal abstract partial class JpegColorConverterBase
public static void ConvertFromRgb(Vector256<float> r, Vector256<float> g, Vector256<float> b, Vector256<float> maximumValue, Vector256<float> halfValue, Vector256<float> scale, out Vector256<float> c0, out Vector256<float> c1, out Vector256<float> c2, out Vector256<float> c3)
{
// YMM lanes evaluate the same Rec.601 equation independently, with no horizontal lane reduction.
c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b));
c0 = Vector256.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b));
c1 = default;
c2 = default;
c3 = default;
@ -108,7 +108,7 @@ internal abstract partial class JpegColorConverterBase
public static void ConvertFromRgb(Vector512<float> r, Vector512<float> g, Vector512<float> b, Vector512<float> maximumValue, Vector512<float> halfValue, Vector512<float> scale, out Vector512<float> c0, out Vector512<float> c1, out Vector512<float> c2, out Vector512<float> c3)
{
// ZMM lanes retain the same arithmetic order as narrower paths so only SIMD width changes.
c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b));
c0 = Vector512.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b));
c1 = default;
c2 = default;
c3 = default;

36
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs

@ -48,9 +48,9 @@ internal abstract partial class JpegColorConverterBase
Vector128<float> k = Vector128<float>.One - (c3 * scale);
// Four lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y) * k;
c0 = Vector128.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector128.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector128.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y) * k;
}
/// <inheritdoc/>
@ -63,9 +63,9 @@ internal abstract partial class JpegColorConverterBase
Vector256<float> k = Vector256<float>.One - (c3 * scale);
// Eight lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y) * k;
c0 = Vector256.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector256.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector256.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y) * k;
}
/// <inheritdoc/>
@ -78,9 +78,9 @@ internal abstract partial class JpegColorConverterBase
Vector512<float> k = Vector512<float>.One - (c3 * scale);
// Sixteen lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation.
c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y) * k;
c0 = Vector512.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y) * k;
c1 = Vector512.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)) * k;
c2 = Vector512.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y) * k;
}
/// <inheritdoc/>
@ -129,9 +129,9 @@ internal abstract partial class JpegColorConverterBase
r = (r * divisor) & nonBlack;
g = (g * divisor) & nonBlack;
b = (b * divisor) & nonBlack;
c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)) * maximumValue;
c1 = halfValue + (Vector128_.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)) * maximumValue);
c2 = halfValue + (Vector128_.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)) * maximumValue);
c0 = Vector128.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b)) * maximumValue;
c1 = halfValue + (Vector128.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b)) * maximumValue);
c2 = halfValue + (Vector128.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b)) * maximumValue);
c3 = k * maximumValue;
}
@ -151,9 +151,9 @@ internal abstract partial class JpegColorConverterBase
r = (r * divisor) & nonBlack;
g = (g * divisor) & nonBlack;
b = (b * divisor) & nonBlack;
c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)) * maximumValue;
c1 = halfValue + (Vector256_.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)) * maximumValue);
c2 = halfValue + (Vector256_.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)) * maximumValue);
c0 = Vector256.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b)) * maximumValue;
c1 = halfValue + (Vector256.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b)) * maximumValue);
c2 = halfValue + (Vector256.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b)) * maximumValue);
c3 = k * maximumValue;
}
@ -173,9 +173,9 @@ internal abstract partial class JpegColorConverterBase
r = (r * divisor) & nonBlack;
g = (g * divisor) & nonBlack;
b = (b * divisor) & nonBlack;
c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)) * maximumValue;
c1 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)) * maximumValue);
c2 = halfValue + (Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)) * maximumValue);
c0 = Vector512.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b)) * maximumValue;
c1 = halfValue + (Vector512.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b)) * maximumValue);
c2 = halfValue + (Vector512.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b)) * maximumValue);
c3 = k * maximumValue;
}
}

54
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs

@ -68,13 +68,13 @@ internal abstract partial class JpegColorConverterBase
// Lanes are four independent Y/Cb/Cr samples. MultiplyAddEstimate maps to FMA where available:
// R uses Cr, B uses Cb, and G subtracts both chroma contributions. Rounding occurs in the sample
// domain before the common normalization scale so all precisions use integer JPEG sample semantics.
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(RCrMult), y);
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-GCbMult), y));
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(BCbMult), y);
Vector128<float> r = Vector128.MultiplyAddEstimate(cr, Vector128.Create(RCrMult), y);
Vector128<float> g = Vector128.MultiplyAddEstimate(cr, Vector128.Create(-GCrMult), Vector128.MultiplyAddEstimate(cb, Vector128.Create(-GCbMult), y));
Vector128<float> b = Vector128.MultiplyAddEstimate(cb, Vector128.Create(BCbMult), y);
c0 = Vector128_.RoundToNearestInteger(r) * scale;
c1 = Vector128_.RoundToNearestInteger(g) * scale;
c2 = Vector128_.RoundToNearestInteger(b) * scale;
c0 = Vector128.Round(r) * scale;
c1 = Vector128.Round(g) * scale;
c2 = Vector128.Round(b) * scale;
}
/// <inheritdoc/>
@ -88,13 +88,13 @@ internal abstract partial class JpegColorConverterBase
// These eight lanes have the same layout and BT.601 arithmetic as the Vector128 overload.
// Keeping an explicit overload allows the JIT to emit native YMM operations without a width
// switch or decomposing the vector into smaller values.
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(RCrMult), y);
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-GCbMult), y));
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(BCbMult), y);
Vector256<float> r = Vector256.MultiplyAddEstimate(cr, Vector256.Create(RCrMult), y);
Vector256<float> g = Vector256.MultiplyAddEstimate(cr, Vector256.Create(-GCrMult), Vector256.MultiplyAddEstimate(cb, Vector256.Create(-GCbMult), y));
Vector256<float> b = Vector256.MultiplyAddEstimate(cb, Vector256.Create(BCbMult), y);
c0 = Vector256_.RoundToNearestInteger(r) * scale;
c1 = Vector256_.RoundToNearestInteger(g) * scale;
c2 = Vector256_.RoundToNearestInteger(b) * scale;
c0 = Vector256.Round(r) * scale;
c1 = Vector256.Round(g) * scale;
c2 = Vector256.Round(b) * scale;
}
/// <inheritdoc/>
@ -108,13 +108,13 @@ internal abstract partial class JpegColorConverterBase
// Sixteen independent samples occupy the ZMM lanes. The explicit constants are broadcasts;
// assembly inspection verifies the JIT hoists them from the loop and retains fused operations.
// The formula and rounding order remain identical to the narrower overloads.
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(RCrMult), y);
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-GCbMult), y));
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(BCbMult), y);
Vector512<float> r = Vector512.MultiplyAddEstimate(cr, Vector512.Create(RCrMult), y);
Vector512<float> g = Vector512.MultiplyAddEstimate(cr, Vector512.Create(-GCrMult), Vector512.MultiplyAddEstimate(cb, Vector512.Create(-GCbMult), y));
Vector512<float> b = Vector512.MultiplyAddEstimate(cb, Vector512.Create(BCbMult), y);
c0 = Vector512_.RoundToNearestInteger(r) * scale;
c1 = Vector512_.RoundToNearestInteger(g) * scale;
c2 = Vector512_.RoundToNearestInteger(b) * scale;
c0 = Vector512.Round(r) * scale;
c1 = Vector512.Round(g) * scale;
c2 = Vector512.Round(b) * scale;
}
/// <inheritdoc/>
@ -137,9 +137,9 @@ internal abstract partial class JpegColorConverterBase
// Each vector holds four consecutive values from one RGB plane. The nested multiply-add sequence
// produces four Y lanes, four Cb lanes, and four Cr lanes without transposition. The association
// exposes two FMA opportunities per output while preserving the scalar formula's term grouping.
c0 = Vector128_.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b));
c1 = halfValue + Vector128_.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b));
c2 = halfValue + Vector128_.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128_.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b));
c0 = Vector128.MultiplyAddEstimate(Vector128.Create(0.299F), r, Vector128.MultiplyAddEstimate(Vector128.Create(0.587F), g, Vector128.Create(0.114F) * b));
c1 = halfValue + Vector128.MultiplyAddEstimate(Vector128.Create(-0.168736F), r, Vector128.MultiplyAddEstimate(Vector128.Create(-0.331264F), g, Vector128.Create(0.5F) * b));
c2 = halfValue + Vector128.MultiplyAddEstimate(Vector128.Create(0.5F), r, Vector128.MultiplyAddEstimate(Vector128.Create(-0.418688F), g, Vector128.Create(-0.081312F) * b));
c3 = default;
}
@ -149,9 +149,9 @@ internal abstract partial class JpegColorConverterBase
{
// Eight planar RGB samples use the identical association as Vector128, allowing direct YMM FMA
// generation while preserving the component-per-vector output layout.
c0 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b));
c1 = halfValue + Vector256_.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b));
c2 = halfValue + Vector256_.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256_.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b));
c0 = Vector256.MultiplyAddEstimate(Vector256.Create(0.299F), r, Vector256.MultiplyAddEstimate(Vector256.Create(0.587F), g, Vector256.Create(0.114F) * b));
c1 = halfValue + Vector256.MultiplyAddEstimate(Vector256.Create(-0.168736F), r, Vector256.MultiplyAddEstimate(Vector256.Create(-0.331264F), g, Vector256.Create(0.5F) * b));
c2 = halfValue + Vector256.MultiplyAddEstimate(Vector256.Create(0.5F), r, Vector256.MultiplyAddEstimate(Vector256.Create(-0.418688F), g, Vector256.Create(-0.081312F) * b));
c3 = default;
}
@ -161,9 +161,9 @@ internal abstract partial class JpegColorConverterBase
{
// Sixteen planar RGB samples use the same nested form. Constants are lane broadcasts and c3 is
// deliberately zero because the shared traversal removes the unused fourth store for this operator.
c0 = Vector512_.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b));
c1 = halfValue + Vector512_.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b));
c2 = halfValue + Vector512_.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512_.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b));
c0 = Vector512.MultiplyAddEstimate(Vector512.Create(0.299F), r, Vector512.MultiplyAddEstimate(Vector512.Create(0.587F), g, Vector512.Create(0.114F) * b));
c1 = halfValue + Vector512.MultiplyAddEstimate(Vector512.Create(-0.168736F), r, Vector512.MultiplyAddEstimate(Vector512.Create(-0.331264F), g, Vector512.Create(0.5F) * b));
c2 = halfValue + Vector512.MultiplyAddEstimate(Vector512.Create(0.5F), r, Vector512.MultiplyAddEstimate(Vector512.Create(-0.418688F), g, Vector512.Create(-0.081312F) * b));
c3 = default;
}
}

36
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs

@ -46,12 +46,12 @@ internal abstract partial class JpegColorConverterBase
Vector128<float> scaledK = c3 * scale * scale;
// Four lanes reconstruct YCbCr concurrently; each rounded result is inverted and modulated by its K lane.
Vector128<float> r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y);
Vector128<float> g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y));
Vector128<float> b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector128_.RoundToNearestInteger(r)) * scaledK;
c1 = (maximumValue - Vector128_.RoundToNearestInteger(g)) * scaledK;
c2 = (maximumValue - Vector128_.RoundToNearestInteger(b)) * scaledK;
Vector128<float> r = Vector128.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y);
Vector128<float> g = Vector128.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y));
Vector128<float> b = Vector128.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector128.Round(r)) * scaledK;
c1 = (maximumValue - Vector128.Round(g)) * scaledK;
c2 = (maximumValue - Vector128.Round(b)) * scaledK;
}
/// <inheritdoc/>
@ -64,12 +64,12 @@ internal abstract partial class JpegColorConverterBase
Vector256<float> scaledK = c3 * scale * scale;
// Eight lanes retain planar alignment from Y/Cb/Cr/K through normalized RGB.
Vector256<float> r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y);
Vector256<float> g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y));
Vector256<float> b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector256_.RoundToNearestInteger(r)) * scaledK;
c1 = (maximumValue - Vector256_.RoundToNearestInteger(g)) * scaledK;
c2 = (maximumValue - Vector256_.RoundToNearestInteger(b)) * scaledK;
Vector256<float> r = Vector256.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y);
Vector256<float> g = Vector256.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y));
Vector256<float> b = Vector256.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector256.Round(r)) * scaledK;
c1 = (maximumValue - Vector256.Round(g)) * scaledK;
c2 = (maximumValue - Vector256.Round(b)) * scaledK;
}
/// <inheritdoc/>
@ -82,12 +82,12 @@ internal abstract partial class JpegColorConverterBase
Vector512<float> scaledK = c3 * scale * scale;
// Sixteen lanes use the same matrix, rounding, inversion, and K modulation order as scalar code.
Vector512<float> r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y);
Vector512<float> g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y));
Vector512<float> b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector512_.RoundToNearestInteger(r)) * scaledK;
c1 = (maximumValue - Vector512_.RoundToNearestInteger(g)) * scaledK;
c2 = (maximumValue - Vector512_.RoundToNearestInteger(b)) * scaledK;
Vector512<float> r = Vector512.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y);
Vector512<float> g = Vector512.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y));
Vector512<float> b = Vector512.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y);
c0 = (maximumValue - Vector512.Round(r)) * scaledK;
c1 = (maximumValue - Vector512.Round(g)) * scaledK;
c2 = (maximumValue - Vector512.Round(b)) * scaledK;
}
/// <inheritdoc/>

3
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.Icc.cs

@ -4,6 +4,7 @@
using System.Buffers;
using System.Numerics;
using System.Numerics.Tensors;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
@ -42,7 +43,7 @@ internal abstract partial class JpegColorConverterBase
{
// The single luminance plane is the ICC source, so it is normalized in place. The temporary
// buffer is still RGB-sized because the profile conversion expands each Y sample to three lanes.
TensorPrimitives_.Multiply(c0, 1F / this.MaximumValue, c0);
TensorPrimitives.Multiply(c0, 1F / this.MaximumValue, c0);
Span<Y> source = MemoryMarshal.Cast<float, Y>(c0);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);

5
src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@ -118,7 +119,7 @@ internal class ComponentProcessor : IDisposable
static void SumVertical(Span<float> target, Span<float> source)
// Exact destination overlap is supported, so each accumulated row remains in target.
=> TensorPrimitives_.Add(target, source, target);
=> TensorPrimitives.Add(target, source, target);
static void SumHorizontal(Span<float> target, int factor)
{
@ -168,6 +169,6 @@ internal class ComponentProcessor : IDisposable
static void MultiplyToAverage(Span<float> target, float multiplier)
// Apply the subsampling reciprocal in place after all contributing rows have been summed.
=> TensorPrimitives_.Multiply(target, multiplier, target);
=> TensorPrimitives.Multiply(target, multiplier, target);
}
}

8
src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Vector256.cs

@ -55,8 +55,8 @@ internal static partial class FloatingPointDCT
tmp12 = tmp6 + tmp7;
Vector256<float> z5 = (tmp10 - tmp12) * Vector256.Create(0.382683433f); // mm256_F_0_3826
Vector256<float> z2 = Vector256_.MultiplyAddEstimate(Vector256.Create(0.541196100f), tmp10, z5); // mm256_F_0_5411
Vector256<float> z4 = Vector256_.MultiplyAddEstimate(Vector256.Create(1.306562965f), tmp12, z5); // mm256_F_1_3065
Vector256<float> z2 = Vector256.MultiplyAddEstimate(Vector256.Create(0.541196100f), tmp10, z5); // mm256_F_0_5411
Vector256<float> z4 = Vector256.MultiplyAddEstimate(Vector256.Create(1.306562965f), tmp12, z5); // mm256_F_1_3065
Vector256<float> z3 = tmp11 * mm256_F_0_7071;
Vector256<float> z11 = tmp7 + z3;
@ -122,8 +122,8 @@ internal static partial class FloatingPointDCT
z5 = (z10 + z12) * Vector256.Create(1.847759065f); // mm256_F_1_8477
tmp10 = Vector256_.MultiplyAddEstimate(z12, Vector256.Create(-1.082392200f), z5); // mm256_F_n1_0823
tmp12 = Vector256_.MultiplyAddEstimate(z10, Vector256.Create(-2.613125930f), z5); // mm256_F_n2_6131
tmp10 = Vector256.MultiplyAddEstimate(z12, Vector256.Create(-1.082392200f), z5); // mm256_F_n1_0823
tmp12 = Vector256.MultiplyAddEstimate(z10, Vector256.Create(-2.613125930f), z5); // mm256_F_n2_6131
tmp6 = tmp12 - tmp7;
tmp5 = tmp11 - tmp6;

30
src/ImageSharp/Formats/Png/Filters/IPngFilterOperator.cs

@ -286,10 +286,10 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator
// distanceAbove = |left - upperLeft|
// Computing both unsigned subtraction directions and OR-ing them obtains
// each absolute difference without widening the byte lanes.
Vector128<byte> aboveMinusUpper = Vector128_.SubtractSaturate(above, upperLeft);
Vector128<byte> leftMinusUpper = Vector128_.SubtractSaturate(left, upperLeft);
Vector128<byte> distanceLeft = Vector128_.SubtractSaturate(upperLeft, above) | aboveMinusUpper;
Vector128<byte> distanceAbove = Vector128_.SubtractSaturate(upperLeft, left) | leftMinusUpper;
Vector128<byte> aboveMinusUpper = Vector128.SubtractSaturate(above, upperLeft);
Vector128<byte> leftMinusUpper = Vector128.SubtractSaturate(left, upperLeft);
Vector128<byte> distanceLeft = Vector128.SubtractSaturate(upperLeft, above) | aboveMinusUpper;
Vector128<byte> distanceAbove = Vector128.SubtractSaturate(upperLeft, left) | leftMinusUpper;
return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove);
}
@ -307,10 +307,10 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator
// Apply the same Paeth identities as the 128-bit path to thirty-two lanes.
// Saturating subtraction in both directions forms the absolute differences
// without widening, preserving one predictor result per source byte.
Vector256<byte> aboveMinusUpper = Vector256_.SubtractSaturate(above, upperLeft);
Vector256<byte> leftMinusUpper = Vector256_.SubtractSaturate(left, upperLeft);
Vector256<byte> distanceLeft = Vector256_.SubtractSaturate(upperLeft, above) | aboveMinusUpper;
Vector256<byte> distanceAbove = Vector256_.SubtractSaturate(upperLeft, left) | leftMinusUpper;
Vector256<byte> aboveMinusUpper = Vector256.SubtractSaturate(above, upperLeft);
Vector256<byte> leftMinusUpper = Vector256.SubtractSaturate(left, upperLeft);
Vector256<byte> distanceLeft = Vector256.SubtractSaturate(upperLeft, above) | aboveMinusUpper;
Vector256<byte> distanceAbove = Vector256.SubtractSaturate(upperLeft, left) | leftMinusUpper;
return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove);
}
@ -328,10 +328,10 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator
// Apply the same byte-lane Paeth identities to sixty-four AVX-512BW lanes.
// No cross-lane operation is required because every component has its own
// left, above, and upper-left inputs at the matching vector index.
Vector512<byte> aboveMinusUpper = Vector512_.SubtractSaturate(above, upperLeft);
Vector512<byte> leftMinusUpper = Vector512_.SubtractSaturate(left, upperLeft);
Vector512<byte> distanceLeft = Vector512_.SubtractSaturate(upperLeft, above) | aboveMinusUpper;
Vector512<byte> distanceAbove = Vector512_.SubtractSaturate(upperLeft, left) | leftMinusUpper;
Vector512<byte> aboveMinusUpper = Vector512.SubtractSaturate(above, upperLeft);
Vector512<byte> leftMinusUpper = Vector512.SubtractSaturate(left, upperLeft);
Vector512<byte> distanceLeft = Vector512.SubtractSaturate(upperLeft, above) | aboveMinusUpper;
Vector512<byte> distanceAbove = Vector512.SubtractSaturate(upperLeft, left) | leftMinusUpper;
return SelectPredictor(left, above, upperLeft, aboveMinusUpper, leftMinusUpper, distanceLeft, distanceAbove);
}
@ -356,7 +356,7 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator
// their summed distance and cannot beat either neighbor; the all-bits mask
// excludes upper-left. On opposite sides, that distance is the absolute
// difference between distanceLeft and distanceAbove.
Vector128<byte> distanceUpper = sameDirection | Vector128_.SubtractSaturate(distanceAbove, distanceLeft) | Vector128_.SubtractSaturate(distanceLeft, distanceAbove);
Vector128<byte> distanceUpper = sameDirection | Vector128.SubtractSaturate(distanceAbove, distanceLeft) | Vector128.SubtractSaturate(distanceLeft, distanceAbove);
// Equality selects above before upper-left, implementing PNG's second tie rule.
Vector128<byte> minimumAboveUpper = Vector128.Min(distanceUpper, distanceAbove);
@ -384,7 +384,7 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator
// Exclude upper-left when its distance is the non-minimal sum; otherwise
// compute its distance as the absolute difference of the two known distances.
Vector256<byte> distanceUpper = sameDirection | Vector256_.SubtractSaturate(distanceAbove, distanceLeft) | Vector256_.SubtractSaturate(distanceLeft, distanceAbove);
Vector256<byte> distanceUpper = sameDirection | Vector256.SubtractSaturate(distanceAbove, distanceLeft) | Vector256.SubtractSaturate(distanceLeft, distanceAbove);
// Select above on equality, then select left on equality to preserve PNG's
// required left, above, upper-left tie order in every byte lane.
@ -412,7 +412,7 @@ internal readonly struct PaethFilterOperator : IPngFilterOperator
// Exclude upper-left when its distance is the non-minimal sum; otherwise
// compute its distance as the absolute difference of the two known distances.
Vector512<byte> distanceUpper = sameDirection | Vector512_.SubtractSaturate(distanceAbove, distanceLeft) | Vector512_.SubtractSaturate(distanceLeft, distanceAbove);
Vector512<byte> distanceUpper = sameDirection | Vector512.SubtractSaturate(distanceAbove, distanceLeft) | Vector512.SubtractSaturate(distanceLeft, distanceAbove);
// Select above on equality, then select left on equality to preserve PNG's
// required left, above, upper-left tie order in every byte lane.

3
src/ImageSharp/Formats/Png/Filters/UpFilter.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
@ -24,7 +25,7 @@ internal static class UpFilter
DebugGuard.MustBeSameSized<byte>(scanline, previousScanline, nameof(scanline));
// The leading filter byte is metadata; every remaining byte is the modulo-256 sum of Raw(x) and Prior(x).
TensorPrimitives_.Add(scanline[1..], previousScanline[1..], scanline[1..]);
TensorPrimitives.Add(scanline[1..], previousScanline[1..], scanline[1..]);
}
/// <summary>

4
src/ImageSharp/Formats/Png/PngCgbiProcessor.cs

@ -113,7 +113,7 @@ internal static class PngCgbiProcessor
{
ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf<Rgba32>());
Vector512<byte> bgra = Unsafe.ReadUnaligned<Vector512<byte>>(ref blockRef);
Vector512<byte> rgba = Vector512_.ShuffleNative(bgra, shuffleMask);
Vector512<byte> rgba = Vector512.ShuffleNative(bgra, shuffleMask);
Vector512<int> packed = rgba.AsInt32();
Vector512<int> alpha = Vector512.ShiftRightLogical(packed, 24);
@ -254,7 +254,7 @@ internal static class PngCgbiProcessor
{
ref byte blockRef = ref Unsafe.Add(ref scanlineRef, i * Unsafe.SizeOf<Rgba32>());
Vector128<byte> bgra = Unsafe.ReadUnaligned<Vector128<byte>>(ref blockRef);
Vector128<byte> rgba = Vector128_.ShuffleNative(bgra, shuffleMask);
Vector128<byte> rgba = Vector128.ShuffleNative(bgra, shuffleMask);
Vector128<int> packed = rgba.AsInt32();
Vector128<int> alpha = Vector128.ShiftRightLogical(packed, 24);

3
src/ImageSharp/Formats/Webp/AlphaDecoder.cs

@ -3,6 +3,7 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@ -364,7 +365,7 @@ internal class AlphaDecoder : IDisposable
else
{
// Byte addition intentionally wraps modulo 256, matching the WebP alpha predictor.
TensorPrimitives_.Add(input[..width], prev[..width], dst[..width]);
TensorPrimitives.Add(input[..width], prev[..width], dst[..width]);
}
}

4
src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs

@ -86,8 +86,8 @@ internal static class ColorSpaceTransformUtils
nuint input1Idx = x + (span / 2);
Vector128<byte> input0 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte();
Vector128<byte> input1 = Unsafe.As<uint, Vector128<uint>>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte();
Vector128<byte> r0 = Vector128_.ShuffleNative(input0, collectColorBlueTransformsShuffleLowMask);
Vector128<byte> r1 = Vector128_.ShuffleNative(input1, collectColorBlueTransformsShuffleHighMask);
Vector128<byte> r0 = Vector128.ShuffleNative(input0, collectColorBlueTransformsShuffleLowMask);
Vector128<byte> r1 = Vector128.ShuffleNative(input1, collectColorBlueTransformsShuffleHighMask);
Vector128<byte> r = r0 | r1;
Vector128<byte> gb0 = input0 & collectColorBlueTransformsGreenBlueMask;
Vector128<byte> gb1 = input1 & collectColorBlueTransformsGreenBlueMask;

16
src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs

@ -128,7 +128,7 @@ internal static unsafe class LosslessUtils
{
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte();
Vector128<byte> in0g0g = Vector128_.ShuffleNative(input, addGreenToBlueAndRedMask);
Vector128<byte> in0g0g = Vector128.ShuffleNative(input, addGreenToBlueAndRedMask);
Vector128<byte> output = input + in0g0g;
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
i += 4;
@ -192,7 +192,7 @@ internal static unsafe class LosslessUtils
{
ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i);
Vector128<byte> input = Unsafe.As<uint, Vector128<uint>>(ref pos).AsByte();
Vector128<byte> in0g0g = Vector128_.ShuffleNative(input, subtractGreenFromBlueAndRedMask);
Vector128<byte> in0g0g = Vector128.ShuffleNative(input, subtractGreenFromBlueAndRedMask);
Vector128<byte> output = input - in0g0g;
Unsafe.As<uint, Vector128<uint>>(ref pos) = output.AsUInt32();
i += 4;
@ -387,7 +387,7 @@ internal static unsafe class LosslessUtils
Vector128<short> b = Vector128_.ShuffleLow(a.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector128<short> c = Vector128_.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector128<short> d = Vector128_.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector128<short> e = Vector128_.ShiftLeftLogical(input.AsInt16(), 8);
Vector128<short> e = input.AsInt16() << 8;
Vector128<short> f = Vector128_.MultiplyHigh(e.AsInt16(), multsb2.AsInt16());
Vector128<int> g = Vector128.ShiftRightLogical(f.AsInt32(), 16);
Vector128<byte> h = g.AsByte() + d.AsByte();
@ -479,7 +479,7 @@ internal static unsafe class LosslessUtils
Vector128<short> c = Vector128_.ShuffleHigh(b.AsInt16(), SimdUtils.Shuffle.MMShuffle2200);
Vector128<short> d = Vector128_.MultiplyHigh(c.AsInt16(), multsrb.AsInt16());
Vector128<byte> e = input.AsByte() + d.AsByte();
Vector128<short> f = Vector128_.ShiftLeftLogical(e.AsInt16(), 8);
Vector128<short> f = e.AsInt16() << 8;
Vector128<short> g = Vector128_.MultiplyHigh(f, multsb2.AsInt16());
Vector128<int> h = Vector128.ShiftRightLogical(g.AsInt32(), 8);
Vector128<byte> i = h.AsByte() + f.AsByte();
@ -1442,10 +1442,10 @@ internal static unsafe class LosslessUtils
Vector128<byte> a0 = Vector128.CreateScalar(a).AsByte();
Vector128<byte> b0 = Vector128.CreateScalar(b).AsByte();
Vector128<byte> c0 = Vector128.CreateScalar(c).AsByte();
Vector128<byte> ac0 = Vector128_.SubtractSaturate(a0, c0);
Vector128<byte> ca0 = Vector128_.SubtractSaturate(c0, a0);
Vector128<byte> bc0 = Vector128_.SubtractSaturate(b0, c0);
Vector128<byte> cb0 = Vector128_.SubtractSaturate(c0, b0);
Vector128<byte> ac0 = Vector128.SubtractSaturate(a0, c0);
Vector128<byte> ca0 = Vector128.SubtractSaturate(c0, a0);
Vector128<byte> bc0 = Vector128.SubtractSaturate(b0, c0);
Vector128<byte> cb0 = Vector128.SubtractSaturate(c0, b0);
Vector128<byte> ac = ac0 | ca0;
Vector128<byte> bc = bc0 | cb0;
Vector128<byte> pa = Vector128_.UnpackLow(ac, Vector128<byte>.Zero); // |a - c|

11
src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
@ -331,7 +332,7 @@ internal abstract unsafe class Vp8LHistogram
{
if (b.IsUsed(0))
{
TensorPrimitives_.Add(this.Literal[..literalSize], b.Literal[..literalSize], output.Literal[..literalSize]);
TensorPrimitives.Add(this.Literal[..literalSize], b.Literal[..literalSize], output.Literal[..literalSize]);
}
else
{
@ -354,7 +355,7 @@ internal abstract unsafe class Vp8LHistogram
{
if (b.IsUsed(1))
{
TensorPrimitives_.Add(this.Red[..size], b.Red[..size], output.Red[..size]);
TensorPrimitives.Add(this.Red[..size], b.Red[..size], output.Red[..size]);
}
else
{
@ -377,7 +378,7 @@ internal abstract unsafe class Vp8LHistogram
{
if (b.IsUsed(2))
{
TensorPrimitives_.Add(this.Blue[..size], b.Blue[..size], output.Blue[..size]);
TensorPrimitives.Add(this.Blue[..size], b.Blue[..size], output.Blue[..size]);
}
else
{
@ -400,7 +401,7 @@ internal abstract unsafe class Vp8LHistogram
{
if (b.IsUsed(3))
{
TensorPrimitives_.Add(this.Alpha[..size], b.Alpha[..size], output.Alpha[..size]);
TensorPrimitives.Add(this.Alpha[..size], b.Alpha[..size], output.Alpha[..size]);
}
else
{
@ -423,7 +424,7 @@ internal abstract unsafe class Vp8LHistogram
{
if (b.IsUsed(4))
{
TensorPrimitives_.Add(this.Distance[..size], b.Distance[..size], output.Distance[..size]);
TensorPrimitives.Add(this.Distance[..size], b.Distance[..size], output.Distance[..size]);
}
else
{

74
src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs

@ -77,7 +77,7 @@ internal static class LossyUtils
Vector256<byte> b01s = Vector256_.UnpackLow(b01.AsByte(), Vector256<byte>.Zero);
// subtract, square and accumulate.
Vector256<short> d0 = Vector256_.SubtractSaturate(a01s.AsInt16(), b01s.AsInt16());
Vector256<short> d0 = Vector256.SubtractSaturate(a01s.AsInt16(), b01s.AsInt16());
Vector256<int> e0 = Vector256_.MultiplyAddAdjacent(d0, d0);
return ReduceSumVector256(e0);
@ -110,8 +110,8 @@ internal static class LossyUtils
Vector128<byte> b23s = Vector128_.UnpackLow(b23.AsByte(), Vector128<byte>.Zero);
// subtract, square and accumulate.
Vector128<short> d0 = Vector128_.SubtractSaturate(a01s.AsInt16(), b01s.AsInt16());
Vector128<short> d1 = Vector128_.SubtractSaturate(a23s.AsInt16(), b23s.AsInt16());
Vector128<short> d0 = Vector128.SubtractSaturate(a01s.AsInt16(), b01s.AsInt16());
Vector128<short> d1 = Vector128.SubtractSaturate(a23s.AsInt16(), b23s.AsInt16());
Vector128<int> e0 = Vector128_.MultiplyAddAdjacent(d0, d0);
Vector128<int> e1 = Vector128_.MultiplyAddAdjacent(d1, d1);
Vector128<int> sum = e0 + e1;
@ -203,8 +203,8 @@ internal static class LossyUtils
private static Vector128<int> SubtractAndAccumulateVector128(Vector128<byte> a, Vector128<byte> b)
{
// Take abs(a-b) in 8b.
Vector128<byte> ab = Vector128_.SubtractSaturate(a, b);
Vector128<byte> ba = Vector128_.SubtractSaturate(b, a);
Vector128<byte> ab = Vector128.SubtractSaturate(a, b);
Vector128<byte> ba = Vector128.SubtractSaturate(b, a);
Vector128<byte> absAb = ab | ba;
// Zero-extend to 16b.
@ -222,8 +222,8 @@ internal static class LossyUtils
private static Vector256<int> SubtractAndAccumulateVector256(Vector256<byte> a, Vector256<byte> b)
{
// Take abs(a-b) in 8b.
Vector256<byte> ab = Vector256_.SubtractSaturate(a, b);
Vector256<byte> ba = Vector256_.SubtractSaturate(b, a);
Vector256<byte> ab = Vector256.SubtractSaturate(a, b);
Vector256<byte> ba = Vector256.SubtractSaturate(b, a);
Vector256<byte> absAb = ab | ba;
// Zero-extend to 16b.
@ -1985,20 +1985,20 @@ internal static class LossyUtils
q0 ^= signBit;
q1 ^= signBit;
Vector128<sbyte> t1 = Vector128_.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1
Vector128<sbyte> t1 = Vector128.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1
t1 = (~notHev & t1.AsByte()).AsSByte(); // hev(p1 - q1)
Vector128<sbyte> t2 = Vector128_.SubtractSaturate(q0.AsSByte(), p0.AsSByte()); // q0 - p0
t1 = Vector128_.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0)
t1 = Vector128_.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0)
t1 = Vector128_.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0)
Vector128<sbyte> t2 = Vector128.SubtractSaturate(q0.AsSByte(), p0.AsSByte()); // q0 - p0
t1 = Vector128.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0)
t1 = Vector128.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0)
t1 = Vector128.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0)
t1 = (t1.AsByte() & mask).AsSByte(); // mask filter values we don't care about.
t2 = Vector128_.AddSaturate(t1, Vector128.Create((byte)3).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 3
Vector128<sbyte> t3 = Vector128_.AddSaturate(t1, Vector128.Create((byte)4).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 4
t2 = Vector128.AddSaturate(t1, Vector128.Create((byte)3).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 3
Vector128<sbyte> t3 = Vector128.AddSaturate(t1, Vector128.Create((byte)4).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 4
t2 = SignedShift8bVector128(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3
t3 = SignedShift8bVector128(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3
p0 = Vector128_.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2
q0 = Vector128_.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3
p0 = Vector128.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2
q0 = Vector128.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3
p0 ^= signBit;
q0 ^= signBit;
@ -2008,8 +2008,8 @@ internal static class LossyUtils
t3 -= Vector128.Create((sbyte)64);
t3 = (notHev & t3.AsByte()).AsSByte(); // if !hev
q1 = Vector128_.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3
p1 = Vector128_.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3
q1 = Vector128.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3
p1 = Vector128.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3
p1 = p1.AsByte() ^ signBit;
q1 = q1.AsByte() ^ signBit;
}
@ -2063,13 +2063,13 @@ internal static class LossyUtils
private static void DoSimpleFilterVector128(ref Vector128<byte> p0, ref Vector128<byte> q0, Vector128<byte> fl)
{
Vector128<sbyte> v3 = Vector128_.AddSaturate(fl.AsSByte(), Vector128.Create((byte)3).AsSByte());
Vector128<sbyte> v4 = Vector128_.AddSaturate(fl.AsSByte(), Vector128.Create((byte)4).AsSByte());
Vector128<sbyte> v3 = Vector128.AddSaturate(fl.AsSByte(), Vector128.Create((byte)3).AsSByte());
Vector128<sbyte> v4 = Vector128.AddSaturate(fl.AsSByte(), Vector128.Create((byte)4).AsSByte());
v4 = SignedShift8bVector128(v4.AsByte()).AsSByte(); // v4 >> 3
v3 = SignedShift8bVector128(v3.AsByte()).AsSByte(); // v3 >> 3
q0 = Vector128_.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4
p0 = Vector128_.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3
q0 = Vector128.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4
p0 = Vector128.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3
}
private static Vector128<byte> GetNotHevVector128(ref Vector128<byte> p1, ref Vector128<byte> p0, ref Vector128<byte> q0, ref Vector128<byte> q1, int hevThresh)
@ -2080,7 +2080,7 @@ internal static class LossyUtils
Vector128<byte> h = Vector128.Create((byte)hevThresh);
Vector128<byte> tMax = Vector128.Max(t1, t2);
Vector128<byte> tMaxH = Vector128_.SubtractSaturate(tMax, h);
Vector128<byte> tMaxH = Vector128.SubtractSaturate(tMax, h);
// not_hev <= t1 && not_hev <= t2
return Vector128.Equals(tMaxH, Vector128<byte>.Zero);
@ -2173,10 +2173,10 @@ internal static class LossyUtils
Vector128<short> t3 = Vector128.ShiftRightLogical(t2.AsInt16(), 1); // abs(p1 - q1) / 2
Vector128<byte> t4 = AbsVector128(p0, q0); // abs(p0 - q0)
Vector128<byte> t5 = Vector128_.AddSaturate(t4, t4); // abs(p0 - q0) * 2
Vector128<byte> t6 = Vector128_.AddSaturate(t5.AsByte(), t3.AsByte()); // abs(p0-q0)*2 + abs(p1-q1)/2
Vector128<byte> t5 = Vector128.AddSaturate(t4, t4); // abs(p0 - q0) * 2
Vector128<byte> t6 = Vector128.AddSaturate(t5.AsByte(), t3.AsByte()); // abs(p0-q0)*2 + abs(p1-q1)/2
Vector128<byte> t7 = Vector128_.SubtractSaturate(t6, mthresh.AsByte()); // mask <= m_thresh
Vector128<byte> t7 = Vector128.SubtractSaturate(t6, mthresh.AsByte()); // mask <= m_thresh
return Vector128.Equals(t7, Vector128<byte>.Zero);
}
@ -2290,11 +2290,11 @@ internal static class LossyUtils
private static Vector128<sbyte> GetBaseDeltaVector128(Vector128<sbyte> p1, Vector128<sbyte> p0, Vector128<sbyte> q0, Vector128<sbyte> q1)
{
// Beware of addition order, for saturation!
Vector128<sbyte> p1q1 = Vector128_.SubtractSaturate(p1, q1); // p1 - q1
Vector128<sbyte> q0p0 = Vector128_.SubtractSaturate(q0, p0); // q0 - p0
Vector128<sbyte> s1 = Vector128_.AddSaturate(p1q1, q0p0); // p1 - q1 + 1 * (q0 - p0)
Vector128<sbyte> s2 = Vector128_.AddSaturate(q0p0, s1); // p1 - q1 + 2 * (q0 - p0)
return Vector128_.AddSaturate(q0p0, s2); // p1 - q1 + 3 * (q0 - p0)
Vector128<sbyte> p1q1 = Vector128.SubtractSaturate(p1, q1); // p1 - q1
Vector128<sbyte> q0p0 = Vector128.SubtractSaturate(q0, p0); // q0 - p0
Vector128<sbyte> s1 = Vector128.AddSaturate(p1q1, q0p0); // p1 - q1 + 1 * (q0 - p0)
Vector128<sbyte> s2 = Vector128.AddSaturate(q0p0, s1); // p1 - q1 + 2 * (q0 - p0)
return Vector128.AddSaturate(q0p0, s2); // p1 - q1 + 3 * (q0 - p0)
}
// Shift each byte of "x" by 3 bits while preserving by the sign bit.
@ -2306,14 +2306,14 @@ internal static class LossyUtils
Vector128<short> low1 = Vector128.ShiftRightArithmetic(low0.AsInt16(), 3 + 8);
Vector128<short> high1 = Vector128.ShiftRightArithmetic(high0.AsInt16(), 3 + 8);
return Vector128_.PackSignedSaturate(low1, high1);
return Vector128.NarrowWithSaturation(low1, high1);
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void ComplexMaskVector128(Vector128<byte> p1, Vector128<byte> p0, Vector128<byte> q0, Vector128<byte> q1, int thresh, int ithresh, ref Vector128<byte> mask)
{
Vector128<byte> it = Vector128.Create((byte)ithresh);
Vector128<byte> diff = Vector128_.SubtractSaturate(mask, it);
Vector128<byte> diff = Vector128.SubtractSaturate(mask, it);
Vector128<byte> threshMask = Vector128.Equals(diff, Vector128<byte>.Zero);
Vector128<byte> filterMask = NeedsFilterVector128(p1, p0, q0, q1, thresh);
@ -2329,9 +2329,9 @@ internal static class LossyUtils
Vector128<byte> signBit = Vector128.Create((byte)0x80);
Vector128<short> a1Low = Vector128.ShiftRightArithmetic(a0Low, 7);
Vector128<short> a1High = Vector128.ShiftRightArithmetic(a0High, 7);
Vector128<sbyte> delta = Vector128_.PackSignedSaturate(a1Low, a1High);
pi = Vector128_.AddSaturate(pi.AsSByte(), delta).AsByte();
qi = Vector128_.SubtractSaturate(qi.AsSByte(), delta).AsByte();
Vector128<sbyte> delta = Vector128.NarrowWithSaturation(a1Low, a1High);
pi = Vector128.AddSaturate(pi.AsSByte(), delta).AsByte();
qi = Vector128.SubtractSaturate(qi.AsSByte(), delta).AsByte();
pi ^= signBit.AsByte();
qi ^= signBit.AsByte();
}
@ -2354,7 +2354,7 @@ internal static class LossyUtils
// Compute abs(p - q) = subs(p - q) OR subs(q - p)
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector128<byte> AbsVector128(Vector128<byte> p, Vector128<byte> q)
=> Vector128_.SubtractSaturate(q, p) | Vector128_.SubtractSaturate(p, q);
=> Vector128.SubtractSaturate(q, p) | Vector128.SubtractSaturate(p, q);
[MethodImpl(InliningOptions.ShortMethod)]
private static bool Hev(ReadOnlySpan<byte> p, int offset, int step, int thresh)

8
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs

@ -542,8 +542,8 @@ internal static unsafe class Vp8Encoding
Vector128<int> tmp32 = tmp31 + Vector128.Create(937);
Vector128<int> tmp1 = Vector128.ShiftRightArithmetic(tmp12, 9);
Vector128<int> tmp3 = Vector128.ShiftRightArithmetic(tmp32, 9);
Vector128<short> s03 = Vector128_.PackSignedSaturate(tmp0, tmp2);
Vector128<short> s12 = Vector128_.PackSignedSaturate(tmp1, tmp3);
Vector128<short> s03 = Vector128.NarrowWithSaturation(tmp0, tmp2);
Vector128<short> s12 = Vector128.NarrowWithSaturation(tmp1, tmp3);
Vector128<short> slo = Vector128_.UnpackLow(s03, s12); // 0 1 0 1 0 1...
Vector128<short> shi = Vector128_.UnpackHigh(s03, s12); // 2 3 2 3 2 3
Vector128<int> v23 = Vector128_.UnpackHigh(slo.AsInt32(), shi.AsInt32());
@ -569,8 +569,8 @@ internal static unsafe class Vp8Encoding
// f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16)
// f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16)
Vector128<short> f1 = Vector128_.PackSignedSaturate(e1, e1);
Vector128<short> f3 = Vector128_.PackSignedSaturate(e3, e3);
Vector128<short> f1 = Vector128.NarrowWithSaturation(e1, e1);
Vector128<short> f3 = Vector128.NarrowWithSaturation(e3, e3);
// g1 = f1 + (a3 != 0);
// The compare will return (0xffff, 0) for (==0, !=0). To turn that into the

16
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs

@ -686,12 +686,12 @@ internal static class YuvConversion
out Vector128<byte> output4,
out Vector128<byte> output5)
{
output0 = Vector128_.ShuffleNative(input0, shuffle0);
output1 = Vector128_.ShuffleNative(input0, shuffle1);
output2 = Vector128_.ShuffleNative(input0, shuffle2);
output3 = Vector128_.ShuffleNative(input1, shuffle0);
output4 = Vector128_.ShuffleNative(input1, shuffle1);
output5 = Vector128_.ShuffleNative(input1, shuffle2);
output0 = Vector128.ShuffleNative(input0, shuffle0);
output1 = Vector128.ShuffleNative(input0, shuffle1);
output2 = Vector128.ShuffleNative(input0, shuffle2);
output3 = Vector128.ShuffleNative(input1, shuffle0);
output4 = Vector128.ShuffleNative(input1, shuffle1);
output5 = Vector128.ShuffleNative(input1, shuffle2);
}
// Convert 32 samples of YUV444 to B/G/R
@ -726,8 +726,8 @@ internal static class YuvConversion
Vector128<ushort> g4 = g2 - g3;
Vector128<ushort> b0 = Vector128_.MultiplyHigh(u0.AsUInt16(), Vector128.Create(26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129).AsUInt16());
Vector128<ushort> b1 = Vector128_.AddSaturate(b0, y1);
Vector128<ushort> b2 = Vector128_.SubtractSaturate(b1, Vector128.Create((ushort)17685));
Vector128<ushort> b1 = Vector128.AddSaturate(b0, y1);
Vector128<ushort> b2 = Vector128.SubtractSaturate(b1, Vector128.Create((ushort)17685));
// Use logical shift for B2, which can be larger than 32767.
r = Vector128.ShiftRightArithmetic(r2.AsInt16(), 6); // range: [-14234, 30815]

10
src/ImageSharp/Formats/Webp/WebpCommonUtils.cs

@ -134,9 +134,9 @@ internal static class WebpCommonUtils
Vector128<int> b1 = (a1 & alphaMask).AsInt32();
Vector128<int> b2 = (a2 & alphaMask).AsInt32();
Vector128<int> b3 = (a3 & alphaMask).AsInt32();
Vector128<short> c0 = Vector128_.PackSignedSaturate(b0, b1).AsInt16();
Vector128<short> c1 = Vector128_.PackSignedSaturate(b2, b3).AsInt16();
Vector128<byte> d = Vector128_.PackSignedSaturate(c0, c1).AsByte();
Vector128<short> c0 = Vector128.NarrowWithSaturation(b0, b1).AsInt16();
Vector128<short> c1 = Vector128.NarrowWithSaturation(b2, b3).AsInt16();
Vector128<byte> d = Vector128.NarrowWithSaturation(c0, c1).AsByte();
Vector128<byte> bits = Vector128.Equals(d, Vector128.Create((byte)0x80).AsByte());
uint mask = bits.ExtractMostSignificantBits();
return mask != 0xFFFF;
@ -150,8 +150,8 @@ internal static class WebpCommonUtils
Vector128<byte> a1 = Vector128.Load(src + i + 16).AsByte();
Vector128<int> b0 = (a0 & alphaMask).AsInt32();
Vector128<int> b1 = (a1 & alphaMask).AsInt32();
Vector128<short> c = Vector128_.PackSignedSaturate(b0, b1).AsInt16();
Vector128<byte> d = Vector128_.PackSignedSaturate(c, c).AsByte();
Vector128<short> c = Vector128.NarrowWithSaturation(b0, b1).AsInt16();
Vector128<byte> d = Vector128.NarrowWithSaturation(c, c).AsByte();
Vector128<byte> bits = Vector128.Equals(d, Vector128.Create((byte)0x80).AsByte());
uint mask = bits.ExtractMostSignificantBits();
return mask != 0xFFFF;

3
src/ImageSharp/ImageSharp.csproj

@ -47,7 +47,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
<PackageReference Include="System.IO.Hashing" Version="10.0.11" />
<PackageReference Include="System.Numerics.Tensors" Version="10.0.11" />
</ItemGroup>
<ItemGroup>

12
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs

@ -848,7 +848,7 @@ internal static partial class AssociatedAlphaPorterDuffFunctions
Vector4 sourceAlpha = Numerics.PermuteW(source);
Vector4 destinationAlpha = Numerics.PermuteW(destination);
Vector4 coefficient = Vector4.One - sourceAlpha;
Vector4 result = Vector128_.FusedMultiplyAdd(destination.AsVector128(), coefficient.AsVector128(), overlap.AsVector128()).AsVector4();
Vector4 result = Vector128.FusedMultiplyAdd(destination.AsVector128(), coefficient.AsVector128(), overlap.AsVector128()).AsVector4();
return Numerics.WithW(result, destinationAlpha);
}
@ -859,7 +859,7 @@ internal static partial class AssociatedAlphaPorterDuffFunctions
{
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl);
Vector256<float> result = Vector256_.FusedMultiplyAdd(destination, Vector256.Create(1F) - sourceAlpha, overlap);
Vector256<float> result = Vector256.FusedMultiplyAdd(destination, Vector256.Create(1F) - sourceAlpha, overlap);
return Avx.Blend(result, destinationAlpha, BlendAlphaControl);
}
@ -870,7 +870,7 @@ internal static partial class AssociatedAlphaPorterDuffFunctions
{
Vector512<float> sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
Vector512<float> result = Vector512_.FusedMultiplyAdd(destination, Vector512.Create(1F) - sourceAlpha, overlap);
Vector512<float> result = Vector512.FusedMultiplyAdd(destination, Vector512.Create(1F) - sourceAlpha, overlap);
return Vector512.ConditionalSelect(AlphaMask512(), destinationAlpha, result);
}
@ -959,18 +959,18 @@ internal static partial class AssociatedAlphaPorterDuffFunctions
public static Vector4 BlendWithCoverage(Vector4 backdrop, Vector4 source, float coverage)
{
// Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths.
return Vector128_.FusedMultiplyAdd((source - backdrop).AsVector128(), Vector128.Create(coverage), backdrop.AsVector128()).AsVector4();
return Vector128.FusedMultiplyAdd((source - backdrop).AsVector128(), Vector128.Create(coverage), backdrop.AsVector128()).AsVector4();
}
/// <inheritdoc cref="BlendWithCoverage(Vector4, Vector4, float)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> BlendWithCoverage(Vector256<float> backdrop, Vector256<float> source, Vector256<float> coverage)
=> Vector256_.FusedMultiplyAdd(source - backdrop, coverage, backdrop);
=> Vector256.FusedMultiplyAdd(source - backdrop, coverage, backdrop);
/// <inheritdoc cref="BlendWithCoverage(Vector4, Vector4, float)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> BlendWithCoverage(Vector512<float> backdrop, Vector512<float> source, Vector512<float> coverage)
=> Vector512_.FusedMultiplyAdd(source - backdrop, coverage, backdrop);
=> Vector512.FusedMultiplyAdd(source - backdrop, coverage, backdrop);
/// <summary>
/// Calculates one associated Overlay overlap component without recovering either straight component.

26
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -586,7 +586,7 @@ internal static partial class PorterDuffFunctions
Vector4 sourcePremultiplied = Numerics.WithW(source * sourceAlpha, sourceAlpha);
// Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths.
Vector4 result = Vector128_.MultiplyAddEstimate((sourcePremultiplied - backdropPremultiplied).AsVector128(), Vector128.Create(coverage), backdropPremultiplied.AsVector128()).AsVector4();
Vector4 result = Vector128.MultiplyAddEstimate((sourcePremultiplied - backdropPremultiplied).AsVector128(), Vector128.Create(coverage), backdropPremultiplied.AsVector128()).AsVector4();
Numerics.UnPremultiply(ref result);
return result;
@ -606,7 +606,7 @@ internal static partial class PorterDuffFunctions
Vector256<float> sourceAlpha = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> backdropPremultiplied = Avx.Blend(backdrop * backdropAlpha, backdropAlpha, BlendAlphaControl);
Vector256<float> sourcePremultiplied = Avx.Blend(source * sourceAlpha, sourceAlpha, BlendAlphaControl);
Vector256<float> result = Vector256_.MultiplyAddEstimate(sourcePremultiplied - backdropPremultiplied, coverage, backdropPremultiplied);
Vector256<float> result = Vector256.MultiplyAddEstimate(sourcePremultiplied - backdropPremultiplied, coverage, backdropPremultiplied);
return Numerics.UnPremultiply(result, Avx.Permute(result, ShuffleAlphaControl));
}
@ -626,7 +626,7 @@ internal static partial class PorterDuffFunctions
Vector512<float> alphaMask = AlphaMask512();
Vector512<float> backdropPremultiplied = Vector512.ConditionalSelect(alphaMask, backdropAlpha, backdrop * backdropAlpha);
Vector512<float> sourcePremultiplied = Vector512.ConditionalSelect(alphaMask, sourceAlpha, source * sourceAlpha);
Vector512<float> result = Vector512_.MultiplyAddEstimate(sourcePremultiplied - backdropPremultiplied, coverage, backdropPremultiplied);
Vector512<float> result = Vector512.MultiplyAddEstimate(sourcePremultiplied - backdropPremultiplied, coverage, backdropPremultiplied);
return Numerics.UnPremultiply(result, Vector512_.ShuffleNative(result, ShuffleAlphaControl));
}
@ -836,8 +836,8 @@ internal static partial class PorterDuffFunctions
// calculate final color
Vector256<float> color = destination * dstW;
color = Vector256_.MultiplyAddEstimate(source, srcW, color);
color = Vector256_.MultiplyAddEstimate(blend, blendW, color);
color = Vector256.MultiplyAddEstimate(source, srcW, color);
color = Vector256.MultiplyAddEstimate(blend, blendW, color);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -866,8 +866,8 @@ internal static partial class PorterDuffFunctions
// calculate final color
Vector512<float> color = destination * dstW;
color = Vector512_.MultiplyAddEstimate(source, srcW, color);
color = Vector512_.MultiplyAddEstimate(blend, blendW, color);
color = Vector512.MultiplyAddEstimate(source, srcW, color);
color = Vector512.MultiplyAddEstimate(blend, blendW, color);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -920,7 +920,7 @@ internal static partial class PorterDuffFunctions
Vector256<float> dstW = alpha - blendW;
// calculate final color
Vector256<float> color = Vector256_.MultiplyAddEstimate(destination, dstW, Avx.Multiply(blend, blendW));
Vector256<float> color = Vector256.MultiplyAddEstimate(destination, dstW, Avx.Multiply(blend, blendW));
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -945,7 +945,7 @@ internal static partial class PorterDuffFunctions
Vector512<float> dstW = alpha - blendW;
// calculate final color
Vector512<float> color = Vector512_.MultiplyAddEstimate(destination, dstW, blend * blendW);
Vector512<float> color = Vector512.MultiplyAddEstimate(destination, dstW, blend * blendW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -1104,8 +1104,8 @@ internal static partial class PorterDuffFunctions
Vector256<float> dstW = vOne - sW;
// calculate alpha
Vector256<float> alpha = Vector256_.MultiplyAddEstimate(sW, srcW, Avx.Multiply(dW, dstW));
Vector256<float> color = Vector256_.MultiplyAddEstimate(Avx.Multiply(sW, source), srcW, Avx.Multiply(Avx.Multiply(dW, destination), dstW));
Vector256<float> alpha = Vector256.MultiplyAddEstimate(sW, srcW, Avx.Multiply(dW, dstW));
Vector256<float> color = Vector256.MultiplyAddEstimate(Avx.Multiply(sW, source), srcW, Avx.Multiply(Avx.Multiply(dW, destination), dstW));
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -1129,8 +1129,8 @@ internal static partial class PorterDuffFunctions
Vector512<float> dstW = vOne - sW;
// calculate alpha
Vector512<float> alpha = Vector512_.MultiplyAddEstimate(sW, srcW, dW * dstW);
Vector512<float> color = Vector512_.MultiplyAddEstimate(sW * source, srcW, (dW * destination) * dstW);
Vector512<float> alpha = Vector512.MultiplyAddEstimate(sW, srcW, dW * dstW);
Vector512<float> color = Vector512.MultiplyAddEstimate(sW * source, srcW, (dW * destination) * dstW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);

22
src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs

@ -451,7 +451,7 @@ public partial struct NormalizedByte4P
source = Vector512.Min(Vector512.Max(source, zero), one);
Vector512<float> alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11);
Vector512<float> nativeAlpha = Vector512.Min(Vector512.Max((alpha * Vector512.Create(2F)) - one, -one), one);
Vector512<float> storedAlpha = Vector512_.RoundToNearestInteger(nativeAlpha * Vector512.Create(MaxPos));
Vector512<float> storedAlpha = Vector512.Round(nativeAlpha * Vector512.Create(MaxPos));
storedAlpha += Vector512.Create(MaxPos);
storedAlpha /= Vector512.Create(ScaledMagnitude);
Vector512<float> result = source * storedAlpha;
@ -480,7 +480,7 @@ public partial struct NormalizedByte4P
source = Vector256.Min(Vector256.Max(source, zero), one);
Vector256<float> alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11);
Vector256<float> nativeAlpha = Vector256.Min(Vector256.Max((alpha * Vector256.Create(2F)) - one, -one), one);
Vector256<float> storedAlpha = Vector256_.RoundToNearestInteger(nativeAlpha * Vector256.Create(MaxPos));
Vector256<float> storedAlpha = Vector256.Round(nativeAlpha * Vector256.Create(MaxPos));
storedAlpha += Vector256.Create(MaxPos);
storedAlpha /= Vector256.Create(ScaledMagnitude);
Vector256<float> result = source * storedAlpha;
@ -509,7 +509,7 @@ public partial struct NormalizedByte4P
source = Vector128.Min(Vector128.Max(source, zero), one);
Vector128<float> alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11);
Vector128<float> nativeAlpha = Vector128.Min(Vector128.Max((alpha * Vector128.Create(2F)) - one, -one), one);
Vector128<float> storedAlpha = Vector128_.RoundToNearestInteger(nativeAlpha * Vector128.Create(MaxPos));
Vector128<float> storedAlpha = Vector128.Round(nativeAlpha * Vector128.Create(MaxPos));
storedAlpha += Vector128.Create(MaxPos);
storedAlpha /= Vector128.Create(ScaledMagnitude);
Vector128<float> result = source * storedAlpha;
@ -610,7 +610,7 @@ public partial struct NormalizedByte4P
Vector512<float> alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11);
Vector512<float> nativeAlpha = Vector512.Min(Vector512.Max((alpha * Vector512.Create(2F)) - one, -one), one);
Vector512<float> storedAlpha = Vector512_.RoundToNearestInteger(nativeAlpha * Vector512.Create(MaxPos));
Vector512<float> storedAlpha = Vector512.Round(nativeAlpha * Vector512.Create(MaxPos));
storedAlpha += Vector512.Create(MaxPos);
storedAlpha /= Vector512.Create(ScaledMagnitude);
Vector512<float> result = source * (storedAlpha / alpha);
@ -640,7 +640,7 @@ public partial struct NormalizedByte4P
Vector256<float> alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11);
Vector256<float> nativeAlpha = Vector256.Min(Vector256.Max((alpha * Vector256.Create(2F)) - one, -one), one);
Vector256<float> storedAlpha = Vector256_.RoundToNearestInteger(nativeAlpha * Vector256.Create(MaxPos));
Vector256<float> storedAlpha = Vector256.Round(nativeAlpha * Vector256.Create(MaxPos));
storedAlpha += Vector256.Create(MaxPos);
storedAlpha /= Vector256.Create(ScaledMagnitude);
Vector256<float> result = source * (storedAlpha / alpha);
@ -670,7 +670,7 @@ public partial struct NormalizedByte4P
Vector128<float> alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11);
Vector128<float> nativeAlpha = Vector128.Min(Vector128.Max((alpha * Vector128.Create(2F)) - one, -one), one);
Vector128<float> storedAlpha = Vector128_.RoundToNearestInteger(nativeAlpha * Vector128.Create(MaxPos));
Vector128<float> storedAlpha = Vector128.Round(nativeAlpha * Vector128.Create(MaxPos));
storedAlpha += Vector128.Create(MaxPos);
storedAlpha /= Vector128.Create(ScaledMagnitude);
Vector128<float> result = source * (storedAlpha / alpha);
@ -701,7 +701,7 @@ public partial struct NormalizedByte4P
Vector512<float> vector = Unsafe.As<Vector4, Vector512<float>>(ref Unsafe.Add(ref sourceBase, (uint)i));
Vector512<int> integers = ConvertToPackedInt32(vector, scaled);
Vector256<short> shorts = Vector256_.PackSignedSaturate(integers.GetLower(), integers.GetUpper());
Vector128<sbyte> packed = Vector128_.PackSignedSaturate(shorts.GetLower(), shorts.GetUpper());
Vector128<sbyte> packed = Vector128.NarrowWithSaturation(shorts.GetLower(), shorts.GetUpper());
if (Avx2.IsSupported)
{
@ -721,8 +721,8 @@ public partial struct NormalizedByte4P
{
Vector256<float> vector = Unsafe.As<Vector4, Vector256<float>>(ref Unsafe.Add(ref sourceBase, (uint)i));
Vector256<int> integers = ConvertToPackedInt32(vector, scaled);
Vector128<short> shorts = Vector128_.PackSignedSaturate(integers.GetLower(), integers.GetUpper());
Vector128<sbyte> packed = Vector128_.PackSignedSaturate(shorts, shorts);
Vector128<short> shorts = Vector128.NarrowWithSaturation(integers.GetLower(), integers.GetUpper());
Vector128<sbyte> packed = Vector128.NarrowWithSaturation(shorts, shorts);
Unsafe.As<NormalizedByte4P, ulong>(ref Unsafe.Add(ref destinationBase, (uint)i)) = packed.AsUInt64().GetElement(0);
}
}
@ -733,8 +733,8 @@ public partial struct NormalizedByte4P
{
Vector128<float> vector = Unsafe.As<Vector4, Vector128<float>>(ref Unsafe.Add(ref sourceBase, (uint)i));
Vector128<int> integers = ConvertToPackedInt32(vector, scaled);
Vector128<short> shorts = Vector128_.PackSignedSaturate(integers, integers);
Vector128<sbyte> packed = Vector128_.PackSignedSaturate(shorts, shorts);
Vector128<short> shorts = Vector128.NarrowWithSaturation(integers, integers);
Vector128<sbyte> packed = Vector128.NarrowWithSaturation(shorts, shorts);
Unsafe.Add(ref destinationBase, (uint)i).PackedValue = packed.AsUInt32().GetElement(0);
}

4
src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs

@ -149,7 +149,7 @@ internal static class SignedShort4PixelOperations
{
Vector256<float> vectors = Unsafe.As<Vector4, Vector256<float>>(ref Unsafe.Add(ref sourceBase, (uint)index));
Vector256<int> integers = ConvertToInt32(vectors, normalized, scaled);
Vector128<short> packed = Vector128_.PackSignedSaturate(integers.GetLower(), integers.GetUpper());
Vector128<short> packed = Vector128.NarrowWithSaturation(integers.GetLower(), integers.GetUpper());
Unsafe.As<short, Vector128<short>>(ref Unsafe.Add(ref destinationBase, (uint)(index * Vector128<float>.Count))) = packed;
}
}
@ -162,7 +162,7 @@ internal static class SignedShort4PixelOperations
{
Vector128<float> vector = Unsafe.As<Vector4, Vector128<float>>(ref Unsafe.Add(ref sourceBase, (uint)index));
Vector128<int> integers = ConvertToInt32(vector, normalized, scaled);
Vector128<short> packed = Vector128_.PackSignedSaturate(integers, integers);
Vector128<short> packed = Vector128.NarrowWithSaturation(integers, integers);
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationBytes, (uint)(index * sizeof(ulong))), packed.AsUInt64().GetElement(0));
}

7
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Numerics.Tensors;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution;
@ -38,7 +39,7 @@ internal static class ConvolutionProcessorHelpers
}
// Divide every weight by the accumulated Gaussian sum so the kernel has unit response.
TensorPrimitives_.Divide<float>(kernel, sum, kernel);
TensorPrimitives.Divide(kernel, sum, kernel);
return kernel;
}
@ -67,14 +68,14 @@ internal static class ConvolutionProcessorHelpers
// Invert the kernel for sharpening.
int midpointRounded = (int)midpoint;
float midpointValue = kernel[midpointRounded];
TensorPrimitives_.Negate<float>(kernel, kernel);
TensorPrimitives.Negate(kernel, kernel);
// The sharpening kernel negates every Gaussian weight except its center. Restore that original
// center while adding twice the Gaussian sum so the complete kernel retains unit response.
kernel[midpointRounded] = (2F * sum) - midpointValue;
// Sharpening changes signs but preserves the Gaussian sum, so the same divisor produces unit response.
TensorPrimitives_.Divide<float>(kernel, sum, kernel);
TensorPrimitives.Divide(kernel, sum, kernel);
return kernel;
}

3
src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs

@ -3,6 +3,7 @@
using System.Collections.Concurrent;
using System.Numerics;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
@ -214,7 +215,7 @@ internal static class BokehBlurKernelDataProvider
// Complex64 stores each value as adjacent real and imaginary floats. Multiplying that flattened
// float span scales both parts independently, which is exactly complex multiplication by a real scalar.
Span<float> values = MemoryMarshal.Cast<Complex64, float>(kernelsRef.AsSpan());
TensorPrimitives_.Multiply(values, scalar, values);
TensorPrimitives.Multiply(values, scalar, values);
}
}
}

3
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
@ -116,7 +117,7 @@ internal abstract class HistogramEqualizationProcessor<TPixel> : ImageProcessor<
int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0;
if (addToEachBin > 0)
{
TensorPrimitives_.Add(histogram, addToEachBin, histogram);
TensorPrimitives.Add(histogram, addToEachBin, histogram);
}
int residual = sumOverClip - (addToEachBin * this.LuminanceLevels);

8
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs

@ -104,8 +104,8 @@ internal readonly unsafe struct ResizeKernel
Vector256<float> pixels256_0 = Unsafe.As<Vector4, Vector256<float>>(ref rowStartRef);
Vector256<float> pixels256_1 = Unsafe.As<Vector4, Vector256<float>>(ref Unsafe.Add(ref rowStartRef, (nuint)2));
result256_0 = Vector256_.MultiplyAddEstimate(Vector256.Load(bufferStart), pixels256_0, result256_0);
result256_1 = Vector256_.MultiplyAddEstimate(Vector256.Load(bufferStart + 8), pixels256_1, result256_1);
result256_0 = Vector256.MultiplyAddEstimate(Vector256.Load(bufferStart), pixels256_0, result256_0);
result256_1 = Vector256.MultiplyAddEstimate(Vector256.Load(bufferStart + 8), pixels256_1, result256_1);
bufferStart += 16;
rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)4);
@ -116,7 +116,7 @@ internal readonly unsafe struct ResizeKernel
if ((this.Length & 3) >= 2)
{
Vector256<float> pixels256_0 = Unsafe.As<Vector4, Vector256<float>>(ref rowStartRef);
result256_0 = Vector256_.MultiplyAddEstimate(Vector256.Load(bufferStart), pixels256_0, result256_0);
result256_0 = Vector256.MultiplyAddEstimate(Vector256.Load(bufferStart), pixels256_0, result256_0);
bufferStart += 8;
rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)2);
@ -127,7 +127,7 @@ internal readonly unsafe struct ResizeKernel
if ((this.Length & 1) != 0)
{
Vector128<float> pixels128 = Unsafe.As<Vector4, Vector128<float>>(ref rowStartRef);
result128 = Vector128_.MultiplyAddEstimate(Vector128.Load(bufferStart), pixels128, result128);
result128 = Vector128.MultiplyAddEstimate(Vector128.Load(bufferStart), pixels128, result128);
}
return result128.AsVector4();

57
tests/ImageSharp.Tests/Common/NumericsTests.cs

@ -5,12 +5,42 @@ namespace SixLabors.ImageSharp.Tests.Common;
public class NumericsTests
{
private static readonly int[] NormalizeSpanLengthValues =
[
0,
1,
3,
4,
5,
7,
8,
9,
15,
16,
17,
31,
32,
33,
63,
64,
65,
127,
128,
129,
2048
];
private ITestOutputHelper Output { get; }
public NumericsTests(ITestOutputHelper output) => this.Output = output;
public static TheoryData<int> IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue };
/// <summary>
/// Gets lengths that exercise scalar execution and the supported SIMD widths.
/// </summary>
public static TheoryData<int> NormalizeSpanLengths => new(NormalizeSpanLengthValues);
private static uint DivideCeil_ReferenceImplementation(uint value, uint divisor) => (uint)MathF.Ceiling((float)value / divisor);
[Fact]
@ -53,4 +83,31 @@ public class NumericsTests
Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})");
}
/// <summary>
/// Verifies that normalization divides every element by the supplied sum.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(NormalizeSpanLengths))]
public void NormalizeMatchesScalarFormula(int length)
{
float[] actual = new float[length];
float[] expected = new float[length];
for (int i = 0; i < actual.Length; i++)
{
actual[i] = (i + 1) * 0.125F;
expected[i] = actual[i] / 7.5F;
}
Numerics.Normalize(actual, 7.5F);
Assert.Equal(expected.Length, actual.Length);
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(BitConverter.SingleToInt32Bits(expected[i]), BitConverter.SingleToInt32Bits(actual[i]));
}
}
}

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

@ -151,9 +151,9 @@ public partial class SimdUtilsTests
private static void RunVectorMultiplyAddUsesRuntimeOrderAndFusedContract()
{
Assert.Equal(Vector128.Create(11F), Vector128_.MultiplyAddEstimate(Vector128.Create(2F), Vector128.Create(3F), Vector128.Create(5F)));
Assert.Equal(Vector256.Create(11F), Vector256_.MultiplyAddEstimate(Vector256.Create(2F), Vector256.Create(3F), Vector256.Create(5F)));
Assert.Equal(Vector512.Create(11F), Vector512_.MultiplyAddEstimate(Vector512.Create(2F), Vector512.Create(3F), Vector512.Create(5F)));
Assert.Equal(Vector128.Create(11F), Vector128.MultiplyAddEstimate(Vector128.Create(2F), Vector128.Create(3F), Vector128.Create(5F)));
Assert.Equal(Vector256.Create(11F), Vector256.MultiplyAddEstimate(Vector256.Create(2F), Vector256.Create(3F), Vector256.Create(5F)));
Assert.Equal(Vector512.Create(11F), Vector512.MultiplyAddEstimate(Vector512.Create(2F), Vector512.Create(3F), Vector512.Create(5F)));
// These associated-alpha components produce an exact midpoint that a separate multiply and add rounds incorrectly.
float left = (68F / byte.MaxValue) * .625F;
@ -161,9 +161,9 @@ public partial class SimdUtilsTests
float addend = ((50F / byte.MaxValue) + (50F / byte.MaxValue)) * left;
float expected = MathF.FusedMultiplyAdd(left, right, addend);
Assert.Equal(Vector128.Create(expected), Vector128_.FusedMultiplyAdd(Vector128.Create(left), Vector128.Create(right), Vector128.Create(addend)));
Assert.Equal(Vector256.Create(expected), Vector256_.FusedMultiplyAdd(Vector256.Create(left), Vector256.Create(right), Vector256.Create(addend)));
Assert.Equal(Vector512.Create(expected), Vector512_.FusedMultiplyAdd(Vector512.Create(left), Vector512.Create(right), Vector512.Create(addend)));
Assert.Equal(Vector128.Create(expected), Vector128.FusedMultiplyAdd(Vector128.Create(left), Vector128.Create(right), Vector128.Create(addend)));
Assert.Equal(Vector256.Create(expected), Vector256.FusedMultiplyAdd(Vector256.Create(left), Vector256.Create(right), Vector256.Create(addend)));
Assert.Equal(Vector512.Create(expected), Vector512.FusedMultiplyAdd(Vector512.Create(left), Vector512.Create(right), Vector512.Create(addend)));
}
private static void TestImpl_BulkConvertByteToNormalizedFloat(

580
tests/ImageSharp.Tests/Common/TensorPrimitivesTests.cs

@ -1,580 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Common;
public class TensorPrimitivesTests
{
private static readonly int[] SpanLengthValues =
[
0,
1,
3,
4,
5,
7,
8,
9,
15,
16,
17,
31,
32,
33,
63,
64,
65,
127,
128,
129,
2048
];
/// <summary>
/// Gets lengths that exercise scalar execution, every SIMD width, overlapping tails, and the unrolled loop.
/// </summary>
public static TheoryData<int> SpanLengths => new(SpanLengthValues);
/// <summary>
/// Verifies every compatibility operation while forcing the supported SIMD feature tiers in isolated processes.
/// </summary>
[Fact]
public void OperationsMatchScalarFormulasAcrossHardwareIntrinsicFeatures()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunOperationsAcrossHardwareIntrinsicFeatures,
HwIntrinsics.AllowAll
| HwIntrinsics.DisableAVX512F
| HwIntrinsics.DisableAVX
| HwIntrinsics.DisableArm64Sve
| HwIntrinsics.DisableHWIntrinsic);
/// <summary>
/// Verifies that span-to-span operations require equal input lengths before accessing either input.
/// </summary>
[Fact]
public void AddSpanSpanRejectsMismatchedInputLengths()
{
ArgumentException exception = Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Add<int>(new int[4], new int[3], new int[4]));
Assert.Null(exception.ParamName);
}
/// <summary>
/// Verifies that every operation rejects a destination that cannot hold all input elements.
/// </summary>
[Fact]
public void OperationsRejectShortDestinations()
{
int[] integers = new int[4];
float[] singles = new float[4];
Assert.Equal("destination", Assert.Throws<ArgumentException>(() => TensorPrimitives_.Add<int>(integers, integers, new int[3])).ParamName);
Assert.Equal("destination", Assert.Throws<ArgumentException>(() => TensorPrimitives_.Add<int>(integers, 1, new int[3])).ParamName);
Assert.Equal("destination", Assert.Throws<ArgumentException>(() => TensorPrimitives_.Multiply<float>(singles, 2F, new float[3])).ParamName);
Assert.Equal("destination", Assert.Throws<ArgumentException>(() => TensorPrimitives_.Divide<float>(singles, 2F, new float[3])).ParamName);
Assert.Equal("destination", Assert.Throws<ArgumentException>(() => TensorPrimitives_.Max<float>(singles, 2F, new float[3])).ParamName);
Assert.Equal("destination", Assert.Throws<ArgumentException>(() => TensorPrimitives_.Clamp<float>(singles, 0F, 1F, new float[3])).ParamName);
Assert.Equal("destination", Assert.Throws<ArgumentException>(() => TensorPrimitives_.Negate<float>(singles, new float[3])).ParamName);
}
/// <summary>
/// Verifies that every operation rejects shifted input and destination overlap.
/// </summary>
[Fact]
public void OperationsRejectShiftedOverlap()
{
int[] integers = new int[5];
int[] separateIntegers = new int[4];
float[] singles = new float[5];
// Both inputs need independent validation because either one may alias a shifted destination.
Assert.Equal(
"destination",
Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Add<int>(integers.AsSpan(0, 4), separateIntegers, integers.AsSpan(1, 4))).ParamName);
Assert.Equal(
"destination",
Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Add<int>(separateIntegers, integers.AsSpan(0, 4), integers.AsSpan(1, 4))).ParamName);
Assert.Equal(
"destination",
Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Add<int>(integers.AsSpan(0, 4), 1, integers.AsSpan(1, 4))).ParamName);
Assert.Equal(
"destination",
Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Multiply<float>(singles.AsSpan(0, 4), 2F, singles.AsSpan(1, 4))).ParamName);
Assert.Equal(
"destination",
Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Divide<float>(singles.AsSpan(0, 4), 2F, singles.AsSpan(1, 4))).ParamName);
Assert.Equal(
"destination",
Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Max<float>(singles.AsSpan(0, 4), 2F, singles.AsSpan(1, 4))).ParamName);
Assert.Equal(
"destination",
Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Clamp<float>(singles.AsSpan(0, 4), 0F, 1F, singles.AsSpan(1, 4))).ParamName);
Assert.Equal(
"destination",
Assert.Throws<ArgumentException>(
() => TensorPrimitives_.Negate<float>(singles.AsSpan(0, 4), singles.AsSpan(1, 4))).ParamName);
}
/// <summary>
/// Runs the TensorPrimitives compatibility assertions inside a process configured for one hardware-intrinsic tier.
/// </summary>
private static void RunOperationsAcrossHardwareIntrinsicFeatures()
{
TensorPrimitivesTests tests = new();
// Reuse the focused assertions so the remote feature matrix cannot drift from the normal test coverage.
foreach (int length in SpanLengthValues)
{
tests.AddByteMatchesScalarFormula(length);
tests.AddUInt32MatchesScalarFormula(length);
tests.AddScalarInt32MatchesScalarFormula(length);
tests.NegateSingleMatchesScalarFormula(length);
tests.NegateDoubleMatchesScalarFormula(length);
tests.ClampInt32MatchesScalarFormula(length);
tests.ClampSingleMatchesRuntimeFormula(length);
tests.DivideSingleMatchesScalarFormula(length);
tests.MaxSingleMatchesRuntimeFormula(length);
tests.MultiplySingleMatchesScalarFormula(length);
tests.NormalizeMatchesScalarFormula(length);
}
tests.ClampSinglePreservesRuntimeSpecialValueSemantics();
tests.ClampDoublePreservesRuntimeSpecialValueSemantics();
}
/// <summary>
/// Verifies that byte addition wraps modulo 256 and supports either input as the in-place destination.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void AddByteMatchesScalarFormula(int length)
{
byte[] x = new byte[length];
byte[] y = new byte[length];
byte[] expected = new byte[length];
for (int i = 0; i < length; i++)
{
x[i] = (byte)((i * 23) + 197);
y[i] = (byte)((i * 41) + 113);
expected[i] = unchecked((byte)(x[i] + y[i]));
}
byte[] destination = new byte[length];
TensorPrimitives_.Add<byte>(x, y, destination);
Assert.Equal(expected, destination);
byte[] xInPlace = (byte[])x.Clone();
TensorPrimitives_.Add<byte>(xInPlace, y, xInPlace);
Assert.Equal(expected, xInPlace);
byte[] yInPlace = (byte[])y.Clone();
TensorPrimitives_.Add<byte>(x, yInPlace, yInPlace);
Assert.Equal(expected, yInPlace);
}
/// <summary>
/// Verifies that unsigned integer addition preserves unchecked histogram accumulation semantics.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void AddUInt32MatchesScalarFormula(int length)
{
uint[] x = new uint[length];
uint[] y = new uint[length];
uint[] expected = new uint[length];
for (int i = 0; i < length; i++)
{
x[i] = ((uint)i * 1_234_567U) + 0xF0000000U;
y[i] = ((uint)i * 7_654_321U) + 0x30000000U;
expected[i] = unchecked(x[i] + y[i]);
}
TensorPrimitives_.Add<uint>(x, y, x);
Assert.Equal(expected, x);
}
/// <summary>
/// Verifies that scalar integer addition produces identical results for separate and in-place destinations.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void AddScalarInt32MatchesScalarFormula(int length)
{
int[] source = new int[length];
int[] expected = new int[length];
const int addend = 17;
for (int i = 0; i < length; i++)
{
source[i] = (i * 37) - 200;
expected[i] = source[i] + addend;
}
int[] destination = new int[length];
TensorPrimitives_.Add(source, addend, destination);
Assert.Equal(expected, destination);
int[] inPlace = (int[])source.Clone();
TensorPrimitives_.Add(inPlace, addend, inPlace);
Assert.Equal(expected, inPlace);
}
/// <summary>
/// Verifies that floating-point negation preserves the scalar operator's exact bit-level behavior.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void NegateSingleMatchesScalarFormula(int length)
{
float[] values =
[
float.NaN,
-0F,
0F,
-1F,
1F,
float.NegativeInfinity,
float.PositiveInfinity
];
float[] source = new float[length];
float[] expected = new float[length];
for (int i = 0; i < source.Length; i++)
{
source[i] = values[i % values.Length];
expected[i] = -source[i];
}
float[] destination = new float[length];
TensorPrimitives_.Negate<float>(source, destination);
AssertSingleBitsEqual(expected, destination);
TensorPrimitives_.Negate<float>(source, source);
AssertSingleBitsEqual(expected, source);
}
/// <summary>
/// Verifies that double-precision negation preserves the scalar operator's exact bit-level behavior.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void NegateDoubleMatchesScalarFormula(int length)
{
double[] values =
[
double.NaN,
-0D,
0D,
-1D,
1D,
double.NegativeInfinity,
double.PositiveInfinity
];
double[] source = new double[length];
double[] expected = new double[length];
for (int i = 0; i < source.Length; i++)
{
source[i] = values[i % values.Length];
expected[i] = -source[i];
}
double[] destination = new double[length];
TensorPrimitives_.Negate<double>(source, destination);
AssertDoubleBitsEqual(expected, destination);
TensorPrimitives_.Negate<double>(source, source);
AssertDoubleBitsEqual(expected, source);
}
/// <summary>
/// Verifies that integer clamping produces identical results for separate and in-place destinations.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void ClampInt32MatchesScalarFormula(int length)
{
int[] source = new int[length];
int[] expected = new int[length];
for (int i = 0; i < source.Length; i++)
{
source[i] = ((i * 37) % 401) - 200;
expected[i] = Math.Clamp(source[i], -73, 91);
}
int[] destination = new int[length];
TensorPrimitives_.Clamp<int>(source, -73, 91, destination);
Assert.Equal(expected, destination);
int[] inPlace = (int[])source.Clone();
TensorPrimitives_.Clamp<int>(inPlace, -73, 91, inPlace);
Assert.Equal(expected, inPlace);
}
/// <summary>
/// Verifies that floating-point clamping matches the runtime tensor formula for special values and unordered bounds.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void ClampSingleMatchesRuntimeFormula(int length)
{
float[] values =
[
float.NaN,
-0F,
0F,
-1F,
1F,
float.NegativeInfinity,
float.PositiveInfinity
];
float[] source = new float[length];
float[] expected = new float[length];
for (int i = 0; i < source.Length; i++)
{
source[i] = values[i % values.Length];
// Runtime main follows Min(Max(x, min), max) for vectorizable types, including unordered bounds.
expected[i] = float.Min(float.Max(source[i], 2F), -2F);
}
TensorPrimitives_.Clamp<float>(source, 2F, -2F, source);
AssertSingleBitsEqual(expected, source);
}
/// <summary>
/// Verifies that single-precision clamping preserves the runtime's signed-zero and NaN behavior.
/// </summary>
[Fact]
public void ClampSinglePreservesRuntimeSpecialValueSemantics()
{
float[] values =
[
float.NaN,
float.NegativeInfinity,
-0F,
0F,
float.PositiveInfinity
];
float[] actual = new float[129];
float[] expected = new float[actual.Length];
for (int i = 0; i < actual.Length; i++)
{
actual[i] = values[i % values.Length];
expected[i] = float.Min(float.Max(actual[i], -0F), 0F);
}
TensorPrimitives_.Clamp<float>(actual, -0F, 0F, actual);
AssertSingleBitsEqual(expected, actual);
}
/// <summary>
/// Verifies that double-precision clamping preserves the runtime's signed-zero and NaN behavior.
/// </summary>
[Fact]
public void ClampDoublePreservesRuntimeSpecialValueSemantics()
{
double[] values =
[
double.NaN,
double.NegativeInfinity,
-0D,
0D,
double.PositiveInfinity
];
double[] actual = new double[65];
double[] expected = new double[actual.Length];
for (int i = 0; i < actual.Length; i++)
{
actual[i] = values[i % values.Length];
expected[i] = double.Min(double.Max(actual[i], -0D), 0D);
}
TensorPrimitives_.Clamp<double>(actual, -0D, 0D, actual);
AssertDoubleBitsEqual(expected, actual);
}
/// <summary>
/// Verifies that division produces identical results for separate and in-place destinations.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void DivideSingleMatchesScalarFormula(int length)
{
float[] source = new float[length];
float[] expected = new float[length];
for (int i = 0; i < source.Length; i++)
{
source[i] = (i - 65.25F) * 1.75F;
expected[i] = source[i] / 3.25F;
}
float[] destination = new float[length];
TensorPrimitives_.Divide<float>(source, 3.25F, destination);
AssertSingleBitsEqual(expected, destination);
float[] inPlace = (float[])source.Clone();
TensorPrimitives_.Divide<float>(inPlace, 3.25F, inPlace);
AssertSingleBitsEqual(expected, inPlace);
}
/// <summary>
/// Verifies that maximum selection preserves the runtime's NaN and signed-zero semantics.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void MaxSingleMatchesRuntimeFormula(int length)
{
float[] values =
[
float.NaN,
float.NegativeInfinity,
-1F,
-0F,
0F,
1F,
float.PositiveInfinity
];
float[] actual = new float[length];
float[] expected = new float[length];
for (int i = 0; i < length; i++)
{
actual[i] = values[i % values.Length];
expected[i] = float.Max(actual[i], -0F);
}
TensorPrimitives_.Max<float>(actual, -0F, actual);
AssertSingleBitsEqual(expected, actual);
}
/// <summary>
/// Verifies that multiplication produces identical results for separate and in-place destinations.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void MultiplySingleMatchesScalarFormula(int length)
{
float[] source = new float[length];
float[] expected = new float[length];
for (int i = 0; i < source.Length; i++)
{
source[i] = (i - 65.25F) * 1.75F;
expected[i] = source[i] * 0.375F;
}
float[] destination = new float[length];
TensorPrimitives_.Multiply<float>(source, 0.375F, destination);
AssertSingleBitsEqual(expected, destination);
TensorPrimitives_.Multiply<float>(source, 0.375F, source);
AssertSingleBitsEqual(expected, source);
}
/// <summary>
/// Verifies that the normalization compatibility call preserves its element-wise division contract.
/// </summary>
/// <param name="length">The input length.</param>
[Theory]
[MemberData(nameof(SpanLengths))]
public void NormalizeMatchesScalarFormula(int length)
{
float[] actual = new float[length];
float[] expected = new float[length];
for (int i = 0; i < actual.Length; i++)
{
actual[i] = (i + 1) * 0.125F;
expected[i] = actual[i] / 7.5F;
}
Numerics.Normalize(actual, 7.5F);
AssertSingleBitsEqual(expected, actual);
}
/// <summary>
/// Compares floating-point results while preserving signed-zero behavior.
/// </summary>
/// <param name="expected">The expected values.</param>
/// <param name="actual">The actual values.</param>
private static void AssertSingleBitsEqual(ReadOnlySpan<float> expected, ReadOnlySpan<float> actual)
{
Assert.Equal(expected.Length, actual.Length);
for (int i = 0; i < expected.Length; i++)
{
if (float.IsNaN(expected[i]))
{
Assert.True(float.IsNaN(actual[i]));
}
else
{
Assert.Equal(BitConverter.SingleToInt32Bits(expected[i]), BitConverter.SingleToInt32Bits(actual[i]));
}
}
}
/// <summary>
/// Compares double-precision results while preserving signed-zero behavior.
/// </summary>
/// <param name="expected">The expected values.</param>
/// <param name="actual">The actual values.</param>
private static void AssertDoubleBitsEqual(ReadOnlySpan<double> expected, ReadOnlySpan<double> actual)
{
Assert.Equal(expected.Length, actual.Length);
for (int i = 0; i < expected.Length; i++)
{
if (double.IsNaN(expected[i]))
{
Assert.True(double.IsNaN(actual[i]));
}
else
{
Assert.Equal(BitConverter.DoubleToInt64Bits(expected[i]), BitConverter.DoubleToInt64Bits(actual[i]));
}
}
}
}
Loading…
Cancel
Save