// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
///
/// Verifies scalar and SIMD parity for every H.273 transfer characteristic consumed by AV1 color conversion.
///
[Trait("Format", "Avif")]
public class Av1TransferFunctionsTests
{
private static readonly float[] SignalValues =
[
-0.5F,
-0.25F,
-0.081247F,
-0.01F,
0F,
0.0031308F,
0.01F,
0.04045F,
1F / 12F,
0.18F,
0.25F,
0.5F,
0.75F,
1F,
1.25F,
2F,
];
///
/// Gets every defined AV1-signallable transfer characteristic, including the deterministic unspecified fallback.
///
public static TheoryData TransferCharacteristics { get; } = new()
{
(int)ObuTransferCharacteristics.Bt709,
(int)ObuTransferCharacteristics.Unspecified,
(int)ObuTransferCharacteristics.Bt470M,
(int)ObuTransferCharacteristics.Bt470BG,
(int)ObuTransferCharacteristics.Bt601,
(int)ObuTransferCharacteristics.Smpte240,
(int)ObuTransferCharacteristics.Linear,
(int)ObuTransferCharacteristics.Log100,
(int)ObuTransferCharacteristics.Log100Sqrt10,
(int)ObuTransferCharacteristics.Iec61966,
(int)ObuTransferCharacteristics.Bt1361,
(int)ObuTransferCharacteristics.Srgb,
(int)ObuTransferCharacteristics.Bt202010Bit,
(int)ObuTransferCharacteristics.Bt202012Bit,
(int)ObuTransferCharacteristics.Smpte2084,
(int)ObuTransferCharacteristics.Smpte428,
(int)ObuTransferCharacteristics.Hlg,
};
///
/// Verifies that every SIMD width matches the scalar inverse transfer function at curve transitions, extrema, and extended-range values.
///
/// The transfer-characteristic code point under test.
[Theory]
[MemberData(nameof(TransferCharacteristics))]
public void ToLinearSimdMatchesScalar(int transferCharacteristicsValue)
{
ObuTransferCharacteristics transferCharacteristics = (ObuTransferCharacteristics)transferCharacteristicsValue;
float[] expected = SignalValues.Select(value => Av1TransferFunctions.ToLinear(transferCharacteristics, value)).ToArray();
Vector128 vector128 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128.Count)));
Vector256 vector256 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256.Count)));
Vector512 vector512 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector512.Create(SignalValues));
AssertVectorMatchesScalar(expected, vector128, transferCharacteristics);
AssertVectorMatchesScalar(expected, vector256, transferCharacteristics);
AssertVectorMatchesScalar(expected, vector512, transferCharacteristics);
}
///
/// Verifies that every SIMD width matches the scalar forward transfer function at curve transitions, extrema, and extended-range values.
///
/// The transfer-characteristic code point under test.
[Theory]
[MemberData(nameof(TransferCharacteristics))]
public void ToGammaSimdMatchesScalar(int transferCharacteristicsValue)
{
ObuTransferCharacteristics transferCharacteristics = (ObuTransferCharacteristics)transferCharacteristicsValue;
float[] expected = SignalValues.Select(value => Av1TransferFunctions.ToGamma(transferCharacteristics, value)).ToArray();
Vector128 vector128 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128.Count)));
Vector256 vector256 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256.Count)));
Vector512 vector512 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector512.Create(SignalValues));
AssertVectorMatchesScalar(expected, vector128, transferCharacteristics);
AssertVectorMatchesScalar(expected, vector256, transferCharacteristics);
AssertVectorMatchesScalar(expected, vector512, transferCharacteristics);
}
///
/// Compares four SIMD lanes with their scalar results.
///
/// The scalar results.
/// The SIMD results.
/// The transfer characteristic under test.
private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector128 actual, ObuTransferCharacteristics transferCharacteristics)
{
for (int i = 0; i < Vector128.Count; i++)
{
AssertClose(expected[i], actual.GetElement(i), transferCharacteristics, i, 128);
}
}
///
/// Compares eight SIMD lanes with their scalar results.
///
/// The scalar results.
/// The SIMD results.
/// The transfer characteristic under test.
private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector256 actual, ObuTransferCharacteristics transferCharacteristics)
{
for (int i = 0; i < Vector256.Count; i++)
{
AssertClose(expected[i], actual.GetElement(i), transferCharacteristics, i, 256);
}
}
///
/// Compares sixteen SIMD lanes with their scalar results.
///
/// The scalar results.
/// The SIMD results.
/// The transfer characteristic under test.
private static void AssertVectorMatchesScalar(ReadOnlySpan expected, Vector512 actual, ObuTransferCharacteristics transferCharacteristics)
{
for (int i = 0; i < Vector512.Count; i++)
{
AssertClose(expected[i], actual.GetElement(i), transferCharacteristics, i, 512);
}
}
///
/// Verifies one SIMD lane within the tolerance of the runtime vector exponential and logarithm kernels.
///
/// The scalar result.
/// The SIMD result.
/// The transfer characteristic under test.
/// The SIMD lane index.
/// The SIMD register width.
private static void AssertClose(float expected, float actual, ObuTransferCharacteristics transferCharacteristics, int lane, int width)
{
float tolerance = MathF.Max(2E-5F, MathF.Abs(expected) * 2E-5F);
Assert.True(
MathF.Abs(expected - actual) <= tolerance,
$"{transferCharacteristics} at {width}-bit lane {lane}: expected {expected:R}, actual {actual:R}, tolerance {tolerance:R}.");
}
}