mirror of https://github.com/SixLabors/ImageSharp
54 changed files with 1839 additions and 4473 deletions
@ -0,0 +1,305 @@ |
|||
// 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; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
|
|||
internal abstract partial class JpegColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Normalizes three planar component lanes and interleaves them into packed XYZ values.
|
|||
/// </summary>
|
|||
/// <param name="xLane">The planar X components.</param>
|
|||
/// <param name="yLane">The planar Y components.</param>
|
|||
/// <param name="zLane">The planar Z components.</param>
|
|||
/// <param name="packed">The destination ordered as consecutive XYZ triples.</param>
|
|||
/// <param name="scale">The normalization factor applied to every component.</param>
|
|||
public static void PackedNormalizeInterleave3(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, Span<float> packed, float scale) |
|||
{ |
|||
DebugGuard.IsTrue(packed.Length % 3 == 0, "Packed length must be divisible by 3."); |
|||
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); |
|||
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); |
|||
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 3, xLane.Length, nameof(packed)); |
|||
|
|||
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
|||
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
|||
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
|||
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
|||
int i = 0; |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<float> scaleVector = Vector128.Create(scale); |
|||
int oneVectorFromEnd = xLane.Length - Vector128<float>.Count; |
|||
|
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
// Each source vector contains four consecutive samples from one plane:
|
|||
// x = [X0 X1 X2 X3]
|
|||
// y = [Y0 Y1 Y2 Y3]
|
|||
// z = [Z0 Z1 Z2 Z3]
|
|||
// Shifting X by one sample supplies the value that follows each XYZ triple:
|
|||
// shiftedX = [X1 X2 X3 0]
|
|||
// The transpose therefore produces overlapping rows [Xn Yn Zn Xn+1].
|
|||
// AlignRight joins those rows into three complete destination vectors, avoiding
|
|||
// the scalar-sized stores that writing four independent Vector3 values requires.
|
|||
Vector128<float> x = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref xLaneRef, i)) * scaleVector; |
|||
Vector128<float> y = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref yLaneRef, i)) * scaleVector; |
|||
Vector128<float> z = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref zLaneRef, i)) * scaleVector; |
|||
Vector128<float> shiftedX = Vector128_.ShiftRightBytesInVector(x.AsByte(), sizeof(float)).AsSingle(); |
|||
|
|||
Transpose4(x, y, z, shiftedX, out Vector128<float> pixel0, out Vector128<float> pixel1, out Vector128<float> pixel2, out Vector128<float> pixel3); |
|||
|
|||
// Dropping pixel2.X lets [Y2] complete [Y1 Z1 X2] from pixel1.
|
|||
Vector128<byte> shiftedPixel2 = Vector128_.ShiftRightBytesInVector(pixel2.AsByte(), sizeof(float)); |
|||
Vector128<float> packed1 = Vector128_.AlignRight(shiftedPixel2, pixel1.AsByte(), sizeof(float)).AsSingle(); |
|||
|
|||
// Dropping pixel3.X leaves [Y3 Z3] to complete [Z2 X3] from pixel2.
|
|||
Vector128<byte> shiftedPixel3 = Vector128_.ShiftRightBytesInVector(pixel3.AsByte(), sizeof(float)); |
|||
Vector128<float> packed2 = Vector128_.AlignRight(shiftedPixel3, pixel2.AsByte(), sizeof(float) * 2).AsSingle(); |
|||
|
|||
ref Vector128<float> destination = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, (uint)i * 3)); |
|||
|
|||
destination = pixel0; |
|||
Unsafe.Add(ref destination, 1) = packed1; |
|||
Unsafe.Add(ref destination, 2) = packed2; |
|||
} |
|||
} |
|||
|
|||
// Fewer than four pixels remain after SIMD, or every pixel reaches this path
|
|||
// when the runtime cannot accelerate the cross-vector transpose.
|
|||
for (; i < xLane.Length; i++) |
|||
{ |
|||
nuint sourceOffset = (uint)i; |
|||
nuint packedOffset = sourceOffset * 3; |
|||
Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, sourceOffset) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, sourceOffset) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, sourceOffset) * scale; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Deinterleaves packed XYZ values into three planar component lanes.
|
|||
/// </summary>
|
|||
/// <param name="packed">The source ordered as consecutive XYZ triples.</param>
|
|||
/// <param name="xLane">The destination X components.</param>
|
|||
/// <param name="yLane">The destination Y components.</param>
|
|||
/// <param name="zLane">The destination Z components.</param>
|
|||
public static void UnpackDeinterleave3(ReadOnlySpan<Vector3> packed, Span<float> xLane, Span<float> yLane, Span<float> zLane) |
|||
{ |
|||
DebugGuard.IsTrue(packed.Length == xLane.Length, nameof(packed), "Channels must be of same size!"); |
|||
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); |
|||
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); |
|||
|
|||
ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Vector3, float>(packed)); |
|||
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
|||
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
|||
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
|||
int i = 0; |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = packed.Length - Vector128<float>.Count; |
|||
|
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
// A Vector3 occupies twelve contiguous bytes, so a sixteen-byte load beginning
|
|||
// at one pixel also reads the X component of the following pixel:
|
|||
// pixel0 = [X0 Y0 Z0 X1]
|
|||
// pixel1 = [X1 Y1 Z1 X2]
|
|||
// The transpose discards this fourth column, making the overlap useful padding
|
|||
// and avoiding two insert instructions per pixel. The final row needs explicit
|
|||
// zero padding only when pixel3 is the last element in the source span.
|
|||
nuint packedOffset = (uint)i * 3; |
|||
Vector128<float> pixel0 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, packedOffset)); |
|||
Vector128<float> pixel1 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, packedOffset + 3)); |
|||
Vector128<float> pixel2 = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, packedOffset + 6)); |
|||
ref float pixel3Ref = ref Unsafe.Add(ref packedRef, packedOffset + 9); |
|||
Vector128<float> pixel3 = i + Vector128<float>.Count < packed.Length ? Unsafe.As<float, Vector128<float>>(ref pixel3Ref) : Unsafe.As<float, Vector3>(ref pixel3Ref).AsVector128(); |
|||
|
|||
Transpose4(pixel0, pixel1, pixel2, pixel3, out Vector128<float> x, out Vector128<float> y, out Vector128<float> z, out _); |
|||
|
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref xLaneRef, i)) = x; |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref yLaneRef, i)) = y; |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref zLaneRef, i)) = z; |
|||
} |
|||
} |
|||
|
|||
// The scalar remainder preserves the original scatter behavior for zero to
|
|||
// three pixels and provides the complete fallback on unsupported hardware.
|
|||
for (; i < packed.Length; i++) |
|||
{ |
|||
nuint packedOffset = (uint)i * 3; |
|||
Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset); |
|||
Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 1); |
|||
Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 2); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Normalizes four planar component lanes and interleaves them into packed XYZW values.
|
|||
/// </summary>
|
|||
/// <param name="xLane">The planar X components.</param>
|
|||
/// <param name="yLane">The planar Y components.</param>
|
|||
/// <param name="zLane">The planar Z components.</param>
|
|||
/// <param name="wLane">The planar W components.</param>
|
|||
/// <param name="packed">The destination ordered as consecutive XYZW groups.</param>
|
|||
/// <param name="maxValue">The maximum component value used to normalize each component.</param>
|
|||
public static void PackedNormalizeInterleave4(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, ReadOnlySpan<float> wLane, Span<float> packed, float maxValue) |
|||
{ |
|||
DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); |
|||
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); |
|||
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); |
|||
DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); |
|||
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); |
|||
|
|||
float scale = 1F / maxValue; |
|||
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
|||
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
|||
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
|||
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); |
|||
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
|||
int i = 0; |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<float> scaleVector = Vector128.Create(scale); |
|||
int oneVectorFromEnd = xLane.Length - Vector128<float>.Count; |
|||
|
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
// Four planar vectors form the rows of a 4x4 matrix. Transposition
|
|||
// converts them into four complete [Xn Yn Zn Wn] pixel vectors, so
|
|||
// normalization and interleaving require only four loads, four
|
|||
// multiplies, the register transpose, and four contiguous stores.
|
|||
Vector128<float> x = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref xLaneRef, i)) * scaleVector; |
|||
Vector128<float> y = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref yLaneRef, i)) * scaleVector; |
|||
Vector128<float> z = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref zLaneRef, i)) * scaleVector; |
|||
Vector128<float> w = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref wLaneRef, i)) * scaleVector; |
|||
|
|||
Transpose4(x, y, z, w, out Vector128<float> pixel0, out Vector128<float> pixel1, out Vector128<float> pixel2, out Vector128<float> pixel3); |
|||
|
|||
ref Vector128<float> destination = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, (uint)i * 4)); |
|||
|
|||
destination = pixel0; |
|||
Unsafe.Add(ref destination, 1) = pixel1; |
|||
Unsafe.Add(ref destination, 2) = pixel2; |
|||
Unsafe.Add(ref destination, 3) = pixel3; |
|||
} |
|||
} |
|||
|
|||
// Process the zero-to-three trailing pixels with the same normalization
|
|||
// and component order as the vector transpose.
|
|||
for (; i < xLane.Length; i++) |
|||
{ |
|||
nuint sourceOffset = (uint)i; |
|||
nuint packedOffset = sourceOffset * 4; |
|||
Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, sourceOffset) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, sourceOffset) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, sourceOffset) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 3) = Unsafe.Add(ref wLaneRef, sourceOffset) * scale; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inverts and normalizes four planar component lanes before interleaving them into packed XYZW values.
|
|||
/// </summary>
|
|||
/// <param name="xLane">The inverted planar X components.</param>
|
|||
/// <param name="yLane">The inverted planar Y components.</param>
|
|||
/// <param name="zLane">The inverted planar Z components.</param>
|
|||
/// <param name="wLane">The inverted planar W components.</param>
|
|||
/// <param name="packed">The destination ordered as consecutive conventional XYZW groups.</param>
|
|||
/// <param name="maxValue">The maximum component value used for inversion and normalization.</param>
|
|||
public static void PackedInvertNormalizeInterleave4(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, ReadOnlySpan<float> wLane, Span<float> packed, float maxValue) |
|||
{ |
|||
DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4."); |
|||
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); |
|||
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); |
|||
DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!"); |
|||
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed)); |
|||
|
|||
float scale = 1F / maxValue; |
|||
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
|||
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
|||
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
|||
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); |
|||
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
|||
int i = 0; |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<float> maximumVector = Vector128.Create(maxValue); |
|||
Vector128<float> scaleVector = Vector128.Create(scale); |
|||
int oneVectorFromEnd = xLane.Length - Vector128<float>.Count; |
|||
|
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
// Adobe JPEG stores all four components inverted in the sample
|
|||
// domain. Reflecting and normalizing the planar vectors before the
|
|||
// transpose keeps both arithmetic operations lane-wise and leaves
|
|||
// the transpose responsible only for the planar-to-packed layout.
|
|||
Vector128<float> x = (maximumVector - Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref xLaneRef, i))) * scaleVector; |
|||
Vector128<float> y = (maximumVector - Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref yLaneRef, i))) * scaleVector; |
|||
Vector128<float> z = (maximumVector - Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref zLaneRef, i))) * scaleVector; |
|||
Vector128<float> w = (maximumVector - Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref wLaneRef, i))) * scaleVector; |
|||
|
|||
Transpose4(x, y, z, w, out Vector128<float> pixel0, out Vector128<float> pixel1, out Vector128<float> pixel2, out Vector128<float> pixel3); |
|||
|
|||
ref Vector128<float> destination = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref packedRef, (uint)i * 4)); |
|||
|
|||
destination = pixel0; |
|||
Unsafe.Add(ref destination, 1) = pixel1; |
|||
Unsafe.Add(ref destination, 2) = pixel2; |
|||
Unsafe.Add(ref destination, 3) = pixel3; |
|||
} |
|||
} |
|||
|
|||
// Preserve the original operation order for the zero-to-three trailing pixels:
|
|||
// subtract in the sample domain, then multiply by the reciprocal maximum.
|
|||
for (; i < xLane.Length; i++) |
|||
{ |
|||
nuint sourceOffset = (uint)i; |
|||
nuint packedOffset = sourceOffset * 4; |
|||
Unsafe.Add(ref packedRef, packedOffset) = (maxValue - Unsafe.Add(ref xLaneRef, sourceOffset)) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 1) = (maxValue - Unsafe.Add(ref yLaneRef, sourceOffset)) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 2) = (maxValue - Unsafe.Add(ref zLaneRef, sourceOffset)) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 3) = (maxValue - Unsafe.Add(ref wLaneRef, sourceOffset)) * scale; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transposes four four-lane rows into four four-lane columns.
|
|||
/// </summary>
|
|||
/// <param name="row0">The first matrix row.</param>
|
|||
/// <param name="row1">The second matrix row.</param>
|
|||
/// <param name="row2">The third matrix row.</param>
|
|||
/// <param name="row3">The fourth matrix row.</param>
|
|||
/// <param name="column0">The first matrix column.</param>
|
|||
/// <param name="column1">The second matrix column.</param>
|
|||
/// <param name="column2">The third matrix column.</param>
|
|||
/// <param name="column3">The fourth matrix column.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void Transpose4(Vector128<float> row0, Vector128<float> row1, Vector128<float> row2, Vector128<float> row3, out Vector128<float> column0, out Vector128<float> column1, out Vector128<float> column2, out Vector128<float> column3) |
|||
{ |
|||
// The first unpack interleaves adjacent 32-bit lanes from rows 0/1 and 2/3:
|
|||
// row01Low = [r0c0 r1c0 r0c1 r1c1]
|
|||
// row23Low = [r2c0 r3c0 r2c1 r3c1]
|
|||
// A second unpack treats each adjacent pair as one 64-bit lane and combines
|
|||
// the row01 and row23 pairs into complete columns. The integer views only
|
|||
// expose the cross-platform unpack helpers; every floating-point bit is preserved.
|
|||
Vector128<int> row01Low = Vector128_.UnpackLow(row0.AsInt32(), row1.AsInt32()); |
|||
Vector128<int> row01High = Vector128_.UnpackHigh(row0.AsInt32(), row1.AsInt32()); |
|||
Vector128<int> row23Low = Vector128_.UnpackLow(row2.AsInt32(), row3.AsInt32()); |
|||
Vector128<int> row23High = Vector128_.UnpackHigh(row2.AsInt32(), row3.AsInt32()); |
|||
|
|||
column0 = Vector128_.UnpackLow(row01Low.AsInt64(), row23Low.AsInt64()).AsSingle(); |
|||
column1 = Vector128_.UnpackHigh(row01Low.AsInt64(), row23Low.AsInt64()).AsSingle(); |
|||
column2 = Vector128_.UnpackLow(row01High.AsInt64(), row23High.AsInt64()).AsSingle(); |
|||
column3 = Vector128_.UnpackHigh(row01High.AsInt64(), row23High.AsInt64()).AsSingle(); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,178 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using BenchmarkDotNet.Attributes; |
|||
using BenchmarkDotNet.Columns; |
|||
using BenchmarkDotNet.Configs; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
|||
|
|||
/// <summary>
|
|||
/// Compares the previous scalar JPEG packing loops with the SIMD register-transpose implementation.
|
|||
/// </summary>
|
|||
[Config(typeof(Config.Short))] |
|||
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
|||
[CategoriesColumn] |
|||
public class JpegColorPacking |
|||
{ |
|||
private const float MaximumValue = 255F; |
|||
private const float Scale = 1F / MaximumValue; |
|||
|
|||
private float[] x = null!; |
|||
private float[] y = null!; |
|||
private float[] z = null!; |
|||
private float[] w = null!; |
|||
private Vector3[] packed3 = null!; |
|||
private float[] destination3 = null!; |
|||
private float[] destination4 = null!; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of pixels transformed by each benchmark invocation.
|
|||
/// </summary>
|
|||
[Params(128, 1024, 4096)] |
|||
public int Length { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Creates deterministic source and destination buffers outside the measured operations.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.x = CreateSamples(this.Length, 1); |
|||
this.y = CreateSamples(this.Length, 2); |
|||
this.z = CreateSamples(this.Length, 3); |
|||
this.w = CreateSamples(this.Length, 4); |
|||
this.packed3 = new Vector3[this.Length]; |
|||
this.destination3 = new float[this.Length * 3]; |
|||
this.destination4 = new float[this.Length * 4]; |
|||
|
|||
for (int i = 0; i < this.packed3.Length; i++) |
|||
{ |
|||
this.packed3[i] = new Vector3(this.x[i], this.y[i], this.z[i]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the previous scalar three-plane normalization and interleave loop.
|
|||
/// </summary>
|
|||
/// <returns>The last destination value, keeping the writes observable.</returns>
|
|||
[Benchmark(Baseline = true)] |
|||
[BenchmarkCategory("Pack3")] |
|||
public float PackedNormalizeInterleave3Scalar() |
|||
{ |
|||
JpegColorPackingScalar.PackedNormalizeInterleave3(this.x, this.y, this.z, this.destination3, Scale); |
|||
|
|||
return this.destination3[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the SIMD three-plane normalization and interleave implementation.
|
|||
/// </summary>
|
|||
/// <returns>The last destination value, keeping the writes observable.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("Pack3")] |
|||
public float PackedNormalizeInterleave3Simd() |
|||
{ |
|||
JpegColorConverterBase.PackedNormalizeInterleave3(this.x, this.y, this.z, this.destination3, Scale); |
|||
|
|||
return this.destination3[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the previous scalar packed-three-channel deinterleave loop.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing the last value written to every destination plane.</returns>
|
|||
[Benchmark(Baseline = true)] |
|||
[BenchmarkCategory("Unpack3")] |
|||
public float UnpackDeinterleave3Scalar() |
|||
{ |
|||
JpegColorPackingScalar.UnpackDeinterleave3(this.packed3, this.x, this.y, this.z); |
|||
|
|||
return this.x[^1] + this.y[^1] + this.z[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the SIMD packed-three-channel deinterleave implementation.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing the last value written to every destination plane.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("Unpack3")] |
|||
public float UnpackDeinterleave3Simd() |
|||
{ |
|||
JpegColorConverterBase.UnpackDeinterleave3(this.packed3, this.x, this.y, this.z); |
|||
|
|||
return this.x[^1] + this.y[^1] + this.z[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the previous scalar four-plane normalization and interleave loop.
|
|||
/// </summary>
|
|||
/// <returns>The last destination value, keeping the writes observable.</returns>
|
|||
[Benchmark(Baseline = true)] |
|||
[BenchmarkCategory("Pack4")] |
|||
public float PackedNormalizeInterleave4Scalar() |
|||
{ |
|||
JpegColorPackingScalar.PackedNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); |
|||
|
|||
return this.destination4[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the SIMD four-plane normalization and interleave implementation.
|
|||
/// </summary>
|
|||
/// <returns>The last destination value, keeping the writes observable.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("Pack4")] |
|||
public float PackedNormalizeInterleave4Simd() |
|||
{ |
|||
JpegColorConverterBase.PackedNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); |
|||
|
|||
return this.destination4[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the previous scalar inverted four-plane normalization and interleave loop.
|
|||
/// </summary>
|
|||
/// <returns>The last destination value, keeping the writes observable.</returns>
|
|||
[Benchmark(Baseline = true)] |
|||
[BenchmarkCategory("InvertPack4")] |
|||
public float PackedInvertNormalizeInterleave4Scalar() |
|||
{ |
|||
JpegColorPackingScalar.PackedInvertNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); |
|||
|
|||
return this.destination4[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures the SIMD inverted four-plane normalization and interleave implementation.
|
|||
/// </summary>
|
|||
/// <returns>The last destination value, keeping the writes observable.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("InvertPack4")] |
|||
public float PackedInvertNormalizeInterleave4Simd() |
|||
{ |
|||
JpegColorConverterBase.PackedInvertNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); |
|||
|
|||
return this.destination4[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates deterministic, non-integral samples for one component plane.
|
|||
/// </summary>
|
|||
/// <param name="length">The number of samples to create.</param>
|
|||
/// <param name="component">The one-based component number used to distinguish the plane.</param>
|
|||
/// <returns>The generated samples.</returns>
|
|||
private static float[] CreateSamples(int length, int component) |
|||
{ |
|||
float[] samples = new float[length]; |
|||
|
|||
for (int i = 0; i < samples.Length; i++) |
|||
{ |
|||
samples[i] = (((i * 37) + (component * 53)) % 251) + (component * 0.125F); |
|||
} |
|||
|
|||
return samples; |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
|||
|
|||
/// <summary>
|
|||
/// Preserves the scalar JPEG packing loops that preceded the SIMD investigation.
|
|||
/// </summary>
|
|||
internal static class JpegColorPackingScalar |
|||
{ |
|||
/// <summary>
|
|||
/// Normalizes and interleaves three planar component lanes using the previous scalar implementation.
|
|||
/// </summary>
|
|||
/// <param name="xLane">The planar X components.</param>
|
|||
/// <param name="yLane">The planar Y components.</param>
|
|||
/// <param name="zLane">The planar Z components.</param>
|
|||
/// <param name="packed">The destination ordered as consecutive XYZ triples.</param>
|
|||
/// <param name="scale">The normalization factor applied to every component.</param>
|
|||
public static void PackedNormalizeInterleave3(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, Span<float> packed, float scale) |
|||
{ |
|||
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
|||
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
|||
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
|||
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
|||
|
|||
for (nuint i = 0; i < (nuint)xLane.Length; i++) |
|||
{ |
|||
nuint packedOffset = i * 3; |
|||
Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, i) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, i) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, i) * scale; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Deinterleaves packed XYZ values using the previous scalar implementation.
|
|||
/// </summary>
|
|||
/// <param name="packed">The source ordered as consecutive XYZ triples.</param>
|
|||
/// <param name="xLane">The destination X components.</param>
|
|||
/// <param name="yLane">The destination Y components.</param>
|
|||
/// <param name="zLane">The destination Z components.</param>
|
|||
public static void UnpackDeinterleave3(ReadOnlySpan<Vector3> packed, Span<float> xLane, Span<float> yLane, Span<float> zLane) |
|||
{ |
|||
ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Vector3, float>(packed)); |
|||
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
|||
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
|||
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
|||
|
|||
for (nuint i = 0; i < (nuint)packed.Length; i++) |
|||
{ |
|||
nuint packedOffset = i * 3; |
|||
Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset); |
|||
Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 1); |
|||
Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 2); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Normalizes and interleaves four planar component lanes using the previous scalar implementation.
|
|||
/// </summary>
|
|||
/// <param name="xLane">The planar X components.</param>
|
|||
/// <param name="yLane">The planar Y components.</param>
|
|||
/// <param name="zLane">The planar Z components.</param>
|
|||
/// <param name="wLane">The planar W components.</param>
|
|||
/// <param name="packed">The destination ordered as consecutive XYZW groups.</param>
|
|||
/// <param name="maximumValue">The maximum component value used to normalize each component.</param>
|
|||
public static void PackedNormalizeInterleave4(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, ReadOnlySpan<float> wLane, Span<float> packed, float maximumValue) |
|||
{ |
|||
float scale = 1F / maximumValue; |
|||
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
|||
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
|||
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
|||
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); |
|||
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
|||
|
|||
for (nuint i = 0; i < (nuint)xLane.Length; i++) |
|||
{ |
|||
nuint packedOffset = i * 4; |
|||
Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, i) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, i) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, i) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 3) = Unsafe.Add(ref wLaneRef, i) * scale; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Inverts, normalizes, and interleaves four planar lanes using the previous scalar implementation.
|
|||
/// </summary>
|
|||
/// <param name="xLane">The inverted planar X components.</param>
|
|||
/// <param name="yLane">The inverted planar Y components.</param>
|
|||
/// <param name="zLane">The inverted planar Z components.</param>
|
|||
/// <param name="wLane">The inverted planar W components.</param>
|
|||
/// <param name="packed">The destination ordered as consecutive conventional XYZW groups.</param>
|
|||
/// <param name="maximumValue">The maximum component value used for inversion and normalization.</param>
|
|||
public static void PackedInvertNormalizeInterleave4(ReadOnlySpan<float> xLane, ReadOnlySpan<float> yLane, ReadOnlySpan<float> zLane, ReadOnlySpan<float> wLane, Span<float> packed, float maximumValue) |
|||
{ |
|||
float scale = 1F / maximumValue; |
|||
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); |
|||
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); |
|||
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); |
|||
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); |
|||
ref float packedRef = ref MemoryMarshal.GetReference(packed); |
|||
|
|||
for (nuint i = 0; i < (nuint)xLane.Length; i++) |
|||
{ |
|||
nuint packedOffset = i * 4; |
|||
Unsafe.Add(ref packedRef, packedOffset) = (maximumValue - Unsafe.Add(ref xLaneRef, i)) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 1) = (maximumValue - Unsafe.Add(ref yLaneRef, i)) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 2) = (maximumValue - Unsafe.Add(ref zLaneRef, i)) * scale; |
|||
Unsafe.Add(ref packedRef, packedOffset + 3) = (maximumValue - Unsafe.Add(ref wLaneRef, i)) * scale; |
|||
} |
|||
} |
|||
} |
|||
@ -1,244 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
using BenchmarkDotNet.Attributes; |
|||
using BenchmarkDotNet.Columns; |
|||
using BenchmarkDotNet.Configs; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; |
|||
|
|||
/// <summary>
|
|||
/// Exposes every YCbCr operator overload directly to the disassembly diagnoser.
|
|||
/// </summary>
|
|||
[Config(typeof(Config.Analysis))] |
|||
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
|||
[CategoriesColumn] |
|||
public class YCbCrOperatorAssembly |
|||
{ |
|||
private const float MaximumValue = 255F; |
|||
private const float HalfValue = 128F; |
|||
private const float Scale = 1F / MaximumValue; |
|||
|
|||
private float scalarC0 = 64F; |
|||
private float scalarC1 = 96F; |
|||
private float scalarC2 = 160F; |
|||
|
|||
private readonly Vector128<float> vector128C0 = Vector128.Create(64F); |
|||
private readonly Vector128<float> vector128C1 = Vector128.Create(96F); |
|||
private readonly Vector128<float> vector128C2 = Vector128.Create(160F); |
|||
private readonly Vector128<float> vector128Maximum = Vector128.Create(MaximumValue); |
|||
private readonly Vector128<float> vector128Half = Vector128.Create(HalfValue); |
|||
private readonly Vector128<float> vector128Scale = Vector128.Create(Scale); |
|||
|
|||
private readonly Vector256<float> vector256C0 = Vector256.Create(64F); |
|||
private readonly Vector256<float> vector256C1 = Vector256.Create(96F); |
|||
private readonly Vector256<float> vector256C2 = Vector256.Create(160F); |
|||
private readonly Vector256<float> vector256Maximum = Vector256.Create(MaximumValue); |
|||
private readonly Vector256<float> vector256Half = Vector256.Create(HalfValue); |
|||
private readonly Vector256<float> vector256Scale = Vector256.Create(Scale); |
|||
|
|||
private readonly Vector512<float> vector512C0 = Vector512.Create(64F); |
|||
private readonly Vector512<float> vector512C1 = Vector512.Create(96F); |
|||
private readonly Vector512<float> vector512C2 = Vector512.Create(160F); |
|||
private readonly Vector512<float> vector512Maximum = Vector512.Create(MaximumValue); |
|||
private readonly Vector512<float> vector512Half = Vector512.Create(HalfValue); |
|||
private readonly Vector512<float> vector512Scale = Vector512.Create(Scale); |
|||
|
|||
/// <summary>
|
|||
/// Invokes the scalar JPEG-to-RGB operator.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing all three converted channels.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("ToRgb")] |
|||
public float ToRgbScalar() |
|||
{ |
|||
float c0 = this.scalarC0; |
|||
float c1 = this.scalarC1; |
|||
float c2 = this.scalarC2; |
|||
|
|||
JpegColorConverterBase.YCbCrOperator.ConvertToRgb( |
|||
ref c0, |
|||
ref c1, |
|||
ref c2, |
|||
0, |
|||
MaximumValue, |
|||
HalfValue, |
|||
Scale); |
|||
|
|||
// Returning the channel sum keeps every output live in the generated assembly.
|
|||
return c0 + c1 + c2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes the Vector128 JPEG-to-RGB operator.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing all three converted channel vectors.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("ToRgb")] |
|||
public Vector128<float> ToRgbVector128() |
|||
{ |
|||
Vector128<float> c0 = this.vector128C0; |
|||
Vector128<float> c1 = this.vector128C1; |
|||
Vector128<float> c2 = this.vector128C2; |
|||
|
|||
JpegColorConverterBase.YCbCrOperator.ConvertToRgb( |
|||
ref c0, |
|||
ref c1, |
|||
ref c2, |
|||
default, |
|||
this.vector128Maximum, |
|||
this.vector128Half, |
|||
this.vector128Scale); |
|||
|
|||
// The vector sum makes all RGB results observable without adding stores to the measured body.
|
|||
return c0 + c1 + c2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes the Vector256 JPEG-to-RGB operator.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing all three converted channel vectors.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("ToRgb")] |
|||
public Vector256<float> ToRgbVector256() |
|||
{ |
|||
Vector256<float> c0 = this.vector256C0; |
|||
Vector256<float> c1 = this.vector256C1; |
|||
Vector256<float> c2 = this.vector256C2; |
|||
|
|||
JpegColorConverterBase.YCbCrOperator.ConvertToRgb( |
|||
ref c0, |
|||
ref c1, |
|||
ref c2, |
|||
default, |
|||
this.vector256Maximum, |
|||
this.vector256Half, |
|||
this.vector256Scale); |
|||
|
|||
// The vector sum makes all RGB results observable without adding stores to the measured body.
|
|||
return c0 + c1 + c2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes the Vector512 JPEG-to-RGB operator.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing all three converted channel vectors.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("ToRgb")] |
|||
public Vector512<float> ToRgbVector512() |
|||
{ |
|||
Vector512<float> c0 = this.vector512C0; |
|||
Vector512<float> c1 = this.vector512C1; |
|||
Vector512<float> c2 = this.vector512C2; |
|||
|
|||
JpegColorConverterBase.YCbCrOperator.ConvertToRgb( |
|||
ref c0, |
|||
ref c1, |
|||
ref c2, |
|||
default, |
|||
this.vector512Maximum, |
|||
this.vector512Half, |
|||
this.vector512Scale); |
|||
|
|||
// The vector sum makes all RGB results observable without adding stores to the measured body.
|
|||
return c0 + c1 + c2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes the scalar RGB-to-JPEG operator.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing all converted components.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("FromRgb")] |
|||
public float FromRgbScalar() |
|||
{ |
|||
JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( |
|||
this.scalarC0, |
|||
this.scalarC1, |
|||
this.scalarC2, |
|||
MaximumValue, |
|||
HalfValue, |
|||
Scale, |
|||
out float c0, |
|||
out float c1, |
|||
out float c2, |
|||
out float c3); |
|||
|
|||
// c3 is deliberately included so a future four-component implementation remains observable.
|
|||
return c0 + c1 + c2 + c3; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes the Vector128 RGB-to-JPEG operator.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing all converted component vectors.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("FromRgb")] |
|||
public Vector128<float> FromRgbVector128() |
|||
{ |
|||
JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( |
|||
this.vector128C0, |
|||
this.vector128C1, |
|||
this.vector128C2, |
|||
this.vector128Maximum, |
|||
this.vector128Half, |
|||
this.vector128Scale, |
|||
out Vector128<float> c0, |
|||
out Vector128<float> c1, |
|||
out Vector128<float> c2, |
|||
out Vector128<float> c3); |
|||
|
|||
// Include all planar results in the returned vector so the JIT retains every calculation.
|
|||
return c0 + c1 + c2 + c3; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes the Vector256 RGB-to-JPEG operator.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing all converted component vectors.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("FromRgb")] |
|||
public Vector256<float> FromRgbVector256() |
|||
{ |
|||
JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( |
|||
this.vector256C0, |
|||
this.vector256C1, |
|||
this.vector256C2, |
|||
this.vector256Maximum, |
|||
this.vector256Half, |
|||
this.vector256Scale, |
|||
out Vector256<float> c0, |
|||
out Vector256<float> c1, |
|||
out Vector256<float> c2, |
|||
out Vector256<float> c3); |
|||
|
|||
// Include all planar results in the returned vector so the JIT retains every calculation.
|
|||
return c0 + c1 + c2 + c3; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes the Vector512 RGB-to-JPEG operator.
|
|||
/// </summary>
|
|||
/// <returns>A checksum containing all converted component vectors.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("FromRgb")] |
|||
public Vector512<float> FromRgbVector512() |
|||
{ |
|||
JpegColorConverterBase.YCbCrOperator.ConvertFromRgb( |
|||
this.vector512C0, |
|||
this.vector512C1, |
|||
this.vector512C2, |
|||
this.vector512Maximum, |
|||
this.vector512Half, |
|||
this.vector512Scale, |
|||
out Vector512<float> c0, |
|||
out Vector512<float> c1, |
|||
out Vector512<float> c2, |
|||
out Vector512<float> c3); |
|||
|
|||
// Include all planar results in the returned vector so the JIT retains every calculation.
|
|||
return c0 + c1 + c2 + c3; |
|||
} |
|||
} |
|||
@ -1,64 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Png.Filters; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; |
|||
|
|||
/// <summary>
|
|||
/// Exposes every normalized PNG filter for assembly inspection.
|
|||
/// </summary>
|
|||
[Config(typeof(Config.Analysis))] |
|||
public class PngFilterEncodeAssembly |
|||
{ |
|||
private const int BytesPerPixel = 4; |
|||
private const int Count = 180; |
|||
|
|||
private byte[] scanline; |
|||
private byte[] previousScanline; |
|||
private byte[] result; |
|||
|
|||
/// <summary>
|
|||
/// Creates inputs whose suffix exercises 512-, 256-, and 128-bit register widths.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.scanline = new byte[Count]; |
|||
this.previousScanline = new byte[Count]; |
|||
this.result = new byte[Count + 1]; |
|||
|
|||
Random random = new(12345678); |
|||
random.NextBytes(this.scanline); |
|||
random.NextBytes(this.previousScanline); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Executes the normalized Sub encoder.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Sub() |
|||
=> SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out _); |
|||
|
|||
/// <summary>
|
|||
/// Executes the normalized Up encoder.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Up() |
|||
=> UpFilter.Encode(this.scanline, this.previousScanline, this.result, out _); |
|||
|
|||
/// <summary>
|
|||
/// Executes the normalized Average encoder.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Average() |
|||
=> AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _); |
|||
|
|||
/// <summary>
|
|||
/// Executes the normalized Paeth encoder.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Paeth() |
|||
=> PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _); |
|||
} |
|||
@ -1,175 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; |
|||
|
|||
/// <summary>
|
|||
/// Exposes every floating-point tensor compatibility operation for assembly inspection.
|
|||
/// </summary>
|
|||
[Config(typeof(Config.Analysis))] |
|||
public class TensorPrimitivesAssembly |
|||
{ |
|||
private const int Count = 2048; |
|||
|
|||
private readonly float[] x = new float[Count]; |
|||
private readonly float[] y = new float[Count]; |
|||
private readonly float[] destination = new float[Count]; |
|||
|
|||
/// <summary>
|
|||
/// Populates the input spans with deterministic non-uniform values.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
for (int i = 0; i < Count; i++) |
|||
{ |
|||
this.x[i] = ((i * 17) % 251) + 1; |
|||
this.y[i] = ((i * 29) % 251) + 1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds two floating-point spans.
|
|||
/// </summary>
|
|||
/// <returns>The first result, which keeps the destination observable.</returns>
|
|||
[Benchmark] |
|||
public float Add() |
|||
{ |
|||
TensorPrimitives_.Add<float>(this.x, this.y, this.destination); |
|||
return this.destination[0]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps a floating-point span between scalar bounds.
|
|||
/// </summary>
|
|||
/// <returns>The first result, which keeps the destination observable.</returns>
|
|||
[Benchmark] |
|||
public float Clamp() |
|||
{ |
|||
TensorPrimitives_.Clamp(this.x, 64F, 128F, this.destination); |
|||
return this.destination[0]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a floating-point span by a scalar.
|
|||
/// </summary>
|
|||
/// <returns>The first result, which keeps the destination observable.</returns>
|
|||
[Benchmark] |
|||
public float Divide() |
|||
{ |
|||
TensorPrimitives_.Divide(this.x, 4096F, this.destination); |
|||
return this.destination[0]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the element-wise maximum of a floating-point span and a scalar.
|
|||
/// </summary>
|
|||
/// <returns>The first result, which keeps the destination observable.</returns>
|
|||
[Benchmark] |
|||
public float Max() |
|||
{ |
|||
TensorPrimitives_.Max(this.x, 64F, this.destination); |
|||
return this.destination[0]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiplies a floating-point span by a scalar.
|
|||
/// </summary>
|
|||
/// <returns>The first result, which keeps the destination observable.</returns>
|
|||
[Benchmark] |
|||
public float Multiply() |
|||
{ |
|||
TensorPrimitives_.Multiply(this.x, 0.5F, this.destination); |
|||
return this.destination[0]; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Exposes integral addition specializations for assembly inspection.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The integral element type.</typeparam>
|
|||
[Config(typeof(Config.Analysis))] |
|||
[GenericTypeArguments(typeof(byte))] |
|||
[GenericTypeArguments(typeof(uint))] |
|||
public class TensorPrimitivesIntegralAddAssembly<T> |
|||
where T : unmanaged, INumber<T> |
|||
{ |
|||
private const int Count = 2048; |
|||
|
|||
private readonly T[] x = new T[Count]; |
|||
private readonly T[] y = new T[Count]; |
|||
private readonly T[] destination = new T[Count]; |
|||
|
|||
/// <summary>
|
|||
/// Populates the input spans with deterministic non-uniform values.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
for (int i = 0; i < Count; i++) |
|||
{ |
|||
this.x[i] = T.CreateTruncating((i * 17) + 31); |
|||
this.y[i] = T.CreateTruncating((i * 29) + 7); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds two integral spans.
|
|||
/// </summary>
|
|||
/// <returns>The first result, which keeps the destination observable.</returns>
|
|||
[Benchmark] |
|||
public T Add() |
|||
{ |
|||
TensorPrimitives_.Add<T>(this.x, this.y, this.destination); |
|||
return this.destination[0]; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Exposes integral clamp specializations for assembly inspection.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The integral element type.</typeparam>
|
|||
[Config(typeof(Config.Analysis))] |
|||
[GenericTypeArguments(typeof(byte))] |
|||
[GenericTypeArguments(typeof(uint))] |
|||
[GenericTypeArguments(typeof(int))] |
|||
public class TensorPrimitivesIntegralClampAssembly<T> |
|||
where T : unmanaged, INumber<T> |
|||
{ |
|||
private const int Count = 2048; |
|||
|
|||
private readonly T[] source = new T[Count]; |
|||
private readonly T[] destination = new T[Count]; |
|||
private T min; |
|||
private T max; |
|||
|
|||
/// <summary>
|
|||
/// Populates the input span and scalar bounds with deterministic values.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.min = T.CreateTruncating(64); |
|||
this.max = T.CreateTruncating(128); |
|||
|
|||
for (int i = 0; i < Count; i++) |
|||
{ |
|||
this.source[i] = T.CreateTruncating((i * 31) % 257); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps an integral span between scalar bounds.
|
|||
/// </summary>
|
|||
/// <returns>The first result, which keeps the destination observable.</returns>
|
|||
[Benchmark] |
|||
public T Clamp() |
|||
{ |
|||
TensorPrimitives_.Clamp(this.source, this.min, this.max, this.destination); |
|||
return this.destination[0]; |
|||
} |
|||
} |
|||
@ -1,128 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; |
|||
|
|||
/// <summary>
|
|||
/// Exposes every stateless packed-pixel shuffle operator for assembly inspection.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Seven pixels expose the short-input and vector-tail paths. Seventeen pixels execute one
|
|||
/// full intrinsic group and leave one complete pixel for the scalar remainder, making every
|
|||
/// part of each generated traversal observable.
|
|||
/// </remarks>
|
|||
[Config(typeof(Config.Analysis))] |
|||
public class PackedPixelConversionAssembly |
|||
{ |
|||
private byte[] source3; |
|||
private byte[] source4; |
|||
private byte[] destination3; |
|||
private byte[] destination4; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of pixels converted by each invocation.
|
|||
/// </summary>
|
|||
[Params(7, 17)] |
|||
public int Count { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Populates the source buffers with deterministic non-uniform channel values.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.source3 = new byte[this.Count * 3]; |
|||
this.source4 = new byte[this.Count * 4]; |
|||
this.destination3 = new byte[this.Count * 3]; |
|||
this.destination4 = new byte[this.Count * 4]; |
|||
|
|||
new Random(42).NextBytes(this.source3); |
|||
new Random(42).NextBytes(this.source4); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Executes the WXYZ four-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Shuffle4Wxyz() => SimdUtils.Shuffle4<WXYZShuffle4>(this.source4, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the WZYX four-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Shuffle4Wzyx() => SimdUtils.Shuffle4<WZYXShuffle4>(this.source4, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the YZWX four-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Shuffle4Yzwx() => SimdUtils.Shuffle4<YZWXShuffle4>(this.source4, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the ZYXW four-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Shuffle4Zyxw() => SimdUtils.Shuffle4<ZYXWShuffle4>(this.source4, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the XWZY four-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Shuffle4Xwzy() => SimdUtils.Shuffle4<XWZYShuffle4>(this.source4, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the XYZ four-to-three operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Slice3Xyz() => SimdUtils.Shuffle4Slice3<XYZWShuffle4Slice3>(this.source4, this.destination3); |
|||
|
|||
/// <summary>
|
|||
/// Executes the YZW four-to-three operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Slice3Yzw() => SimdUtils.Shuffle4Slice3<YZWXShuffle4Slice3>(this.source4, this.destination3); |
|||
|
|||
/// <summary>
|
|||
/// Executes the WZY four-to-three operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Slice3Wzy() => SimdUtils.Shuffle4Slice3<WZYXShuffle4Slice3>(this.source4, this.destination3); |
|||
|
|||
/// <summary>
|
|||
/// Executes the ZYX four-to-three operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Slice3Zyx() => SimdUtils.Shuffle4Slice3<ZYXWShuffle4Slice3>(this.source4, this.destination3); |
|||
|
|||
/// <summary>
|
|||
/// Executes the XYZW three-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Pad4Xyzw() => SimdUtils.Pad3Shuffle4<XYZWPad3Shuffle4>(this.source3, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the WXYZ three-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Pad4Wxyz() => SimdUtils.Pad3Shuffle4<WXYZPad3Shuffle4>(this.source3, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the WZYX three-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Pad4Wzyx() => SimdUtils.Pad3Shuffle4<WZYXPad3Shuffle4>(this.source3, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the ZYXW three-to-four operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Pad4Zyxw() => SimdUtils.Pad3Shuffle4<ZYXWPad3Shuffle4>(this.source3, this.destination4); |
|||
|
|||
/// <summary>
|
|||
/// Executes the ZYX three-to-three operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void Shuffle3Zyx() => SimdUtils.Shuffle3<ZYXShuffle3>(this.source3, this.destination3); |
|||
} |
|||
@ -1,59 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.PixelFormats.Utils; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; |
|||
|
|||
/// <summary>
|
|||
/// Exposes every stateful affine operator and traversal remainder for assembly inspection.
|
|||
/// </summary>
|
|||
[Config(typeof(Config.Analysis))] |
|||
public class Vector4AffineTransformAssembly |
|||
{ |
|||
private static readonly Vector4 Multiplier = new(255F, 2F, 65535F, .5F); |
|||
private static readonly Vector4 Offset = new(17F, -1F, 32768F, 3F); |
|||
private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F); |
|||
|
|||
private Vector4[] vectors; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of vectors transformed by each invocation.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Three vectors exercise the 256- and 128-bit stages. Seventeen vectors exercise
|
|||
/// the 512-bit loop and leave one vector for the 128-bit remainder.
|
|||
/// </remarks>
|
|||
[Params(3, 17)] |
|||
public int Count { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Creates a non-uniform input buffer.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.vectors = new Vector4[this.Count]; |
|||
|
|||
for (int i = 0; i < this.vectors.Length; i++) |
|||
{ |
|||
this.vectors[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Executes the multiply-then-add stateful operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void MultiplyThenAdd() |
|||
=> Vector4Converters.MultiplyThenAdd(this.vectors, Multiplier, Offset); |
|||
|
|||
/// <summary>
|
|||
/// Executes the add-then-divide stateful operator.
|
|||
/// </summary>
|
|||
[Benchmark] |
|||
public void AddThenDivide() |
|||
=> Vector4Converters.AddThenDivide(this.vectors, Offset, Divisor); |
|||
} |
|||
@ -1,327 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders; |
|||
|
|||
/// <summary>
|
|||
/// Exposes every shared pixel-blender traversal shape for assembly inspection.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Seven pixels leave three scalar pixels after AVX-512 or one scalar pixel after AVX2, so each
|
|||
/// hardware job contains both its widest supported loop and the portable Vector4 remainder.
|
|||
/// </remarks>
|
|||
[Config(typeof(Config.Analysis))] |
|||
public class PixelBlenderTraversalAssembly |
|||
{ |
|||
private const int Count = 7; |
|||
private const float Amount = .625F; |
|||
|
|||
private readonly ExposedNormalSrcOverBlender blender = new(); |
|||
private readonly Vector4[] destination = new Vector4[Count]; |
|||
private readonly Vector4[] background = new Vector4[Count]; |
|||
private readonly Vector4[] source = new Vector4[Count]; |
|||
private readonly float[] amounts = new float[Count]; |
|||
private readonly float[] coverage = new float[Count]; |
|||
private Vector4 constantSource; |
|||
|
|||
/// <summary>
|
|||
/// Populates all lanes with deterministic, non-constant values.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
Random random = new(42); |
|||
|
|||
for (int i = 0; i < Count; i++) |
|||
{ |
|||
// Distinct RGBA lanes make incorrect pixel grouping visible in both results and assembly.
|
|||
this.background[i] = CreatePixel(random); |
|||
this.source[i] = CreatePixel(random); |
|||
this.amounts[i] = random.NextSingle(); |
|||
this.coverage[i] = random.NextSingle(); |
|||
} |
|||
|
|||
this.constantSource = CreatePixel(random); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends a source row with one shared amount.
|
|||
/// </summary>
|
|||
/// <returns>The final destination pixel.</returns>
|
|||
[Benchmark] |
|||
public Vector4 SourceSpanScalarAmount() |
|||
{ |
|||
this.blender.BlendSourceSpanScalarAmount( |
|||
this.destination, |
|||
this.background, |
|||
this.source, |
|||
Amount); |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends a constant source with one shared amount.
|
|||
/// </summary>
|
|||
/// <returns>The final destination pixel.</returns>
|
|||
[Benchmark] |
|||
public Vector4 ConstantSourceScalarAmount() |
|||
{ |
|||
this.blender.BlendConstantSourceScalarAmount( |
|||
this.destination, |
|||
this.background, |
|||
this.constantSource, |
|||
Amount); |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends a source row with per-pixel amounts.
|
|||
/// </summary>
|
|||
/// <returns>The final destination pixel.</returns>
|
|||
[Benchmark] |
|||
public Vector4 SourceSpanAmountSpan() |
|||
{ |
|||
this.blender.BlendSourceSpanAmountSpan( |
|||
this.destination, |
|||
this.background, |
|||
this.source, |
|||
this.amounts); |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends a constant source with per-pixel amounts.
|
|||
/// </summary>
|
|||
/// <returns>The final destination pixel.</returns>
|
|||
[Benchmark] |
|||
public Vector4 ConstantSourceAmountSpan() |
|||
{ |
|||
this.blender.BlendConstantSourceAmountSpan( |
|||
this.destination, |
|||
this.background, |
|||
this.constantSource, |
|||
this.amounts); |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends a source row with one shared amount and per-pixel coverage.
|
|||
/// </summary>
|
|||
/// <returns>The final destination pixel.</returns>
|
|||
[Benchmark] |
|||
public Vector4 SourceSpanScalarAmountCoverage() |
|||
{ |
|||
this.blender.BlendSourceSpanScalarAmountCoverage( |
|||
this.destination, |
|||
this.background, |
|||
this.source, |
|||
Amount, |
|||
this.coverage); |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends a constant source with one shared amount and per-pixel coverage.
|
|||
/// </summary>
|
|||
/// <returns>The final destination pixel.</returns>
|
|||
[Benchmark] |
|||
public Vector4 ConstantSourceScalarAmountCoverage() |
|||
{ |
|||
this.blender.BlendConstantSourceScalarAmountCoverage( |
|||
this.destination, |
|||
this.background, |
|||
this.constantSource, |
|||
Amount, |
|||
this.coverage); |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends a source row with per-pixel amounts and coverage.
|
|||
/// </summary>
|
|||
/// <returns>The final destination pixel.</returns>
|
|||
[Benchmark] |
|||
public Vector4 SourceSpanAmountSpanCoverage() |
|||
{ |
|||
this.blender.BlendSourceSpanAmountSpanCoverage( |
|||
this.destination, |
|||
this.background, |
|||
this.source, |
|||
this.amounts, |
|||
this.coverage); |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends a constant source with per-pixel amounts and coverage.
|
|||
/// </summary>
|
|||
/// <returns>The final destination pixel.</returns>
|
|||
[Benchmark] |
|||
public Vector4 ConstantSourceAmountSpanCoverage() |
|||
{ |
|||
this.blender.BlendConstantSourceAmountSpanCoverage( |
|||
this.destination, |
|||
this.background, |
|||
this.constantSource, |
|||
this.amounts, |
|||
this.coverage); |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates one non-constant RGBA sample.
|
|||
/// </summary>
|
|||
/// <param name="random">The deterministic value source.</param>
|
|||
/// <returns>The sample pixel.</returns>
|
|||
private static Vector4 CreatePixel(Random random) |
|||
=> new(random.NextSingle(), random.NextSingle(), random.NextSingle(), random.NextSingle()); |
|||
|
|||
/// <summary>
|
|||
/// Exposes the protected shared traversal overloads without adding benchmark hooks to production APIs.
|
|||
/// </summary>
|
|||
private sealed class ExposedNormalSrcOverBlender : |
|||
DefaultPixelBlender<RgbaVector, DefaultPixelBlenderOperators.NormalSrcOver> |
|||
{ |
|||
/// <summary>
|
|||
/// Invokes the source-span, scalar-amount traversal.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="background">The background vectors.</param>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="amount">The shared source amount.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void BlendSourceSpanScalarAmount( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
ReadOnlySpan<Vector4> source, |
|||
float amount) |
|||
=> this.BlendFunction(destination, background, source, amount); |
|||
|
|||
/// <summary>
|
|||
/// Invokes the constant-source, scalar-amount traversal.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="background">The background vectors.</param>
|
|||
/// <param name="source">The constant source vector.</param>
|
|||
/// <param name="amount">The shared source amount.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void BlendConstantSourceScalarAmount( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
Vector4 source, |
|||
float amount) |
|||
=> this.BlendFunction(destination, background, source, amount); |
|||
|
|||
/// <summary>
|
|||
/// Invokes the source-span, amount-span traversal.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="background">The background vectors.</param>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="amount">The per-pixel source amounts.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void BlendSourceSpanAmountSpan( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
ReadOnlySpan<Vector4> source, |
|||
ReadOnlySpan<float> amount) |
|||
=> this.BlendFunction(destination, background, source, amount); |
|||
|
|||
/// <summary>
|
|||
/// Invokes the constant-source, amount-span traversal.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="background">The background vectors.</param>
|
|||
/// <param name="source">The constant source vector.</param>
|
|||
/// <param name="amount">The per-pixel source amounts.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void BlendConstantSourceAmountSpan( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
Vector4 source, |
|||
ReadOnlySpan<float> amount) |
|||
=> this.BlendFunction(destination, background, source, amount); |
|||
|
|||
/// <summary>
|
|||
/// Invokes the source-span, scalar-amount traversal with coverage.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="background">The background vectors.</param>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="amount">The shared source amount.</param>
|
|||
/// <param name="coverage">The per-pixel coverage values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void BlendSourceSpanScalarAmountCoverage( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
ReadOnlySpan<Vector4> source, |
|||
float amount, |
|||
ReadOnlySpan<float> coverage) |
|||
=> this.BlendWithCoverageFunction(destination, background, source, amount, coverage); |
|||
|
|||
/// <summary>
|
|||
/// Invokes the constant-source, scalar-amount traversal with coverage.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="background">The background vectors.</param>
|
|||
/// <param name="source">The constant source vector.</param>
|
|||
/// <param name="amount">The shared source amount.</param>
|
|||
/// <param name="coverage">The per-pixel coverage values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void BlendConstantSourceScalarAmountCoverage( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
Vector4 source, |
|||
float amount, |
|||
ReadOnlySpan<float> coverage) |
|||
=> this.BlendWithCoverageFunction(destination, background, source, amount, coverage); |
|||
|
|||
/// <summary>
|
|||
/// Invokes the source-span, amount-span traversal with coverage.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="background">The background vectors.</param>
|
|||
/// <param name="source">The source vectors.</param>
|
|||
/// <param name="amount">The per-pixel source amounts.</param>
|
|||
/// <param name="coverage">The per-pixel coverage values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void BlendSourceSpanAmountSpanCoverage( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
ReadOnlySpan<Vector4> source, |
|||
ReadOnlySpan<float> amount, |
|||
ReadOnlySpan<float> coverage) |
|||
=> this.BlendWithCoverageFunction(destination, background, source, amount, coverage); |
|||
|
|||
/// <summary>
|
|||
/// Invokes the constant-source, amount-span traversal with coverage.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination vectors.</param>
|
|||
/// <param name="background">The background vectors.</param>
|
|||
/// <param name="source">The constant source vector.</param>
|
|||
/// <param name="amount">The per-pixel source amounts.</param>
|
|||
/// <param name="coverage">The per-pixel coverage values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void BlendConstantSourceAmountSpanCoverage( |
|||
Span<Vector4> destination, |
|||
ReadOnlySpan<Vector4> background, |
|||
Vector4 source, |
|||
ReadOnlySpan<float> amount, |
|||
ReadOnlySpan<float> coverage) |
|||
=> this.BlendWithCoverageFunction(destination, background, source, amount, coverage); |
|||
} |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Jpg; |
|||
|
|||
/// <summary>
|
|||
/// Tests the planar and packed buffer transformations used around JPEG color-profile conversion.
|
|||
/// </summary>
|
|||
[Trait("Format", "Jpg")] |
|||
public class JpegColorPackingTests |
|||
{ |
|||
private static readonly int[] Lengths = [0, 1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 33, 129]; |
|||
|
|||
/// <summary>
|
|||
/// Verifies every packing operation against its scalar definition with and without hardware intrinsics.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void PackingOperationsMatchScalarDefinitions() |
|||
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePackingOperations, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); |
|||
|
|||
/// <summary>
|
|||
/// Exercises every SIMD transition and scalar remainder for the packing operations.
|
|||
/// </summary>
|
|||
private static void ValidatePackingOperations() |
|||
{ |
|||
foreach (int length in Lengths) |
|||
{ |
|||
ValidatePackedNormalizeInterleave3(length); |
|||
ValidateUnpackDeinterleave3(length); |
|||
ValidatePackedNormalizeInterleave4(length); |
|||
ValidatePackedInvertNormalizeInterleave4(length); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares normalized three-plane interleaving with the original scalar loop.
|
|||
/// </summary>
|
|||
/// <param name="length">The number of samples in each component plane.</param>
|
|||
private static void ValidatePackedNormalizeInterleave3(int length) |
|||
{ |
|||
const float scale = 1F / 255F; |
|||
float[] x = CreateSamples(length, 1); |
|||
float[] y = CreateSamples(length, 2); |
|||
float[] z = CreateSamples(length, 3); |
|||
float[] expected = new float[length * 3]; |
|||
float[] actual = new float[length * 3]; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
int packedOffset = i * 3; |
|||
expected[packedOffset] = x[i] * scale; |
|||
expected[packedOffset + 1] = y[i] * scale; |
|||
expected[packedOffset + 2] = z[i] * scale; |
|||
} |
|||
|
|||
JpegColorConverterBase.PackedNormalizeInterleave3(x, y, z, actual, scale); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares packed three-channel deinterleaving with the original scalar loop.
|
|||
/// </summary>
|
|||
/// <param name="length">The number of packed values.</param>
|
|||
private static void ValidateUnpackDeinterleave3(int length) |
|||
{ |
|||
Vector3[] packed = new Vector3[length]; |
|||
float[] expectedX = CreateSamples(length, 1); |
|||
float[] expectedY = CreateSamples(length, 2); |
|||
float[] expectedZ = CreateSamples(length, 3); |
|||
float[] actualX = new float[length]; |
|||
float[] actualY = new float[length]; |
|||
float[] actualZ = new float[length]; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
packed[i] = new Vector3(expectedX[i], expectedY[i], expectedZ[i]); |
|||
} |
|||
|
|||
JpegColorConverterBase.UnpackDeinterleave3(packed, actualX, actualY, actualZ); |
|||
|
|||
Assert.Equal(expectedX, actualX); |
|||
Assert.Equal(expectedY, actualY); |
|||
Assert.Equal(expectedZ, actualZ); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares normalized four-plane interleaving with the original scalar loop.
|
|||
/// </summary>
|
|||
/// <param name="length">The number of samples in each component plane.</param>
|
|||
private static void ValidatePackedNormalizeInterleave4(int length) |
|||
{ |
|||
const float maximumValue = 255F; |
|||
const float scale = 1F / maximumValue; |
|||
float[] x = CreateSamples(length, 1); |
|||
float[] y = CreateSamples(length, 2); |
|||
float[] z = CreateSamples(length, 3); |
|||
float[] w = CreateSamples(length, 4); |
|||
float[] expected = new float[length * 4]; |
|||
float[] actual = new float[length * 4]; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
int packedOffset = i * 4; |
|||
expected[packedOffset] = x[i] * scale; |
|||
expected[packedOffset + 1] = y[i] * scale; |
|||
expected[packedOffset + 2] = z[i] * scale; |
|||
expected[packedOffset + 3] = w[i] * scale; |
|||
} |
|||
|
|||
JpegColorConverterBase.PackedNormalizeInterleave4(x, y, z, w, actual, maximumValue); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares inverted normalized four-plane interleaving with the original scalar loop.
|
|||
/// </summary>
|
|||
/// <param name="length">The number of samples in each component plane.</param>
|
|||
private static void ValidatePackedInvertNormalizeInterleave4(int length) |
|||
{ |
|||
const float maximumValue = 255F; |
|||
const float scale = 1F / maximumValue; |
|||
float[] x = CreateSamples(length, 1); |
|||
float[] y = CreateSamples(length, 2); |
|||
float[] z = CreateSamples(length, 3); |
|||
float[] w = CreateSamples(length, 4); |
|||
float[] expected = new float[length * 4]; |
|||
float[] actual = new float[length * 4]; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
int packedOffset = i * 4; |
|||
expected[packedOffset] = (maximumValue - x[i]) * scale; |
|||
expected[packedOffset + 1] = (maximumValue - y[i]) * scale; |
|||
expected[packedOffset + 2] = (maximumValue - z[i]) * scale; |
|||
expected[packedOffset + 3] = (maximumValue - w[i]) * scale; |
|||
} |
|||
|
|||
JpegColorConverterBase.PackedInvertNormalizeInterleave4(x, y, z, w, actual, maximumValue); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates deterministic, non-integral sample values that expose lane-order and arithmetic mistakes.
|
|||
/// </summary>
|
|||
/// <param name="length">The number of samples to create.</param>
|
|||
/// <param name="component">The one-based component number used to distinguish each plane.</param>
|
|||
/// <returns>The generated sample values.</returns>
|
|||
private static float[] CreateSamples(int length, int component) |
|||
{ |
|||
float[] samples = new float[length]; |
|||
|
|||
for (int i = 0; i < samples.Length; i++) |
|||
{ |
|||
// The relatively prime multipliers produce a distinct sequence for each plane
|
|||
// while keeping every value inside the eight-bit JPEG sample domain.
|
|||
samples[i] = (((i * 37) + (component * 53)) % 251) + (component * 0.125F); |
|||
} |
|||
|
|||
return samples; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue