// 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}."); } }