// 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.Transform; using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; /// /// Verifies AV1 forward and inverse transform reconstruction across supported sizes, precisions, and intrinsic tiers. /// [Trait("Format", "Avif")] public class Av1InverseTransformTests { /// /// The hardware configurations covering every transform SIMD tier and the scalar fallback. /// private const HwIntrinsics TransformConfigurations = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; /// /// Verifies DCT operator parity across the supported hardware feature levels. /// [Fact] public void DctOperatorsProduceIdenticalScalarAndSimdResults() => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertDctOperatorParity, TransformConfigurations); /// /// Verifies ADST operator parity across the supported hardware feature levels. /// [Fact] public void AdstOperatorsProduceIdenticalScalarAndSimdResults() => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertAdstOperatorParity, TransformConfigurations); /// /// Verifies identity operator parity across the supported hardware feature levels. /// [Fact] public void IdentityOperatorsProduceIdenticalScalarAndSimdResults() => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertIdentityOperatorParity, TransformConfigurations); /// /// Verifies the inverse DCT operators against their scalar implementations. /// private static void AssertDctOperatorParity() { AssertOperatorParity(4); AssertOperatorParity(8); AssertOperatorParity(16); AssertOperatorParity(32); AssertOperatorParity(64); } /// /// Verifies the inverse ADST operators against their scalar implementations. /// private static void AssertAdstOperatorParity() { AssertOperatorParity(4); AssertOperatorParity(8); AssertOperatorParity(16); } /// /// Verifies the inverse identity operators against their scalar implementations. /// private static void AssertIdentityOperatorParity() { AssertOperatorParity(4); AssertOperatorParity(8); AssertOperatorParity(16); AssertOperatorParity(32); } [Theory] [InlineData((int)Av1TransformSize.Size4x4, 0, -4)] [InlineData((int)Av1TransformSize.Size8x8, -1, -4)] [InlineData((int)Av1TransformSize.Size16x16, -2, -4)] [InlineData((int)Av1TransformSize.Size32x32, -2, -4)] [InlineData((int)Av1TransformSize.Size64x64, -2, -4)] [InlineData((int)Av1TransformSize.Size4x8, 0, -4)] [InlineData((int)Av1TransformSize.Size8x4, 0, -4)] [InlineData((int)Av1TransformSize.Size8x16, -1, -4)] [InlineData((int)Av1TransformSize.Size16x8, -1, -4)] [InlineData((int)Av1TransformSize.Size16x32, -1, -4)] [InlineData((int)Av1TransformSize.Size32x16, -1, -4)] [InlineData((int)Av1TransformSize.Size32x64, -1, -4)] [InlineData((int)Av1TransformSize.Size64x32, -1, -4)] [InlineData((int)Av1TransformSize.Size4x16, -1, -4)] [InlineData((int)Av1TransformSize.Size16x4, -1, -4)] [InlineData((int)Av1TransformSize.Size8x32, -2, -4)] [InlineData((int)Av1TransformSize.Size32x8, -2, -4)] [InlineData((int)Av1TransformSize.Size16x64, -2, -4)] [InlineData((int)Av1TransformSize.Size64x16, -2, -4)] public void InverseConfigurationUsesNormativeShifts(int transformSizeValue, int firstShift, int secondShift) { Av1TransformSize transformSize = (Av1TransformSize)transformSizeValue; Av1Transform2dFlipConfiguration config = Av1Transform2dFlipConfiguration.CreateInverse(Av1TransformType.DctDct, transformSize, 8); Assert.Equal(firstShift, config.Shift0); Assert.Equal(secondShift, config.Shift1); Assert.Equal(0, config.Shift2); Assert.Equal(12, config.CosBitColumn); Assert.Equal(12, config.CosBitRow); } [Theory] [InlineData(8, 16, 16)] [InlineData(10, 18, 16)] [InlineData(12, 20, 18)] public void InverseConfigurationUsesNormativeStageRanges(int bitDepth, byte rowRange, byte columnRange) { Av1Transform2dFlipConfiguration config = Av1Transform2dFlipConfiguration.CreateInverse( Av1TransformType.AdstAdst, Av1TransformSize.Size16x16, bitDepth); Av1TransformStageRange configuredRowRange = config.StageRangeRow; Av1TransformStageRange configuredColumnRange = config.StageRangeColumn; for (int index = 0; index < config.StageNumberRow; index++) { Assert.Equal(rowRange, configuredRowRange[index]); } for (int index = 0; index < config.StageNumberColumn; index++) { Assert.Equal(columnRange, configuredColumnRange[index]); } } [Fact] public void ForwardAndInverseOperatorPairsReconstructTheirInput() { AssertRoundTrip(Av1TransformType.DctDct, Av1TransformSize.Size4x4, 1, 1); AssertRoundTrip(Av1TransformType.DctDct, Av1TransformSize.Size8x8, 2, 2); AssertRoundTrip(Av1TransformType.DctDct, Av1TransformSize.Size16x16, 3, 3); AssertRoundTrip(Av1TransformType.DctDct, Av1TransformSize.Size32x32, 4, 4); AssertRoundTrip(Av1TransformType.DctDct, Av1TransformSize.Size64x64, 5, 5); AssertRoundTrip(Av1TransformType.AdstAdst, Av1TransformSize.Size4x4, 1, 1); AssertRoundTrip(Av1TransformType.AdstAdst, Av1TransformSize.Size8x8, 2, 2); AssertRoundTrip(Av1TransformType.AdstAdst, Av1TransformSize.Size16x16, 3, 3); AssertRoundTrip(Av1TransformType.Identity, Av1TransformSize.Size4x4, 1, 1); AssertRoundTrip(Av1TransformType.Identity, Av1TransformSize.Size8x8, 2, 1); AssertRoundTrip(Av1TransformType.Identity, Av1TransformSize.Size16x16, 3, 1); AssertRoundTrip(Av1TransformType.Identity, Av1TransformSize.Size32x32, 4, 1); } /// /// Verifies that every applicable SIMD traversal reconstructs the same samples as the scalar traversal. /// /// The integral value. /// The integral value. /// The coded sample bit depth. [Theory] [MemberData(nameof(Av1ForwardTransformTests.ValidTransformCases), MemberType = typeof(Av1ForwardTransformTests))] public void TwoDimensionalSimdKernelsMatchScalarForEveryValidConfiguration( int transformTypeValue, int transformSizeValue, int bitDepth) { Av1TransformType transformType = (Av1TransformType)transformTypeValue; Av1TransformSize transformSize = (Av1TransformSize)transformSizeValue; Av1Transform2dFlipConfiguration config = Av1Transform2dFlipConfiguration.CreateInverse(transformType, transformSize, bitDepth); DispatchColumn(transformType, transformSize, bitDepth, ref config); } /// /// Verifies lossless inverse Walsh-Hadamard reconstruction against an independent definition. /// [Fact] public void LosslessWalshHadamardMatchesReferenceAcrossIntrinsicTiers() => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertLosslessWalshHadamardParity, TransformConfigurations); /// /// Exercises DC-only and complete lossless blocks at every supported sample precision. /// private static void AssertLosslessWalshHadamardParity() { const int stride = 7; int[] workspace = new int[Av1TransformWorkspace.MaximumLength]; int[][] coefficientCases = [ [512, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [-516, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [320, -192, 64, -448, 128, 256, -320, 96, -224, 160, 384, -128, 448, -64, -256, 192] ]; for (int coefficientCase = 0; coefficientCase < coefficientCases.Length; coefficientCase++) { int[] coefficients = coefficientCases[coefficientCase]; int coefficientCount = coefficientCase < 2 ? 1 : coefficients.Length; byte[] expectedBytes = new byte[stride * 4]; Array.Fill(expectedBytes, (byte)233); PopulatePrediction(expectedBytes, stride, byte.MaxValue); byte[] actualBytes = (byte[])expectedBytes.Clone(); ApplyWalshHadamardReference(coefficients, expectedBytes, stride, coefficientCount, 8); Av1InverseTransformer.Reconstruct8Bit( coefficients, actualBytes, stride, Av1TransformSize.Size4x4, Av1TransformType.DctDct, 0, coefficientCount, true, workspace); Assert.Equal(expectedBytes, actualBytes); foreach (int bitDepth in new[] { 10, 12 }) { int maximum = (1 << bitDepth) - 1; short[] expected = new short[stride * 4]; Array.Fill(expected, (short)-1); PopulatePrediction(expected, stride, maximum); short[] actual = (short[])expected.Clone(); ApplyWalshHadamardReference(coefficients, expected, stride, coefficientCount, bitDepth); Av1InverseTransformer.ReconstructHighBitDepth( coefficients, actual, stride, Av1TransformSize.Size4x4, Av1TransformType.DctDct, 0, coefficientCount, true, bitDepth == 10 ? Av1BitDepth.TenBit : Av1BitDepth.TwelveBit, workspace); Assert.Equal(expected, actual); } } } [Fact] public void ReconstructionDispatchDoesNotAllocatePerBlock() { const int width = 8; int[] coefficients = new int[width * width]; byte[] reconstruction = new byte[coefficients.Length]; int[] workspace = new int[Av1TransformWorkspace.MaximumLength]; Av1InverseTransformer.Reconstruct8Bit( coefficients, reconstruction, width, Av1TransformSize.Size8x8, Av1TransformType.DctDct, 0, coefficients.Length, false, workspace); long before = GC.GetAllocatedBytesForCurrentThread(); for (int iteration = 0; iteration < 32; iteration++) { Av1InverseTransformer.Reconstruct8Bit( coefficients, reconstruction, width, Av1TransformSize.Size8x8, Av1TransformType.DctDct, 0, coefficients.Length, false, workspace); } long allocated = GC.GetAllocatedBytesForCurrentThread() - before; Assert.Equal(0, allocated); } [Theory] [InlineData((int)Av1BitDepth.TenBit, 1023)] [InlineData((int)Av1BitDepth.TwelveBit, 4095)] public void HighBitDepthReconstructionClipsPositiveValues(int bitDepthIndex, short maximum) { const int width = 4; int[] coefficients = new int[width * width]; coefficients[0] = 64; short[] reconstruction = new short[width * width]; Array.Fill(reconstruction, (short)(maximum - 1)); int[] workspace = new int[Av1TransformWorkspace.MaximumLength]; Av1InverseTransformer.ReconstructHighBitDepth( coefficients, reconstruction, width, Av1TransformSize.Size4x4, Av1TransformType.DctDct, 0, 1, false, (Av1BitDepth)bitDepthIndex, workspace); Assert.All(reconstruction, value => Assert.Equal(maximum, value)); } [Theory] [InlineData((int)Av1BitDepth.TenBit)] [InlineData((int)Av1BitDepth.TwelveBit)] public void HighBitDepthReconstructionClipsNegativeValues(int bitDepthIndex) { const int width = 4; int[] coefficients = new int[width * width]; coefficients[0] = -64; short[] reconstruction = new short[width * width]; Array.Fill(reconstruction, (short)1); int[] workspace = new int[Av1TransformWorkspace.MaximumLength]; Av1InverseTransformer.ReconstructHighBitDepth( coefficients, reconstruction, width, Av1TransformSize.Size4x4, Av1TransformType.DctDct, 0, 1, false, (Av1BitDepth)bitDepthIndex, workspace); Assert.All(reconstruction, value => Assert.Equal((short)0, value)); } /// /// Populates active eight-bit prediction samples while preserving row-padding sentinels. /// private static void PopulatePrediction(Span prediction, int stride, int maximum) { for (int row = 0; row < 4; row++) { for (int column = 0; column < 4; column++) { prediction[(row * stride) + column] = (byte)(((row * 101) + (column * 67) + 19) & maximum); } } } /// /// Populates active high-bit-depth prediction samples while preserving row-padding sentinels. /// private static void PopulatePrediction(Span prediction, int stride, int maximum) { for (int row = 0; row < 4; row++) { for (int column = 0; column < 4; column++) { prediction[(row * stride) + column] = (short)(((row * 911) + (column * 593) + 37) & maximum); } } } /// /// Applies the normative inverse Walsh-Hadamard definition to an eight-bit prediction block. /// private static void ApplyWalshHadamardReference(ReadOnlySpan coefficients, Span destination, int stride, int coefficientCount, int bitDepth) { int[] residuals = CalculateWalshHadamardReference(coefficients, coefficientCount); int maximum = (1 << bitDepth) - 1; for (int row = 0; row < 4; row++) { for (int column = 0; column < 4; column++) { int offset = (row * stride) + column; destination[offset] = (byte)Math.Clamp(destination[offset] + residuals[(row * 4) + column], 0, maximum); } } } /// /// Applies the normative inverse Walsh-Hadamard definition to a high-bit-depth prediction block. /// private static void ApplyWalshHadamardReference(ReadOnlySpan coefficients, Span destination, int stride, int coefficientCount, int bitDepth) { int[] residuals = CalculateWalshHadamardReference(coefficients, coefficientCount); int maximum = (1 << bitDepth) - 1; for (int row = 0; row < 4; row++) { for (int column = 0; column < 4; column++) { int offset = (row * stride) + column; destination[offset] = (short)Math.Clamp(destination[offset] + residuals[(row * 4) + column], 0, maximum); } } } /// /// Calculates the exact four-by-four residual matrix defined by AV1's reversible transform. /// private static int[] CalculateWalshHadamardReference(ReadOnlySpan coefficients, int coefficientCount) { int[] residuals = new int[16]; if (coefficientCount == 1) { int first = coefficients[0] >> 2; int half = first >> 1; int firstIntermediate = first - half; for (int column = 0; column < 4; column++) { int intermediate = column == 0 ? firstIntermediate : half; int repeatedResidual = intermediate >> 1; residuals[column] = intermediate - repeatedResidual; residuals[4 + column] = repeatedResidual; residuals[8 + column] = repeatedResidual; residuals[12 + column] = repeatedResidual; } return residuals; } int[] intermediateValues = new int[16]; for (int row = 0; row < 4; row++) { int coefficientOffset = row * 4; int a = coefficients[coefficientOffset] >> 2; int c = coefficients[coefficientOffset + 1] >> 2; int d = coefficients[coefficientOffset + 2] >> 2; int b = coefficients[coefficientOffset + 3] >> 2; ApplyWalshHadamardReference(ref a, ref b, ref c, ref d); intermediateValues[row] = a; intermediateValues[4 + row] = b; intermediateValues[8 + row] = c; intermediateValues[12 + row] = d; } for (int column = 0; column < 4; column++) { int offset = column * 4; int a = intermediateValues[offset]; int c = intermediateValues[offset + 1]; int d = intermediateValues[offset + 2]; int b = intermediateValues[offset + 3]; ApplyWalshHadamardReference(ref a, ref b, ref c, ref d); residuals[column] = a; residuals[4 + column] = b; residuals[8 + column] = c; residuals[12 + column] = d; } return residuals; } /// /// Applies one scalar four-point reversible Walsh-Hadamard dimension for the independent test definition. /// private static void ApplyWalshHadamardReference(ref int a, ref int b, ref int c, ref int d) { a += c; d -= b; int middle = (a - d) >> 1; b = middle - b; c = middle - c; a -= b; d += c; } /// /// Compares one inverse transform operator across scalar and the supported SIMD lane widths. /// /// The inverse transform operator. /// The transform length. private static void AssertOperatorParity(int length) where TOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator { const int cosBit = 12; Av1TransformStageRange stageRange = default; for (int index = 0; index < Av1Transform2dFlipConfiguration.MaxStageNumber; index++) { stageRange[index] = 24; } Av1TransformVector> input128 = default; Av1TransformVector> output128 = default; Av1TransformVector> step128 = default; Av1TransformVector> input256 = default; Av1TransformVector> output256 = default; Av1TransformVector> step256 = default; for (int index = 0; index < length; index++) { input128[index] = Vector128.Create( GetInputValue(index, 0), GetInputValue(index, 1), GetInputValue(index, 2), GetInputValue(index, 3)); input256[index] = Vector256.Create( GetInputValue(index, 0), GetInputValue(index, 1), GetInputValue(index, 2), GetInputValue(index, 3), GetInputValue(index, 4), GetInputValue(index, 5), GetInputValue(index, 6), GetInputValue(index, 7)); } TOperator.Transform(ref input128, ref output128, ref step128, cosBit, stageRange); TOperator.Transform(ref input256, ref output256, ref step256, cosBit, stageRange); int[] scalarInput = new int[length]; int[] scalarOutput = new int[length]; int[] scalarStep = new int[length]; for (int lane = 0; lane < Vector256.Count; lane++) { for (int index = 0; index < length; index++) { scalarInput[index] = GetInputValue(index, lane); } TOperator.Transform(scalarInput, scalarOutput, scalarStep, cosBit, stageRange); for (int index = 0; index < length; index++) { Assert.Equal(scalarOutput[index], output256[index].GetElement(lane)); if (lane < Vector128.Count) { Assert.Equal(scalarOutput[index], output128[index].GetElement(lane)); } } } } /// /// Verifies that a matching one-dimensional forward and inverse operator pair reconstructs bounded input. /// /// The forward transform operator. /// The inverse transform operator. /// The compound transform type. /// The transform-block dimensions. /// The power-of-two scale applied by the operator pair. /// The maximum permitted reconstruction error. private static void AssertRoundTrip(Av1TransformType transformType, Av1TransformSize transformSize, int scaleLog2, int allowedError) where TForwardOperator : struct, Av1ForwardTransformer.IAv1ForwardTransform1dOperator where TInverseOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator { const int bitDepth = 10; const int testBlockCount = 30; Av1Transform2dFlipConfiguration forwardConfig = Av1Transform2dFlipConfiguration.CreateForward(transformType, transformSize, bitDepth); Av1Transform2dFlipConfiguration inverseConfig = Av1Transform2dFlipConfiguration.CreateInverse(transformType, transformSize, bitDepth); int length = transformSize.GetWidth(); Random random = new(0); int[] input = new int[length]; int[] forward = new int[length]; int[] inverse = new int[length]; int[] step = new int[length]; Av1TransformVector values = default; Av1TransformVector buffer0 = default; Av1TransformVector buffer1 = default; for (int block = 0; block < testBlockCount; block++) { for (int index = 0; index < length; index++) { input[index] = random.Next((1 << bitDepth) - 1); values[index] = input[index]; } ref byte valuesBase = ref System.Runtime.CompilerServices.Unsafe.As, byte>(ref values); TForwardOperator.Transform(ref valuesBase, sizeof(int), sizeof(int), ref buffer0, ref buffer1, forwardConfig.CosBitColumn); for (int index = 0; index < length; index++) { forward[index] = values[index]; } TInverseOperator.Transform(forward, inverse, step, inverseConfig.CosBitColumn, inverseConfig.StageRangeColumn); for (int index = 0; index < length; index++) { int reconstructed = inverse[index] >> scaleLog2; Assert.InRange(Math.Abs(input[index] - reconstructed), 0, allowedError); } } } /// /// Closes the static-generic inverse column operator selected by a transform configuration. /// /// The compound transform type. /// The transform-block dimensions. /// The coded sample bit depth. /// The inverse transform configuration. private static void DispatchColumn( Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth, ref Av1Transform2dFlipConfiguration config) { switch (config.TransformFunctionTypeColumn) { case Av1TransformFunctionType.Dct4: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Dct8: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Dct16: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Dct32: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Dct64: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Adst4: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Adst8: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Adst16: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Identity4: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Identity8: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Identity16: DispatchRow(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Identity32: DispatchRow(transformType, transformSize, bitDepth, ref config); break; default: Assert.Fail($"Unexpected column function {config.TransformFunctionTypeColumn} for {transformType} {transformSize}."); break; } } /// /// Closes the static-generic inverse row operator after the column operator has been selected. /// /// The selected inverse column operator. /// The compound transform type. /// The transform-block dimensions. /// The coded sample bit depth. /// The inverse transform configuration. private static void DispatchRow( Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth, ref Av1Transform2dFlipConfiguration config) where TColumnOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator { switch (config.TransformFunctionTypeRow) { case Av1TransformFunctionType.Dct4: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Dct8: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Dct16: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Dct32: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Dct64: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Adst4: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Adst8: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Adst16: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Identity4: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Identity8: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Identity16: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; case Av1TransformFunctionType.Identity32: AssertTransform2dParity(transformType, transformSize, bitDepth, ref config); break; default: Assert.Fail($"Unexpected row function {config.TransformFunctionTypeRow} for {transformType} {transformSize}."); break; } } /// /// Produces bounded conformant coefficients and selects byte or high-bit-depth reconstruction verification. /// /// The selected inverse column operator. /// The selected inverse row operator. /// The compound transform type. /// The transform-block dimensions. /// The coded sample bit depth. /// The inverse transform configuration. private static void AssertTransform2dParity( Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth, ref Av1Transform2dFlipConfiguration config) where TColumnOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator where TRowOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator { int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); int inputStride = width + 5; int maximum = (1 << bitDepth) - 1; short[] residual = new short[inputStride * height]; for (int row = 0; row < height; row++) { for (int column = 0; column < width; column++) { int index = (row * width) + column; residual[(row * inputStride) + column] = (short)((index & 3) switch { 0 => maximum, 1 => -maximum, 2 => ((index * 73) % ((maximum * 2) + 1)) - maximum, _ => 0, }); } } // A conformant forward transform supplies coefficient magnitudes at the exact fixed-point bounds expected by // the inverse kernels. This is stronger than arbitrary small coefficients and avoids impossible stress inputs. int[] coefficients = new int[width * height]; int[] forwardWorkspace = new int[Av1TransformWorkspace.GetRequiredLength(transformSize)]; Av1ForwardTransformer.Transform2d(residual, coefficients, (uint)inputStride, transformType, transformSize, bitDepth, forwardWorkspace); if (bitDepth == 8) { AssertByteTransform2dParity(coefficients, transformSize, ref config); return; } AssertHighBitDepthTransform2dParity(coefficients, transformSize, bitDepth, ref config); } /// /// Compares eight-bit scalar and SIMD reconstruction with independently padded read and write rows. /// /// The selected inverse column operator. /// The selected inverse row operator. /// The conformant forward-transform coefficients. /// The transform-block dimensions. /// The inverse transform configuration. private static void AssertByteTransform2dParity( int[] coefficients, Av1TransformSize transformSize, ref Av1Transform2dFlipConfiguration config) where TColumnOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator where TRowOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator { const int bitDepth = 8; int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); int readStride = width + 3; int writeStride = width + 7; int workspaceLength = Av1TransformWorkspace.GetRequiredLength(transformSize); byte[] prediction = new byte[readStride * height]; for (int row = 0; row < height; row++) { for (int column = 0; column < width; column++) { prediction[(row * readStride) + column] = (byte)(((row * width) + column) * 29); } } byte[] scalar = new byte[writeStride * height]; byte[] vector128 = new byte[writeStride * height]; int[] scalarWorkspace = new int[workspaceLength]; int[] vector128Workspace = new int[workspaceLength]; Array.Fill(scalar, byte.MaxValue); Array.Fill(vector128, byte.MaxValue); Av1Inverse2dTransformer.Transform2dScalar, TColumnOperator, TRowOperator>( coefficients, prediction, readStride, scalar, writeStride, ref config, scalarWorkspace, bitDepth); Av1Inverse2dTransformer.Transform2dVector128, TColumnOperator, TRowOperator>( coefficients, prediction, readStride, vector128, writeStride, ref config, vector128Workspace, bitDepth); Assert.Equal(scalar, vector128); if (width >= Vector256.Count && height >= Vector256.Count) { byte[] vector256 = new byte[writeStride * height]; int[] vector256Workspace = new int[workspaceLength]; Array.Fill(vector256, byte.MaxValue); Av1Inverse2dTransformer.Transform2dVector256, TColumnOperator, TRowOperator>( coefficients, prediction, readStride, vector256, writeStride, ref config, vector256Workspace, bitDepth); Assert.Equal(scalar, vector256); } } /// /// Compares high-bit-depth scalar and SIMD reconstruction with independently padded read and write rows. /// /// The selected inverse column operator. /// The selected inverse row operator. /// The conformant forward-transform coefficients. /// The transform-block dimensions. /// The coded sample bit depth. /// The inverse transform configuration. private static void AssertHighBitDepthTransform2dParity( int[] coefficients, Av1TransformSize transformSize, int bitDepth, ref Av1Transform2dFlipConfiguration config) where TColumnOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator where TRowOperator : struct, Av1Inverse2dTransformer.IAv1InverseTransform1dOperator { int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); int readStride = width + 3; int writeStride = width + 7; int maximum = (1 << bitDepth) - 1; int workspaceLength = Av1TransformWorkspace.GetRequiredLength(transformSize); short[] prediction = new short[readStride * height]; for (int row = 0; row < height; row++) { for (int column = 0; column < width; column++) { prediction[(row * readStride) + column] = (short)((((row * width) + column) * 47) & maximum); } } short[] scalar = new short[writeStride * height]; short[] vector128 = new short[writeStride * height]; int[] scalarWorkspace = new int[workspaceLength]; int[] vector128Workspace = new int[workspaceLength]; Array.Fill(scalar, short.MinValue); Array.Fill(vector128, short.MinValue); Av1Inverse2dTransformer.Transform2dScalar, TColumnOperator, TRowOperator>( coefficients, prediction, readStride, scalar, writeStride, ref config, scalarWorkspace, bitDepth); Av1Inverse2dTransformer.Transform2dVector128, TColumnOperator, TRowOperator>( coefficients, prediction, readStride, vector128, writeStride, ref config, vector128Workspace, bitDepth); Assert.Equal(scalar, vector128); if (width >= Vector256.Count && height >= Vector256.Count) { short[] vector256 = new short[writeStride * height]; int[] vector256Workspace = new int[workspaceLength]; Array.Fill(vector256, short.MinValue); Av1Inverse2dTransformer.Transform2dVector256, TColumnOperator, TRowOperator>( coefficients, prediction, readStride, vector256, writeStride, ref config, vector256Workspace, bitDepth); Assert.Equal(scalar, vector256); } } /// /// Produces deterministic bounded input for one transform position and SIMD lane. /// /// The position within the transform. /// The SIMD lane index. /// The input value. private static int GetInputValue(int index, int lane) => (((index * 73) + (lane * 151)) % 1023) - 511; }