Browse Source

Merge pull request #3179 from SixLabors/perf/runtime-simd-apis

Use .NET 10 tensor/SIMD primitives directly
main
James Jackson-South 1 week ago
committed by GitHub
parent
commit
236dc73ab3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsIcc.cs
  2. 22
      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. 3
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  49. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  50. 1
      tests/ImageSharp.Tests/Common/NumericsTests.cs
  51. 12
      tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
  52. 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

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

@ -1,7 +1,8 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// 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.
@ -1032,13 +1033,4 @@ internal static class Numerics
public static nuint Vector512Count<TVector>(int length)
where TVector : struct
=> (uint)length / (uint)Vector512<TVector>.Count;
/// <summary>
/// Normalizes the values in a given <see cref="Span{T}"/>.
/// </summary>
/// <param name="span">The sequence of <see cref="float"/> values to normalize.</param>
/// <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);
}

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();

3
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -3,6 +3,7 @@
using System.Buffers;
using System.Diagnostics;
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
@ -243,7 +244,7 @@ internal partial class ResizeKernelMap : IDisposable
// Normalize, best to do it here rather than in the pixel loop later on.
if (sum > 0)
{
Numerics.Normalize(kernelValues, sum);
TensorPrimitives.Divide(kernelValues, sum, kernelValues);
}
kernel.FillOrCopyAndExpand(kernelValues);

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -69,7 +69,6 @@
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.Numerics.Tensors" Version="10.0.0" Condition="'$(TargetFramework)' == 'net10.0'" />
</ItemGroup>
<!-- Exclude benchmarks using internals, in case of unsigned benchmark execution: -->

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

@ -53,4 +53,5 @@ public class NumericsTests
Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})");
}
}

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