From bedf8eccac278da36e1defee3e6d794d3a0fe4e1 Mon Sep 17 00:00:00 2001
From: winscripter <142818255+winscripter@users.noreply.github.com>
Date: Fri, 4 Sep 2026 21:57:14 +0400
Subject: [PATCH] Add tests, complete splines, SIMD-accelerate transpose, add a
few SIMD methods, add SIMD-based quantization, XorShift constructor,
heuristics (incomplete), spline encoder, v256/v128 support to DCT
output/source, JxlBlending (blending.cc, blending.h)
---
src/ImageSharp/Common/Helpers/SimdUtils.cs | 39 +-
.../Common/Helpers/Vector128Utilities.cs | 40 ++
.../Common/Helpers/Vector256Utilities.cs | 40 ++
.../Formats/Jxl/Memory/JxlImage3{T}.cs | 33 ++
.../Processing/AcStrategy/JxlAcStrategyRow.cs | 10 +-
.../Blending/JxlAlphaBlendingInputLayer.cs | 10 +-
.../Blending/JxlAlphaBlendingOutput.cs | 10 +-
.../Jxl/Processing/Blending/JxlBlending.cs | 368 ++++++++++++++++
.../Jxl/Processing/Dct/JxlDctOutput.cs | 19 +
.../Jxl/Processing/Dct/JxlDctSource.cs | 21 +
.../Jxl/Processing/Encoder/Ans/JxlToken.cs | 13 +
.../Jxl/Processing/Encoder/JxlHeuristics.cs | 170 ++++++++
.../Processing/Encoder/JxlSplineEncoder.cs | 83 ++++
.../Jxl/Processing/JxlCoefficientOrder.cs | 2 +-
...JxlSimdUtils.StoreInterleaved.Generated.cs | 165 ++++----
.../JxlSimdUtils.StoreInterleaved.tt | 5 +-
.../Formats/Jxl/Processing/JxlSimdUtils.cs | 332 ++++++++++++++-
.../Formats/Jxl/Processing/JxlTranspose.cs | 113 ++++-
.../Processing/Primitives/JxlLehmerCode.cs | 14 +-
.../Jxl/Processing/Primitives/JxlXorShift.cs | 2 +-
.../Quantization/JxlQuantizerSimd.cs | 28 ++
.../Formats/Jxl/Processing/Splines/Dct32.cs | 15 +
.../Processing/Splines/JxlQuantizedSpline.cs | 4 +-
.../Jxl/Processing/Splines/JxlSpline.cs | 4 +-
.../Jxl/Processing/Splines/JxlSplineUtils.cs | 394 +++++++++++++++++
.../Jxl/Processing/Splines/JxlSplines.cs | 289 +++++++++++++
.../Formats/Jxl/Processing/AnsCommonTests.cs | 48 +++
.../Encoder/GammaCorrectionTests.cs | 37 ++
.../Encoder/Noise/PhotonNoiseTests.cs | 43 ++
.../Processing/Primitives/LehmerCodeTests.cs | 88 ++++
.../Processing/Primitives/XorShiftTests.cs | 396 ++++++++++++++++++
tests/ImageSharp.Tests/Formats/Jxl/README.md | 5 +
tests/ImageSharp.Tests/Formats/Jxl/Rng.cs | 87 ++++
33 files changed, 2816 insertions(+), 111 deletions(-)
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Blending/JxlBlending.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Encoder/Ans/JxlToken.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlHeuristics.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlSplineEncoder.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Quantization/JxlQuantizerSimd.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Splines/Dct32.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Splines/JxlSplineUtils.cs
create mode 100644 src/ImageSharp/Formats/Jxl/Processing/Splines/JxlSplines.cs
create mode 100644 tests/ImageSharp.Tests/Formats/Jxl/Processing/AnsCommonTests.cs
create mode 100644 tests/ImageSharp.Tests/Formats/Jxl/Processing/Encoder/GammaCorrectionTests.cs
create mode 100644 tests/ImageSharp.Tests/Formats/Jxl/Processing/Encoder/Noise/PhotonNoiseTests.cs
create mode 100644 tests/ImageSharp.Tests/Formats/Jxl/Processing/Primitives/LehmerCodeTests.cs
create mode 100644 tests/ImageSharp.Tests/Formats/Jxl/Processing/Primitives/XorShiftTests.cs
create mode 100644 tests/ImageSharp.Tests/Formats/Jxl/README.md
create mode 100644 tests/ImageSharp.Tests/Formats/Jxl/Rng.cs
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs
index 2b8f58b086..3e0abcd6ef 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics;
@@ -71,6 +71,43 @@ internal static partial class SimdUtils
return val_2p23_f32 | sign;
}
+ ///
+ /// Estimates the reciprocal of this vector.
+ ///
+ /// The vector to get reciprocal estimate of.
+ /// An estimated reciprocal of each element in the vector.
+ internal static Vector ReciprocalEstimate(this Vector v)
+ {
+ // TODO: System.Runtime.Intrinsics.Arm has Sve and Sve2
+ // support but is for evaluation purposes only; add SVE/SVE2
+ // support when possible
+ if (Avx512F.IsSupported && Vector.Count == 16)
+ {
+ // x86
+ return Avx512F.Reciprocal14(v.AsVector512()).AsVector();
+ }
+ else if (Avx.IsSupported && Vector.Count == 8)
+ {
+ // x86
+ return Avx.Reciprocal(v.AsVector256()).AsVector();
+ }
+ else if (AdvSimd.IsSupported && Vector.Count == 4)
+ {
+ // ARM
+ return AdvSimd.ReciprocalEstimate(v.AsVector128()).AsVector();
+ }
+ else if (Sse.IsSupported && Vector.Count == 4)
+ {
+ // x86
+ return Sse.Reciprocal(v.AsVector128()).AsVector();
+ }
+ else
+ {
+ // Exact reciprocal fallback (slower)
+ return Vector.One / v;
+ }
+ }
+
[Conditional("DEBUG")]
private static void DebugVerifySpanInput(ReadOnlySpan source, ReadOnlySpan dest, int shouldBeDivisibleBy)
{
diff --git a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs
index 6b4c6ad63c..0e9a40dd1b 100644
--- a/src/ImageSharp/Common/Helpers/Vector128Utilities.cs
+++ b/src/ImageSharp/Common/Helpers/Vector128Utilities.cs
@@ -899,4 +899,44 @@ internal static class Vector128_
return (shuffledA & maskA) | (shuffledB & maskB);
}
+
+ ///
+ /// Interleaves the lower half of the vector.
+ ///
+ /// First vector
+ /// Second vector
+ ///
+ /// { a[0], b[0], a[1], b[1] }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector128 InterleaveLower(Vector128 a, Vector128 b)
+ {
+ Vector128 shuffledA = Vector128.Shuffle(a, Vector128.Create(0, 0, 1, 1));
+ Vector128 shuffledB = Vector128.Shuffle(b, Vector128.Create(0, 0, 1, 1));
+
+ Vector128 maskA = Vector128.Create(-1, 0, -1, 0).AsSingle();
+ Vector128 maskB = Vector128.Create(0, -1, 0, -1).AsSingle();
+
+ return (shuffledA & maskA) | (shuffledB & maskB);
+ }
+
+ ///
+ /// Interleaves the upper half of the vector.
+ ///
+ /// First vector
+ /// Second vector
+ ///
+ /// { a[2], b[2], a[3], b[3] }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector128 InterleaveUpper(Vector128 a, Vector128 b)
+ {
+ Vector128 shuffledA = Vector128.Shuffle(a, Vector128.Create(2, 2, 3, 3));
+ Vector128 shuffledB = Vector128.Shuffle(b, Vector128.Create(2, 2, 3, 3));
+
+ Vector128 maskA = Vector128.Create(-1, 0, -1, 0).AsSingle();
+ Vector128 maskB = Vector128.Create(0, -1, 0, -1).AsSingle();
+
+ return (shuffledA & maskA) | (shuffledB & maskB);
+ }
}
diff --git a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs
index 4bd78b88fd..bb11108869 100644
--- a/src/ImageSharp/Common/Helpers/Vector256Utilities.cs
+++ b/src/ImageSharp/Common/Helpers/Vector256Utilities.cs
@@ -563,4 +563,44 @@ internal static class Vector256_
return (shuffledA & maskA) | (shuffledB & maskB);
}
+
+ ///
+ /// Interleaves the lower half of the vector.
+ ///
+ /// First vector
+ /// Second vector
+ ///
+ /// { a[0], b[0], a[1], b[1], a[2], b[2], a[3], b[3] }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector256 InterleaveLower(Vector256 a, Vector256 b)
+ {
+ Vector256 shuffledA = Vector256.Shuffle(a, Vector256.Create(0, 0, 1, 1, 2, 2, 3, 3));
+ Vector256 shuffledB = Vector256.Shuffle(b, Vector256.Create(0, 0, 1, 1, 2, 2, 3, 3));
+
+ Vector256 maskA = Vector256.Create(-1, 0, -1, 0, -1, 0, -1, 0).AsSingle();
+ Vector256 maskB = Vector256.Create(0, -1, 0, -1, 0, -1, 0, -1).AsSingle();
+
+ return (shuffledA & maskA) | (shuffledB & maskB);
+ }
+
+ ///
+ /// Interleaves the upper half of the vector.
+ ///
+ /// First vector
+ /// Second vector
+ ///
+ /// { a[4], b[4], a[5], b[5], a[6], b[6], a[7], b[7] }
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector256 InterleaveUpper(Vector256 a, Vector256 b)
+ {
+ Vector256 shuffledA = Vector256.Shuffle(a, Vector256.Create(4, 4, 5, 5, 6, 6, 7, 7));
+ Vector256 shuffledB = Vector256.Shuffle(b, Vector256.Create(4, 4, 5, 5, 6, 6, 7, 7));
+
+ Vector256 maskA = Vector256.Create(-1, 0, -1, 0, -1, 0, -1, 0).AsSingle();
+ Vector256 maskB = Vector256.Create(0, -1, 0, -1, 0, -1, 0, -1).AsSingle();
+
+ return (shuffledA & maskA) | (shuffledB & maskB);
+ }
}
diff --git a/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs b/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs
index 4442eb6495..882c1098bb 100644
--- a/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs
+++ b/src/ImageSharp/Formats/Jxl/Memory/JxlImage3{T}.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Buffers;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Memory;
@@ -9,6 +11,21 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Memory;
internal class JxlImage3 : IDisposable
where T : unmanaged
{
+ private sealed class TypeChangingMemoryManager(Memory memory) : MemoryManager
+ where TTarget : unmanaged
+ {
+ public override Span GetSpan() => MemoryMarshal.Cast(memory.Span);
+
+ // we don't use these
+ public override MemoryHandle Pin(int elementIndex = 0) => throw new NotImplementedException();
+
+ public override void Unpin() => throw new NotImplementedException();
+
+ protected override void Dispose(bool disposing)
+ {
+ }
+ }
+
private const int PlaneCount = 3;
private JxlPlane[] planes = new JxlPlane[3];
@@ -46,6 +63,22 @@ internal class JxlImage3 : IDisposable
return rowSpan;
}
+ // This method performs minor allocations!
+ public Memory PlaneRowMemory(int plane, int row)
+ {
+ this.PlaneRowBoundsCheck(plane, row);
+
+ int rowOffset = row * this.planes[0].BytesPerRow;
+ Memory rowMemoryBytes = this.planes[plane].Bytes[rowOffset..];
+
+ // we have to allocate a utility class so we can reinterpret
+ // a Memory.
+ // Unsafe.As is truly unsafe because, f.e. what if there are
+ // 400 bytes but T is 4 bytes? the length will remain as 400.
+ TypeChangingMemoryManager reinterpreter = new(rowMemoryBytes);
+ return reinterpreter.Memory;
+ }
+
public Span PlaneRow(Rectangle rectangle, int c, int y)
{
DebugGuard.MustBeGreaterThanOrEqualTo(y + rectangle.Top, 0, nameof(y));
diff --git a/src/ImageSharp/Formats/Jxl/Processing/AcStrategy/JxlAcStrategyRow.cs b/src/ImageSharp/Formats/Jxl/Processing/AcStrategy/JxlAcStrategyRow.cs
index 7a9b1eeafb..60900dc578 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/AcStrategy/JxlAcStrategyRow.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/AcStrategy/JxlAcStrategyRow.cs
@@ -6,17 +6,13 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.AcStrategy;
-internal sealed class JxlAcStrategyRow
+internal readonly struct JxlAcStrategyRow(ReadOnlyMemory row)
{
- private readonly ReadOnlyMemory row;
-
- public JxlAcStrategyRow(ReadOnlyMemory row) => this.row = row;
-
- public JxlAcStrategy this[int x]
+ public readonly JxlAcStrategy this[int x]
{
get
{
- ReadOnlySpan span = this.row.Span;
+ ReadOnlySpan span = row.Span;
DebugGuard.MustBeLessThan(x * 8, span.Length, "x overflows");
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlAlphaBlendingInputLayer.cs b/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlAlphaBlendingInputLayer.cs
index 9a73f659fb..b749a5ba26 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlAlphaBlendingInputLayer.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlAlphaBlendingInputLayer.cs
@@ -3,13 +3,13 @@
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Blending;
-internal sealed class JxlAlphaBlendingInputLayer
+internal ref struct JxlAlphaBlendingInputLayer(ReadOnlySpan singleSpan)
{
- public ReadOnlyMemory R { get; set; }
+ public ReadOnlySpan R = singleSpan;
- public ReadOnlyMemory G { get; set; }
+ public ReadOnlySpan G = singleSpan;
- public ReadOnlyMemory B { get; set; }
+ public ReadOnlySpan B = singleSpan;
- public ReadOnlyMemory A { get; set; }
+ public ReadOnlySpan A = singleSpan;
}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlAlphaBlendingOutput.cs b/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlAlphaBlendingOutput.cs
index 62296b20e7..76a10d2158 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlAlphaBlendingOutput.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlAlphaBlendingOutput.cs
@@ -3,13 +3,13 @@
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Blending;
-internal sealed class JxlAlphaBlendingOutput
+internal ref struct JxlAlphaBlendingOutput(Span singleSpan)
{
- public Memory R { get; set; }
+ public Span R = singleSpan;
- public Memory G { get; set; }
+ public Span G = singleSpan;
- public Memory B { get; set; }
+ public Span B = singleSpan;
- public Memory A { get; set; }
+ public Span A = singleSpan;
}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlBlending.cs b/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlBlending.cs
new file mode 100644
index 0000000000..7ddad4714e
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/Blending/JxlBlending.cs
@@ -0,0 +1,368 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Jxl.IO.FrameHeader;
+using SixLabors.ImageSharp.Formats.Jxl.IO.Metadata;
+using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes;
+using SixLabors.ImageSharp.Formats.Jxl.Processing.Decoder;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Blending;
+
+internal static class JxlBlending
+{
+ public static bool NeedsBlending(JxlFrameHeader header)
+ {
+ if (header.FrameType is not JxlFrameType.RegularFrame and not JxlFrameType.SkipProgressive)
+ {
+ return false;
+ }
+
+ JxlBlendingInfo? blendingInfo = header.BlendingInfo;
+ if (blendingInfo is null)
+ {
+ return false;
+ }
+
+ bool replaceAll = blendingInfo.BlendMode == JxlBlendMode.Replace;
+
+ foreach (JxlBlendingInfo info in header.ExtraChannelBlendingInfo)
+ {
+ if (info.BlendMode != JxlBlendMode.Replace)
+ {
+ replaceAll = false;
+ }
+ }
+
+ if (!header.CustomSizeOrOrigin && replaceAll)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void PerformBlending(
+ Configuration configuration,
+ Buffer2D bg,
+ Buffer2D fg,
+ Buffer2D output,
+ int x0,
+ int xsize,
+ JxlPatchBlending colorBlending,
+ Span ecBlending,
+ List extraChannelInfo)
+ {
+ bool hasAlpha = extraChannelInfo.Any(x => x.Type == JxlExtraChannel.Alpha);
+
+ int numEc = extraChannelInfo.Count;
+ using JxlImageF tmp = new(configuration, xsize, 3 + numEc);
+
+ for (int i = 0; i < numEc; i++)
+ {
+ int i3 = 3 + i;
+
+ switch (ecBlending[i].Mode)
+ {
+ case JxlPatchBlendMode.Add:
+ {
+ Span row = tmp.GetRow(i3);
+ for (int x = 0; x < xsize; x++)
+ {
+ row[x] = bg[i3, x + x0] + fg[i3, x + x0];
+ }
+
+ continue;
+ }
+
+ case JxlPatchBlendMode.BlendAbove:
+ {
+ int alpha = ecBlending[i].AlphaChannel;
+ bool isPremultiplied = extraChannelInfo[alpha].AlphaAssociated;
+
+ Span bgSpan3 = bg.DangerousGetRowSpan(i3)[x0..];
+ Span bgSpan3Alpha = bg.DangerousGetRowSpan(3 + alpha)[x0..];
+ Span fgSpan3 = fg.DangerousGetRowSpan(i3)[x0..];
+ Span fgSpan3Alpha = fg.DangerousGetRowSpan(3 + alpha)[x0..];
+
+ JxlAlphaHelper.PerformAlphaBlending(
+ bgSpan3,
+ bgSpan3Alpha,
+ fgSpan3,
+ fgSpan3Alpha,
+ tmp.GetRow(i3),
+ xsize,
+ isPremultiplied,
+ ecBlending[i].Clamp);
+
+ continue;
+ }
+
+ case JxlPatchBlendMode.BlendBelow:
+ {
+ int alpha = ecBlending[i].AlphaChannel;
+ bool isPremultiplied = extraChannelInfo[alpha].AlphaAssociated;
+
+ Span bgSpan3 = bg.DangerousGetRowSpan(i3)[x0..];
+ Span bgSpan3Alpha = bg.DangerousGetRowSpan(3 + alpha)[x0..];
+ Span fgSpan3 = fg.DangerousGetRowSpan(i3)[x0..];
+ Span fgSpan3Alpha = fg.DangerousGetRowSpan(3 + alpha)[x0..];
+
+ JxlAlphaHelper.PerformAlphaBlending(
+ bgSpan3,
+ bgSpan3Alpha,
+ fgSpan3,
+ fgSpan3Alpha,
+ tmp.GetRow(3 + i),
+ xsize,
+ isPremultiplied,
+ ecBlending[i].Clamp);
+
+ continue;
+ }
+
+ case JxlPatchBlendMode.AlphaWeightedAddAbove:
+ {
+ int alpha = ecBlending[i].AlphaChannel;
+
+ Span bgSpan3 = bg.DangerousGetRowSpan(i3)[x0..];
+ Span bgSpan3Alpha = bg.DangerousGetRowSpan(3 + alpha)[x0..];
+ Span fgSpan3 = fg.DangerousGetRowSpan(i3)[x0..];
+
+ JxlAlphaHelper.PerformAlphaWeightedAdd(
+ bgSpan3,
+ fgSpan3,
+ bgSpan3Alpha,
+ tmp.GetRow(3 + i),
+ xsize,
+ ecBlending[i].Clamp);
+
+ continue;
+ }
+
+ case JxlPatchBlendMode.AlphaWeightedAddBelow:
+ {
+ int alpha = ecBlending[i].AlphaChannel;
+
+ Span bgSpan3 = bg.DangerousGetRowSpan(i3)[x0..];
+ Span bgSpan3Alpha = bg.DangerousGetRowSpan(3 + alpha)[x0..];
+ Span fgSpan3 = fg.DangerousGetRowSpan(i3)[x0..];
+
+ JxlAlphaHelper.PerformAlphaWeightedAdd(
+ fgSpan3,
+ bgSpan3,
+ bgSpan3Alpha,
+ tmp.GetRow(3 + i),
+ xsize,
+ ecBlending[i].Clamp);
+
+ continue;
+ }
+
+ case JxlPatchBlendMode.Multiply:
+ {
+ Span bgSpan3 = bg.DangerousGetRowSpan(i3)[x0..];
+ Span fgSpan3 = fg.DangerousGetRowSpan(i3)[x0..];
+
+ JxlAlphaHelper.PerformMultiplyBlending(
+ bgSpan3,
+ fgSpan3,
+ tmp.GetRow(i3),
+ xsize,
+ ecBlending[i].Clamp);
+
+ continue;
+ }
+
+ case JxlPatchBlendMode.Replace:
+ if (xsize > 0)
+ {
+ Span fgSpan3 = fg.DangerousGetRowSpan(i3)[x0..];
+ fgSpan3.Slice(0, xsize).CopyTo(tmp.GetRow(i3));
+ }
+
+ continue;
+
+ case JxlPatchBlendMode.None:
+ if (xsize > 0)
+ {
+ Span bgSpan3 = bg.DangerousGetRowSpan(i3)[x0..];
+ bgSpan3.Slice(0, xsize).CopyTo(tmp.GetRow(i3));
+ }
+
+ continue;
+ }
+ }
+
+ int colorBlendingAlpha = colorBlending.AlphaChannel;
+
+ void Add()
+ {
+ for (int p = 0; p < 3; p++)
+ {
+ Span output = tmp.GetRow(p);
+ Span bgSpan = bg.DangerousGetRowSpan(p);
+ Span fgSpan = fg.DangerousGetRowSpan(p);
+
+ for (int x = 0; x < xsize; x++)
+ {
+ int xPlusX0 = x + x0;
+
+ output[x] = bgSpan[xPlusX0] + fgSpan[xPlusX0];
+ }
+ }
+ }
+
+ void BlendWeighted(Span bottom, Span top)
+ {
+ bool isPremultiplied = extraChannelInfo[colorBlendingAlpha].AlphaAssociated;
+
+ JxlAlphaHelper.PerformAlphaBlending(
+ new JxlAlphaBlendingInputLayer()
+ {
+ R = bottom[x0..],
+ G = bottom[(x0 + 1)..],
+ B = bottom[(2 + x0)..],
+ A = bottom[(3 + colorBlendingAlpha + x0)..]
+ },
+ new JxlAlphaBlendingInputLayer()
+ {
+ R = top[x0..],
+ G = top[(x0 + 1)..],
+ B = top[(x0 + 2)..],
+ A = top[(3 + colorBlendingAlpha + x0)..]
+ },
+ new JxlAlphaBlendingOutput()
+ {
+ R = tmp.GetRow(0),
+ G = tmp.GetRow(1),
+ B = tmp.GetRow(2),
+ A = tmp.GetRow(3)
+ },
+ xsize,
+ isPremultiplied,
+ colorBlending.Clamp);
+ }
+
+ void AddWeighted(Span bottom, Span top)
+ {
+ for (int c = 0; c < 3; c++)
+ {
+ JxlAlphaHelper.PerformAlphaWeightedAdd(bottom[(c + x0)..], top[(c + x0)..], top[(3 + colorBlendingAlpha + x0)..], tmp.GetRow(c), xsize, colorBlending.Clamp);
+ }
+ }
+
+ void Copy(Span src)
+ {
+ for (int p = 0; p < 3; p++)
+ {
+ src.Slice(p + x0, xsize).CopyTo(tmp.GetRow(p));
+ }
+ }
+
+ switch (colorBlending.Mode)
+ {
+ case JxlPatchBlendMode.Add:
+ {
+ Add();
+ break;
+ }
+
+ case JxlPatchBlendMode.AlphaWeightedAddAbove:
+ {
+ if (hasAlpha)
+ {
+ AddWeighted(bg.DangerousGetSingleSpan(), fg.DangerousGetSingleSpan());
+ }
+ else
+ {
+ Add();
+ }
+
+ break;
+ }
+
+ case JxlPatchBlendMode.AlphaWeightedAddBelow:
+ {
+ if (hasAlpha)
+ {
+ AddWeighted(fg.DangerousGetSingleSpan(), bg.DangerousGetSingleSpan());
+ }
+ else
+ {
+ Add();
+ }
+
+ break;
+ }
+
+ case JxlPatchBlendMode.BlendAbove:
+ {
+ if (hasAlpha)
+ {
+ BlendWeighted(bg.DangerousGetSingleSpan(), fg.DangerousGetSingleSpan());
+ }
+ else
+ {
+ Copy(fg.DangerousGetSingleSpan());
+ }
+
+ break;
+ }
+
+ case JxlPatchBlendMode.BlendBelow:
+ {
+ if (hasAlpha)
+ {
+ BlendWeighted(fg.DangerousGetSingleSpan(), bg.DangerousGetSingleSpan());
+ }
+ else
+ {
+ Copy(fg.DangerousGetSingleSpan());
+ }
+
+ break;
+ }
+
+ case JxlPatchBlendMode.Multiply:
+ {
+ Span bgSpan = bg.DangerousGetSingleSpan();
+ Span fgSpan = fg.DangerousGetSingleSpan();
+
+ for (int p = 0; p < 3; p++)
+ {
+ JxlAlphaHelper.PerformMultiplyBlending(
+ bgSpan[(p + x0)..],
+ fgSpan[(p + x0)..],
+ tmp.GetRow(p),
+ xsize,
+ colorBlending.Clamp);
+ }
+
+ break;
+ }
+
+ case JxlPatchBlendMode.Replace:
+ {
+ Copy(fg.DangerousGetSingleSpan());
+ break;
+ }
+
+ case JxlPatchBlendMode.None:
+ {
+ Copy(bg.DangerousGetSingleSpan());
+ break;
+ }
+ }
+
+ if (xsize != 0)
+ {
+ Span outputSpan = output.DangerousGetSingleSpan();
+
+ for (int i = 0; i < 3; i++)
+ {
+ tmp.GetRow(i).Slice(0, xsize).CopyTo(outputSpan[(i + x0)..]);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Dct/JxlDctOutput.cs b/src/ImageSharp/Formats/Jxl/Processing/Dct/JxlDctOutput.cs
index 5ef6218908..5a1d5c9581 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Dct/JxlDctOutput.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Dct/JxlDctOutput.cs
@@ -3,6 +3,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Dct;
@@ -49,4 +50,22 @@ internal ref struct JxlDctOutput(Span data, int stride)
/// The offset.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void StorePart(Vector value, int row, int index) => value.CopyTo(this.Address(row, index));
+
+ ///
+ /// Stores the vector into the data at the specified row and offset.
+ ///
+ /// The vector to write.
+ /// The row index.
+ /// The offset.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void StorePart256(Vector256 value, int row, int index) => value.CopyTo(this.Address(row, index));
+
+ ///
+ /// Stores the vector into the data at the specified row and offset.
+ ///
+ /// The vector to write.
+ /// The row index.
+ /// The offset.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void StorePart128(Vector128 value, int row, int index) => value.CopyTo(this.Address(row, index));
}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Dct/JxlDctSource.cs b/src/ImageSharp/Formats/Jxl/Processing/Dct/JxlDctSource.cs
index 0fe17e92ad..ba99dd5f67 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/Dct/JxlDctSource.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/Dct/JxlDctSource.cs
@@ -3,6 +3,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Dct;
@@ -52,4 +53,24 @@ internal readonly ref struct JxlDctSource(Span data, int stride)
/// Vector at that row and offset.
///
public Vector LoadPart(int row, int i) => new(this.Address(row, i));
+
+ ///
+ /// Loads a vector at the specified row and offset.
+ ///
+ /// The row index.
+ /// The offset.
+ ///
+ /// Vector at that row and offset.
+ ///
+ public Vector256 LoadPart256(int row, int i) => Vector256.Create(this.Address(row, i));
+
+ ///
+ /// Loads a vector at the specified row and offset.
+ ///
+ /// The row index.
+ /// The offset.
+ ///
+ /// Vector at that row and offset.
+ ///
+ public Vector128 LoadPart128(int row, int i) => Vector128.Create(this.Address(row, i));
}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Encoder/Ans/JxlToken.cs b/src/ImageSharp/Formats/Jxl/Processing/Encoder/Ans/JxlToken.cs
new file mode 100644
index 0000000000..2b07fb14d9
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/Encoder/Ans/JxlToken.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Jxl.Processing.Splines;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.Ans;
+
+internal struct JxlToken(JxlSplineEntropyContext c, uint value)
+{
+ public bool IsLz77Length;
+ public JxlSplineEntropyContext Context = c;
+ public uint Value = value;
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlHeuristics.cs b/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlHeuristics.cs
new file mode 100644
index 0000000000..846f791f69
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlHeuristics.cs
@@ -0,0 +1,170 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Formats.Jxl.Memory.ImageTypes;
+using SixLabors.ImageSharp.Formats.Jxl.Processing.AcStrategy;
+using SixLabors.ImageSharp.Formats.Jxl.Processing.Primitives;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder;
+
+internal static class JxlHeuristics
+{
+ private static ReadOnlySpan SimpleContextMap =>
+ [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ ];
+
+ public static void FindBestBlockEntropyModel(JxlCompressParameters cparameters, JxlImageI rqf, JxlAcStrategyImage acStrategy, JxlBlockContextMap blockCtxMap)
+ {
+ if (cparameters.DecodingSpeedTier >= 1)
+ {
+ SimpleContextMap.CopyTo(blockCtxMap.ContextMap.AsSpan());
+ blockCtxMap.ContextCount = 2;
+ blockCtxMap.DcContextCount = 1;
+ return;
+ }
+
+ if (cparameters.SpeedTier >= JxlSpeedTier.Falcon)
+ {
+ return;
+ }
+
+ int total = rqf.XSize * rqf.YSize;
+ int sizeForContextModel = (1 << 10) * cparameters.ButteraugliDistance;
+
+ if (total < sizeForContextModel)
+ {
+ return;
+ }
+
+ OccCounters counters = new(rqf, acStrategy);
+ int sizeForQfSplit = (1 << 13) * cparameters.ButteraugliDistance;
+ int numQfSegments = total < sizeForQfSplit ? 1 : 2;
+ List qft = blockCtxMap.QfThresholds;
+ qft.Clear();
+ int cumulativeSum = 0;
+ int next = 1;
+ int lastCut = 256;
+ int cut = total * next / numQfSegments;
+
+ for (int j = 0; j < 256; j++)
+ {
+ cumulativeSum += counters.QfCounts[j];
+
+ if (cumulativeSum > cut)
+ {
+ if (j != 0)
+ {
+ qft.Add((uint)j);
+ }
+
+ lastCut = j;
+
+ while (cumulativeSum > cut)
+ {
+ next++;
+ cut = total * next / numQfSegments;
+ }
+ }
+ else if (next > qft.Count + 1)
+ {
+ if (j - 1 == lastCut && j != 0)
+ {
+ qft.Add((uint)j);
+ }
+ }
+ }
+
+ int[]? pooledCounts = null;
+ int[]? pooledRemap = null;
+ int[]? pooledClusters = null;
+ int countsLength = JxlForwardCoefficientOrder.OrderCount * (qft.Count + 1);
+
+ Span counts =
+ countsLength <= 128
+ ? stackalloc int[128].Slice(0, countsLength)
+ : pooledCounts = ArrayPool.Shared.Rent(countsLength);
+
+ Span remap =
+ countsLength <= 128
+ ? stackalloc int[128].Slice(0, countsLength)
+ : pooledRemap = ArrayPool.Shared.Rent(countsLength);
+
+ Span clusters =
+ countsLength <= 128
+ ? stackalloc int[128].Slice(0, countsLength)
+ : pooledClusters = ArrayPool.Shared.Rent(countsLength);
+
+ int qftPos = 0;
+
+ for (int j = 0; j < 256; j++)
+ {
+ if (qftPos < qft.Count && j == qft[qftPos])
+ {
+ qftPos++;
+ }
+
+ for (int i = 0; i < JxlForwardCoefficientOrder.OrderCount; i++)
+ {
+ counts[qftPos + (i * (qft.Count + 1))] += counters.QfOrdCounts[i, j];
+ }
+ }
+
+ JxlSimdUtils.Iota(remap, 0);
+ remap.CopyTo(clusters);
+
+ int numClusters = Math.Clamp(total / sizeForContextModel / 2, 2, 9);
+ int numClustersChroma = Math.Clamp(total / sizeForContextModel / 3, 1, 5);
+
+ // TODO: method incomplete
+ // do not forget to ArrayPool.Shared.Return pooledCounts, pooledRemap, pooledClusters if needed
+ }
+
+ private sealed class OccCounters : IDisposable
+ {
+ private readonly int[] qfCounts;
+ private readonly int[] dataForQfOrdCounts;
+ private readonly int[] ordCounts;
+
+ public OccCounters(JxlImageI rqf, JxlAcStrategyImage acStrategy)
+ {
+ this.qfCounts = ArrayPool.Shared.Rent(256);
+ this.dataForQfOrdCounts = ArrayPool.Shared.Rent(256 * JxlForwardCoefficientOrder.OrderCount);
+ this.ordCounts = ArrayPool.Shared.Rent(JxlForwardCoefficientOrder.OrderCount);
+
+ this.QfOrdCounts = new(JxlForwardCoefficientOrder.OrderCount, 256, this.dataForQfOrdCounts);
+
+ for (int y = 0; y < rqf.YSize; y++)
+ {
+ Span qfRow = rqf.GetRow(y);
+ JxlAcStrategyRow acsRow = acStrategy.GetRow(y);
+
+ for (int x = 0; x < rqf.XSize; x++)
+ {
+ int ord = JxlCoefficientOrder.StrategyOrder[acsRow[x].RawStrategy];
+ int qf = qfRow[x] - 1;
+ this.qfCounts[qf]++;
+ this.QfOrdCounts[ord, qf]++;
+ this.ordCounts[ord]++;
+ }
+ }
+ }
+
+ public Span QfCounts => this.qfCounts.AsSpan();
+
+ public DenseMatrix QfOrdCounts { get; }
+
+ public Span OrdCounts => this.ordCounts.AsSpan();
+
+ public void Dispose()
+ {
+ ArrayPool.Shared.Return(this.qfCounts);
+ ArrayPool.Shared.Return(this.dataForQfOrdCounts);
+ ArrayPool.Shared.Return(this.ordCounts);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlSplineEncoder.cs b/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlSplineEncoder.cs
new file mode 100644
index 0000000000..949b43870e
--- /dev/null
+++ b/src/ImageSharp/Formats/Jxl/Processing/Encoder/JxlSplineEncoder.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.Ans;
+using SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder.AuxiliaryOutput;
+using SixLabors.ImageSharp.Formats.Jxl.Processing.Primitives;
+using SixLabors.ImageSharp.Formats.Jxl.Processing.Splines;
+
+namespace SixLabors.ImageSharp.Formats.Jxl.Processing.Encoder;
+
+internal sealed class JxlSplineEncoder
+{
+ private static void Tokenize(JxlQuantizedSpline spline, List tokens)
+ {
+ tokens.Add(new(JxlSplineEntropyContext.NumControlPoints, (uint)spline.ControlPoints.Length));
+
+ foreach (JxlControlPoint point in spline.ControlPoints.Span)
+ {
+ tokens.Add(new(JxlSplineEntropyContext.ControlPoints, JxlPackSigned.PackUnsigned(point.First)));
+ tokens.Add(new(JxlSplineEntropyContext.ControlPoints, JxlPackSigned.PackUnsigned(point.Second)));
+ }
+
+ void EncodeDCT(Span dct)
+ {
+ for (int i = 0; i < 32; i++)
+ {
+ tokens.Add(new(JxlSplineEntropyContext.Dct, JxlPackSigned.PackUnsigned(dct[i])));
+ }
+ }
+
+ foreach (Span dct in spline.ColorDct)
+ {
+ EncodeDCT(dct);
+ }
+
+ EncodeDCT(spline.SigmaDct);
+ }
+
+ public static void EncodeAllStartingPoints(Span points, List tokens)
+ {
+ long lastX = 0;
+ long lastY = 0;
+
+ for (int i = 0; i < points.Length; i++)
+ {
+ long x = (long)MathF.Round(points[i].X, MidpointRounding.AwayFromZero);
+ long y = (long)MathF.Round(points[i].Y, MidpointRounding.AwayFromZero);
+
+ if (i == 0)
+ {
+ tokens.Add(new(JxlSplineEntropyContext.StartingPosition, (uint)x));
+ tokens.Add(new(JxlSplineEntropyContext.StartingPosition, (uint)y));
+ }
+ else
+ {
+ tokens.Add(new(JxlSplineEntropyContext.StartingPosition, JxlPackSigned.PackUnsigned((int)(x - lastX))));
+ tokens.Add(new(JxlSplineEntropyContext.StartingPosition, JxlPackSigned.PackUnsigned((int)(y - lastY))));
+ }
+
+ lastX = x;
+ lastY = y;
+ }
+ }
+
+ public static void EncodeSplines(JxlSplines splines, JxlBitWriter writer, JxlLayerType layer, JxlHistogramParameters histogramParameters, JxlAuxiliaryOutput auxOut)
+ {
+ Span quantizedSplines = splines.QuantizedSplines;
+ List> tokens = [[]];
+ tokens[0].Add(new(JxlSplineEntropyContext.NumSplineContexts, (uint)(quantizedSplines.Length - 1)));
+
+ EncodeAllStartingPoints(splines.StartingPoints, tokens[0]);
+
+ tokens[0].Add(new(JxlSplineEntropyContext.QuantizationAdjustment, JxlPackSigned.PackUnsigned(splines.QuantizationAdjustment)));
+
+ foreach (JxlQuantizedSpline spline in quantizedSplines)
+ {
+ Tokenize(spline, tokens[0]);
+ }
+
+ _ = BuildAndEncodeHistograms(writer, histogramParameters, JxlSplineEntropyContext.NumSplineContexts, tokens, out JxlEntropyEncodingData codes, writer, layer, auxOut);
+ WriteTokens(tokens[0], codes, 0, writer, layer, auxOut);
+ }
+}
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlCoefficientOrder.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlCoefficientOrder.cs
index 4cace39d73..00c162be9a 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/JxlCoefficientOrder.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlCoefficientOrder.cs
@@ -50,7 +50,7 @@ internal static class JxlCoefficientOrder
return Math.Min(token, PermutationContexts - 1u);
}
- public static bool ReadPermutation(int skip, int size, Span order, JxlBitReader bitReader, JxlAnsSymbolReader reader, Span contextMap)
+ public static bool ReadPermutation(int skip, int size, Span order, JxlBitReader bitReader, JxlAnsSymbolReader reader, Span contextMap)
{
DebugGuard.MustBeLessThanOrEqualTo(size, 65536, nameof(size));
diff --git a/src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.StoreInterleaved.Generated.cs b/src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.StoreInterleaved.Generated.cs
index b03c77eced..8e4495e0f0 100644
--- a/src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.StoreInterleaved.Generated.cs
+++ b/src/ImageSharp/Formats/Jxl/Processing/JxlSimdUtils.StoreInterleaved.Generated.cs
@@ -10,124 +10,139 @@ namespace SixLabors.ImageSharp.Formats.Jxl.Processing;
internal static partial class JxlSimdUtils
{
- public static void StoreInterleaved(Vector v1, Vector v2, ref T memory)
+ public static unsafe void StoreInterleaved(Vector v1, Vector v2, ref T memory)
+ where T : unmanaged
{
- v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
- v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
+ v1.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 0));
+ v2.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 1));
}
- public static void StoreInterleaved(Vector v1, Vector v2, Vector v3, ref T memory)
+ public static unsafe void StoreInterleaved(Vector v1, Vector v2, Vector v3, ref T memory)
+ where T : unmanaged
{
- v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
- v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
- v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
+ v1.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 0));
+ v2.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 1));
+ v3.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 2));
}
- public static void StoreInterleaved(Vector v1, Vector v2, Vector v3, Vector v4, ref T memory)
+ public static unsafe void StoreInterleaved(Vector v1, Vector v2, Vector v3, Vector v4, ref T memory)
+ where T : unmanaged
{
- v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
- v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
- v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
- v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
+ v1.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 0));
+ v2.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 1));
+ v3.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 2));
+ v4.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 3));
}
- public static void StoreInterleaved(Vector v1, Vector v2, Vector v3, Vector v4, Vector v5, ref T memory)
+ public static unsafe void StoreInterleaved(Vector v1, Vector v2, Vector v3, Vector v4, Vector v5, ref T memory)
+ where T : unmanaged
{
- v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
- v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
- v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
- v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
- v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4));
+ v1.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 0));
+ v2.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 1));
+ v3.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 2));
+ v4.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 3));
+ v5.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 4));
}
- public static void StoreInterleaved(Vector v1, Vector v2, Vector v3, Vector v4, Vector v5, Vector v6, ref T memory)
+ public static unsafe void StoreInterleaved(Vector v1, Vector v2, Vector v3, Vector v4, Vector v5, Vector v6, ref T memory)
+ where T : unmanaged
{
- v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
- v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
- v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
- v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
- v5.StoreUnsafe(ref Unsafe.Add(ref memory, 4));
- v6.StoreUnsafe(ref Unsafe.Add(ref memory, 5));
+ v1.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 0));
+ v2.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 1));
+ v3.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 2));
+ v4.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 3));
+ v5.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 4));
+ v6.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 5));
}
- public static void StoreInterleaved(Vector128 v1, Vector128 v2, ref T memory)
+ public static unsafe void StoreInterleaved(Vector128 v1, Vector128 v2, ref T memory)
+ where T : unmanaged
{
- v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
- v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
+ v1.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 0));
+ v2.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 1));
}
- public static void StoreInterleaved(Vector128 v1, Vector128 v2, Vector128 v3, ref T memory)
+ public static unsafe void StoreInterleaved(Vector128 v1, Vector128 v2, Vector128 v3, ref T memory)
+ where T : unmanaged
{
- v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
- v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
- v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
+ v1.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 0));
+ v2.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 1));
+ v3.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 2));
}
- public static void StoreInterleaved(Vector128 v1, Vector128 v2, Vector128 v3, Vector128 v4, ref T memory)
+ public static unsafe void StoreInterleaved(Vector128 v1, Vector128 v2, Vector128 v3, Vector128 v4, ref T memory)
+ where T : unmanaged
{
- v1.StoreUnsafe(ref Unsafe.Add(ref memory, 0));
- v2.StoreUnsafe(ref Unsafe.Add(ref memory, 1));
- v3.StoreUnsafe(ref Unsafe.Add(ref memory, 2));
- v4.StoreUnsafe(ref Unsafe.Add(ref memory, 3));
+ v1.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 0));
+ v2.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 1));
+ v3.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 2));
+ v4.StoreUnsafe(ref Unsafe.Add(ref memory, Vector.Count * 3));
}
- public static void StoreInterleaved(Vector128 v1, Vector128