diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 36f04aded..2c45cab31 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -178,8 +178,8 @@ The single-reference syntax, buffer, reconstruction, and ownership foundation is - [x] Compound reference selection, paired reference-MV derivation, and equal averaging. - [x] Inter-intra prediction. -- [~] Distance-weighted compound prediction. Current item. -- [~] Wedge compound prediction. +- [x] Distance-weighted compound prediction. +- [~] Wedge compound prediction. Current item. - [~] Difference-weighted compound prediction. - [~] OBMC. - [~] Scaled-reference prediction. @@ -251,6 +251,41 @@ Verified inter-intra checkpoint evidence on 2026-08-31: failures or skips. Scoped analyzer and whitespace verification pass for both changed C# files. Roslynk reports zero compiler errors and no diagnostics in the changed files, `git diff --check` passes, and `.gitattributes` is unchanged. +- [x] The completed checkpoint was committed as `18b1c881271a3494489ca6f410ab140544902e2d` + with author and committer `James Jackson-South `. + +Verified distance-weighted compound checkpoint evidence on 2026-08-31: + +- [x] Audited reference-distance quantization against `quant_dist_weight` and + `quant_dist_lookup_table` in current libaom `av1/common/common_data.h`, and audited order-hint + distance selection and forward/backward reference assignment against + `av1_dist_wtd_comp_weight_assign` in `av1/common/reconinter.c`. +- [x] Audited reconstruction against current libaom `av1/common/convolve.c`. Corrected the production + 10/12-bit subpixel path, which incorrectly finalized its two no-round compound intermediates with an + equal average instead of the signaled distance weights. The fixed path applies libaom's 4-bit weighted + shift before bias removal, final rounding, and clipping. +- [x] Added descending Vector512, Vector256, Vector128, and scalar traversal to the existing semantic + distance-weighted intermediate predictor family. Unsigned widening preserves the biased 12-bit + intermediate range. No per-block, per-row, or per-scanline allocation or copy was added. +- [x] Added FeatureTestRunner coverage for every current-libaom distance-weight class in both reference + orders, and for 10/12-bit copy, horizontal, vertical, and separable subpixel prediction at widths 9, + 17, 33, and 65, with an independent no-round oracle and row-padding sentinels. +- [x] Added a complete `Av1BlockDecoder.DecodeBlock` 10/12-bit half-sample regression that selects the + 13:3 distance weights through real order hints. Its first reconstructed sample differs from the old + equal-average result, so the test proves the corrected production branch is executed. +- [x] Extracted the fixture's 5,372-byte AV1 `mdat` payload and decoded it with the refreshed current + libaom `aomdec`, using one thread with row threading disabled. All 19 frames decoded. The final 19,200 + YUV444 samples have SHA-256 + `E8CAA650F1571C5B9CACAF8C06E1DDF5F5D2ED35F65F1C34377076C573425899` and match the retained native + reference with zero differing samples. +- [x] The real 19-frame production sequence requires decoded distance-weighted compound blocks, compares + the final native Y, Cb, and Cr planes exactly, compares final RGBA presentation through ImageSharp's + established reference-output API, and repeats the complete decode with a 1,024-byte constrained + tracked allocator and exactly-once return checks. +- [x] The focused Release checkpoint set passes 44/44 on net10.0 and 44/44 on net11.0, with zero failures + or skips. Scoped analyzer and whitespace verification pass for every changed C# file. Roslynk reports + zero compiler errors and no diagnostics in the changed files, `git diff --check` passes, and + `.gitattributes` is unchanged. For every item: diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.Operator.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.Operator.cs index f211f3052..aa46f9beb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.Operator.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.Operator.cs @@ -102,6 +102,86 @@ internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor int secondWeight, int roundBits, int roundOffset); + + /// + /// Distance-weights and finalizes one pair of high-bit-depth compound intermediate samples. + /// + /// The first compound intermediate. + /// The second compound intermediate. + /// The first predictor weight. + /// The second predictor weight. + /// The final reconstruction shift. + /// The compound intermediate bias. + /// The maximum reconstructed sample value. + /// The reconstructed sample. + public static abstract ushort DistanceWeightedHighBitDepth( + ushort first, + ushort second, + int firstWeight, + int secondWeight, + int roundBits, + int roundOffset, + int maximum); + + /// + /// Distance-weights and finalizes 128 bits of high-bit-depth compound intermediate samples. + /// + /// The first compound intermediates. + /// The second compound intermediates. + /// The first predictor weight. + /// The second predictor weight. + /// The final reconstruction shift. + /// The compound intermediate bias. + /// The maximum reconstructed sample value. + /// The reconstructed samples. + public static abstract Vector128 DistanceWeightedHighBitDepth( + Vector128 first, + Vector128 second, + int firstWeight, + int secondWeight, + int roundBits, + int roundOffset, + int maximum); + + /// + /// Distance-weights and finalizes 256 bits of high-bit-depth compound intermediate samples. + /// + /// The first compound intermediates. + /// The second compound intermediates. + /// The first predictor weight. + /// The second predictor weight. + /// The final reconstruction shift. + /// The compound intermediate bias. + /// The maximum reconstructed sample value. + /// The reconstructed samples. + public static abstract Vector256 DistanceWeightedHighBitDepth( + Vector256 first, + Vector256 second, + int firstWeight, + int secondWeight, + int roundBits, + int roundOffset, + int maximum); + + /// + /// Distance-weights and finalizes 512 bits of high-bit-depth compound intermediate samples. + /// + /// The first compound intermediates. + /// The second compound intermediates. + /// The first predictor weight. + /// The second predictor weight. + /// The final reconstruction shift. + /// The compound intermediate bias. + /// The maximum reconstructed sample value. + /// The reconstructed samples. + public static abstract Vector512 DistanceWeightedHighBitDepth( + Vector512 first, + Vector512 second, + int firstWeight, + int secondWeight, + int roundBits, + int roundOffset, + int maximum); } /// @@ -124,6 +204,22 @@ internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor return (byte)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, byte.MaxValue); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort DistanceWeightedHighBitDepth( + ushort first, + ushort second, + int firstWeight, + int secondWeight, + int roundBits, + int roundOffset, + int maximum) + { + int result = ((first * firstWeight) + (second * secondWeight)) >> DistanceWeightBits; + result -= roundOffset; + return (ushort)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, maximum); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 DistanceWeighted( @@ -169,6 +265,96 @@ internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor DistanceWeighted(first0, second0, firstWeight, secondWeight, roundBits, roundOffset), DistanceWeighted(first1, second1, firstWeight, secondWeight, roundBits, roundOffset)); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 DistanceWeightedHighBitDepth( + Vector128 first, + Vector128 second, + int firstWeight, + int secondWeight, + int roundBits, + int roundOffset, + int maximum) + { + Vector128 firstLower = Vector128.WidenLower(first); + Vector128 firstUpper = Vector128.WidenUpper(first); + Vector128 secondLower = Vector128.WidenLower(second); + Vector128 secondUpper = Vector128.WidenUpper(second); + Vector128 lower = + ((firstLower * Vector128.Create((uint)firstWeight)) + + (secondLower * Vector128.Create((uint)secondWeight))) >> DistanceWeightBits; + + Vector128 upper = + ((firstUpper * Vector128.Create((uint)firstWeight)) + + (secondUpper * Vector128.Create((uint)secondWeight))) >> DistanceWeightBits; + + return FinalizeHighBitDepthIntermediate( + Vector128.Narrow(lower, upper), + roundBits, + roundOffset, + maximum); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DistanceWeightedHighBitDepth( + Vector256 first, + Vector256 second, + int firstWeight, + int secondWeight, + int roundBits, + int roundOffset, + int maximum) + { + Vector256 firstLower = Vector256.WidenLower(first); + Vector256 firstUpper = Vector256.WidenUpper(first); + Vector256 secondLower = Vector256.WidenLower(second); + Vector256 secondUpper = Vector256.WidenUpper(second); + Vector256 lower = + ((firstLower * Vector256.Create((uint)firstWeight)) + + (secondLower * Vector256.Create((uint)secondWeight))) >> DistanceWeightBits; + + Vector256 upper = + ((firstUpper * Vector256.Create((uint)firstWeight)) + + (secondUpper * Vector256.Create((uint)secondWeight))) >> DistanceWeightBits; + + return FinalizeHighBitDepthIntermediate( + Vector256.Narrow(lower, upper), + roundBits, + roundOffset, + maximum); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DistanceWeightedHighBitDepth( + Vector512 first, + Vector512 second, + int firstWeight, + int secondWeight, + int roundBits, + int roundOffset, + int maximum) + { + Vector512 firstLower = Vector512.WidenLower(first); + Vector512 firstUpper = Vector512.WidenUpper(first); + Vector512 secondLower = Vector512.WidenLower(second); + Vector512 secondUpper = Vector512.WidenUpper(second); + Vector512 lower = + ((firstLower * Vector512.Create((uint)firstWeight)) + + (secondLower * Vector512.Create((uint)secondWeight))) >> DistanceWeightBits; + + Vector512 upper = + ((firstUpper * Vector512.Create((uint)firstWeight)) + + (secondUpper * Vector512.Create((uint)secondWeight))) >> DistanceWeightBits; + + return FinalizeHighBitDepthIntermediate( + Vector512.Narrow(lower, upper), + roundBits, + roundOffset, + maximum); + } + /// /// Distance-weights 128-bit lanes without overflowing the unsigned intermediate range. /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.cs index c8479bbc6..9598d95f5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.cs @@ -153,4 +153,131 @@ internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor } } } + + /// + /// Combines two high-bit-depth compound intermediates using the decoded display-distance weights. + /// + public static void DistanceWeightedIntermediate( + Span destination, + int destinationStride, + ReadOnlySpan first, + int firstStride, + ReadOnlySpan second, + int secondStride, + int width, + int height, + int firstWeight, + int secondWeight, + int bitDepth) + => DistanceWeightedIntermediate( + destination, + destinationStride, + first, + firstStride, + second, + secondStride, + width, + height, + firstWeight, + secondWeight, + bitDepth); + + /// + /// Executes one closed high-bit-depth distance-weighted compound-intermediate operator. + /// + /// The compound-intermediate operator. + private static void DistanceWeightedIntermediate( + Span destination, + int destinationStride, + ReadOnlySpan first, + int firstStride, + ReadOnlySpan second, + int secondStride, + int width, + int height, + int firstWeight, + int secondWeight, + int bitDepth) + where TOperator : struct, IAv1CompoundIntermediateDistanceWeightedOperator + { + GetIntermediateRounding(bitDepth, out int roundBits, out int roundOffset); + int maximum = (1 << bitDepth) - 1; + + for (int row = 0; row < height; row++) + { + Span destinationRow = destination.Slice(row * destinationStride, width); + ReadOnlySpan firstRow = first.Slice(row * firstStride, width); + ReadOnlySpan secondRow = second.Slice(row * secondStride, width); + ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow); + ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); + ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); + int column = 0; + + if (Vector512.IsHardwareAccelerated) + { + int vectorEnd = width - Vector512.Count; + for (; column <= vectorEnd; column += Vector512.Count) + { + Vector512 firstVector = Vector512.LoadUnsafe(ref firstReference, (nuint)column); + Vector512 secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); + TOperator.DistanceWeightedHighBitDepth( + firstVector, + secondVector, + firstWeight, + secondWeight, + roundBits, + roundOffset, + maximum).StoreUnsafe(ref destinationReference, (nuint)column); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorEnd = width - Vector256.Count; + for (; column <= vectorEnd; column += Vector256.Count) + { + Vector256 firstVector = Vector256.LoadUnsafe(ref firstReference, (nuint)column); + Vector256 secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); + TOperator.DistanceWeightedHighBitDepth( + firstVector, + secondVector, + firstWeight, + secondWeight, + roundBits, + roundOffset, + maximum).StoreUnsafe(ref destinationReference, (nuint)column); + } + } + + if (Vector128.IsHardwareAccelerated) + { + int vectorEnd = width - Vector128.Count; + for (; column <= vectorEnd; column += Vector128.Count) + { + Vector128 firstVector = Vector128.LoadUnsafe(ref firstReference, (nuint)column); + Vector128 secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); + TOperator.DistanceWeightedHighBitDepth( + firstVector, + secondVector, + firstWeight, + secondWeight, + roundBits, + roundOffset, + maximum).StoreUnsafe(ref destinationReference, (nuint)column); + } + } + + for (; column < width; column++) + { + destinationRow[column] = TOperator.DistanceWeightedHighBitDepth( + firstRow[column], + secondRow[column], + firstWeight, + secondWeight, + roundBits, + roundOffset, + maximum); + } + } + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs index cc2ab9e13..9394e594b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs @@ -714,16 +714,36 @@ internal sealed class Av1BlockDecoder : IDisposable Span highBitDepthDestination = MemoryMarshal.Cast( highBitDepthBlockReconstructionBuffer[reconstructionStride..]); - Av1CompoundIntermediateAveragePredictor.AverageIntermediate( - highBitDepthDestination, - reconstructionStride, - first, - predictionWidth, - highBitDepthSecondPrediction, - predictionWidth, - predictionWidth, - predictionHeight, - this.frameBuffer.BitDepth.GetBitCount()); + if (modeInfo.CompoundType == Av1CompoundType.DistanceWeighted) + { + // Distance weighting must consume the no-round intermediates. Equal-averaging the + // already filtered references loses the decoded display-distance contribution. + Av1CompoundIntermediateDistanceWeightedPredictor.DistanceWeightedIntermediate( + highBitDepthDestination, + reconstructionStride, + first, + predictionWidth, + highBitDepthSecondPrediction, + predictionWidth, + predictionWidth, + predictionHeight, + firstCompoundWeight, + secondCompoundWeight, + this.frameBuffer.BitDepth.GetBitCount()); + } + else + { + Av1CompoundIntermediateAveragePredictor.AverageIntermediate( + highBitDepthDestination, + reconstructionStride, + first, + predictionWidth, + highBitDepthSecondPrediction, + predictionWidth, + predictionWidth, + predictionHeight, + this.frameBuffer.BitDepth.GetBitCount()); + } } else { diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs index 443256abe..ff34b5368 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs @@ -132,6 +132,15 @@ public class Av1CompoundBlockDecoderTests ValidateSubpixelHighBitDepthEqualAverageCompoundPrediction, CompoundPredictionConfigurations); + /// + /// Verifies that high-bit-depth subpixel predictors retain no-round precision until distance weighting. + /// + [Fact] + public void DecodeBlockReconstructsSubpixelHighBitDepthDistanceWeightedCompoundPrediction() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + ValidateSubpixelHighBitDepthDistanceWeightedCompoundPrediction, + CompoundPredictionConfigurations); + /// /// Verifies that both references of a GLOBAL_GLOBALMV block use their complete matrix before compound averaging. /// @@ -649,7 +658,18 @@ public class Av1CompoundBlockDecoderTests { foreach (Av1BitDepth bitDepth in new[] { Av1BitDepth.TenBit, Av1BitDepth.TwelveBit }) { - ValidateSubpixelHighBitDepthEqualAverageCompoundPredictionAtBitDepth(bitDepth); + ValidateSubpixelHighBitDepthCompoundPredictionAtBitDepth(bitDepth, Av1CompoundType.Average); + } + } + + /// + /// Reconstructs the high-bit-depth subpixel distance-weighted regression at every supported source precision. + /// + private static void ValidateSubpixelHighBitDepthDistanceWeightedCompoundPrediction() + { + foreach (Av1BitDepth bitDepth in new[] { Av1BitDepth.TenBit, Av1BitDepth.TwelveBit }) + { + ValidateSubpixelHighBitDepthCompoundPredictionAtBitDepth(bitDepth, Av1CompoundType.DistanceWeighted); } } @@ -657,15 +677,23 @@ public class Av1CompoundBlockDecoderTests /// Reconstructs one high-bit-depth half-sample compound block and compares it with the scalar no-round pipeline. /// /// The native sample depth. - private static void ValidateSubpixelHighBitDepthEqualAverageCompoundPredictionAtBitDepth(Av1BitDepth bitDepth) + /// The final compound operation. + private static void ValidateSubpixelHighBitDepthCompoundPredictionAtBitDepth( + Av1BitDepth bitDepth, + Av1CompoundType compoundType) { const int frameSize = 32; const int blockOrigin = 8; const int blockSize = 8; ObuSequenceHeader sequenceHeader = CreateSequenceHeader(bitDepth, frameSize); + sequenceHeader.OrderHintInfo.EnableOrderHint = true; + sequenceHeader.OrderHintInfo.OrderHintBits = 5; ObuFrameHeader frameHeader = CreateFrameHeader(frameSize); + frameHeader.OrderHint = 10; frameHeader.GetReferenceFrameIndices()[0] = 0; frameHeader.GetReferenceFrameIndices()[1] = 1; + frameHeader.GetReferenceOrderHints()[0] = 9; + frameHeader.GetReferenceOrderHints()[1] = 5; using Av1ReferenceFrameStore referenceFrames = new(); Assert.True(referenceFrames.Commit( @@ -682,8 +710,8 @@ public class Av1CompoundBlockDecoderTests { Skip = true, YMode = Av1PredictionMode.NearestNearestMotionVector, - CompoundIndex = true, - CompoundType = Av1CompoundType.Average, + CompoundIndex = compoundType != Av1CompoundType.DistanceWeighted, + CompoundType = compoundType, }; modeInfo.ReferenceFrames[0] = Av1ReferenceFrameType.Last; @@ -735,18 +763,46 @@ public class Av1CompoundBlockDecoderTests } ushort[] expected = new ushort[blockSize * blockSize]; - Av1CompoundIntermediateAveragePredictor.AverageIntermediate( - expected, - blockSize, - expectedFirst, - blockSize, - expectedSecond, - blockSize, - blockSize, - blockSize, - bitDepth.GetBitCount()); - - Assert.Equal((ushort)60, expected[0]); + if (compoundType == Av1CompoundType.DistanceWeighted) + { + Av1CompoundDistanceWeights.Derive( + sequenceHeader.OrderHintInfo, + frameHeader, + modeInfo.ReferenceFrames[0], + modeInfo.ReferenceFrames[1], + out int firstWeight, + out int secondWeight); + + Av1CompoundIntermediateDistanceWeightedPredictor.DistanceWeightedIntermediate( + expected, + blockSize, + expectedFirst, + blockSize, + expectedSecond, + blockSize, + blockSize, + blockSize, + firstWeight, + secondWeight, + bitDepth.GetBitCount()); + + Assert.NotEqual((ushort)60, expected[0]); + } + else + { + Av1CompoundIntermediateAveragePredictor.AverageIntermediate( + expected, + blockSize, + expectedFirst, + blockSize, + expectedSecond, + blockSize, + blockSize, + blockSize, + bitDepth.GetBitCount()); + + Assert.Equal((ushort)60, expected[0]); + } using Av1FrameBuffer frameBuffer = new( Configuration.Default, diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs index 4989e3c94..76f318a73 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -34,14 +35,62 @@ public class Av1CompoundInterPredictorTests => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthAverage, PredictorConfigurations); /// - /// Verifies 10/12-bit no-round prediction and final equal averaging across every intrinsic width. + /// Verifies 10/12-bit no-round prediction and compound finalization across every intrinsic width. /// [Fact] - public void HighBitDepthIntermediateAverageMatchesIndependentOracleAcrossIntrinsicWidths() + public void HighBitDepthCompoundIntermediatesMatchIndependentOracleAcrossIntrinsicWidths() => FeatureTestRunner.RunWithHwIntrinsicsFeature( - ValidateHighBitDepthIntermediateAverage, + ValidateHighBitDepthCompoundIntermediates, PredictorConfigurations); + /// + /// Verifies every current libaom display-distance quantization class in both temporal directions. + /// + /// The first reference order hint. + /// The second reference order hint. + /// The expected first predictor weight. + /// The expected second predictor weight. + [Theory] + [InlineData(13, 20, 9, 7)] + [InlineData(15, 18, 11, 5)] + [InlineData(15, 19, 12, 4)] + [InlineData(15, 20, 13, 3)] + [InlineData(12, 19, 7, 9)] + [InlineData(14, 17, 5, 11)] + [InlineData(13, 17, 4, 12)] + [InlineData(12, 17, 3, 13)] + [InlineData(12, 16, 3, 13)] + [InlineData(16, 20, 13, 3)] + public void DistanceWeightsMatchCurrentLibaomQuantization( + int firstOrderHint, + int secondOrderHint, + int expectedFirstWeight, + int expectedSecondWeight) + { + ObuOrderHintInfo orderHintInfo = new() + { + EnableOrderHint = true, + OrderHintBits = 5, + }; + + ObuFrameHeader frameHeader = new() { OrderHint = 16 }; + frameHeader.GetReferenceFrameIndices()[0] = 0; + frameHeader.GetReferenceFrameIndices()[1] = 1; + frameHeader.GetReferenceOrderHints()[0] = (uint)firstOrderHint; + frameHeader.GetReferenceOrderHints()[1] = (uint)secondOrderHint; + + Av1CompoundDistanceWeights.Derive( + orderHintInfo, + frameHeader, + Av1ReferenceFrameType.Last, + Av1ReferenceFrameType.Last2, + out int firstWeight, + out int secondWeight); + + Assert.Equal(expectedFirstWeight, firstWeight); + Assert.Equal(expectedSecondWeight, secondWeight); + } + /// /// Verifies 8-bit distance and per-sample mask blending across every intrinsic width and scalar tail. /// @@ -294,7 +343,7 @@ public class Av1CompoundInterPredictorTests /// /// Applies the high-bit-depth no-round convolution equations independently of the production operators. /// - private static void ValidateHighBitDepthIntermediateAverage() + private static void ValidateHighBitDepthCompoundIntermediates() { ReadOnlySpan widths = [9, 17, 33, 65]; ReadOnlySpan<(int Horizontal, int Vertical)> phases = @@ -503,6 +552,47 @@ public class Av1CompoundInterPredictorTests bitDepth); Assert.Equal(expectedDestination, actualDestination); + + ReadOnlySpan distanceWeights = [9, 7, 11, 5, 12, 4, 13, 3]; + for (int weightIndex = 0; weightIndex < distanceWeights.Length; weightIndex += 2) + { + ushort[] expectedWeighted = new ushort[destinationStride * height]; + ushort[] actualWeighted = new ushort[destinationStride * height]; + expectedWeighted.AsSpan().Fill(0xA5A5); + actualWeighted.AsSpan().Fill(0xA5A5); + int firstWeight = distanceWeights[weightIndex]; + int secondWeight = distanceWeights[weightIndex + 1]; + + for (int row = 0; row < height; row++) + { + for (int column = 0; column < width; column++) + { + int intermediateIndex = (row * intermediateStride) + column; + int result = ((expectedFirst[intermediateIndex] * firstWeight) + + (expectedSecond[intermediateIndex] * secondWeight)) >> 4; + + result -= roundOffset; + result = (result + (1 << (roundBits - 1))) >> roundBits; + expectedWeighted[(row * destinationStride) + column] = + (ushort)Math.Clamp(result, 0, maximum); + } + } + + Av1CompoundIntermediateDistanceWeightedPredictor.DistanceWeightedIntermediate( + actualWeighted, + destinationStride, + actualFirst, + intermediateStride, + actualSecond, + intermediateStride, + width, + height, + firstWeight, + secondWeight, + bitDepth); + + Assert.Equal(expectedWeighted, actualWeighted); + } } } } diff --git a/tests/Images/Input/Heif/Av1/Conformance/README.md b/tests/Images/Input/Heif/Av1/Conformance/README.md index ddd330415..b867e3ca6 100644 --- a/tests/Images/Input/Heif/Av1/Conformance/README.md +++ b/tests/Images/Input/Heif/Av1/Conformance/README.md @@ -144,11 +144,17 @@ with row threading disabled. Current `aomdec` produced all 19 YUV444 frames. The native samples have SHA-256 `E8B776C2751DC30CA838931A4B74535FC6E681179568A1278747A38CFF2E5BFA` and match the retained Y4M with zero differing samples. -The production tests independently require their decoded mode states. The inter-intra input must exercise -both smooth and wedge inter-intra prediction, decode all preceding samples, compare the final native Y, -Cb, and Cr planes exactly, compare final RGBA presentation through ImageSharp's established -reference-output API, and repeat reconstruction with constrained tracked allocation. The retained PNG is -presentation evidence only and is not an AV1 reconstruction reference. +The distance-weighted fixture's 5,372-byte AV1 `mdat` payload was decoded under the same current-libaom +conditions. Current `aomdec` produced all 19 YUV444 frames. The final frame's 19,200 native samples have +SHA-256 `E8CAA650F1571C5B9CACAF8C06E1DDF5F5D2ED35F65F1C34377076C573425899` and match the retained +Y4M with zero differing samples. + +The production tests independently require their decoded mode states. The distance-weighted input must +exercise distance-weighted compound prediction and the inter-intra input must exercise both smooth and +wedge inter-intra prediction. The tests decode all preceding samples, compare final native Y, Cb, and Cr +planes exactly, compare final RGBA presentation through ImageSharp's established reference-output API, +and repeat reconstruction with constrained tracked allocation. The retained PNG files are presentation +evidence only and are not AV1 reconstruction references. ## Overlapping motion-compensation fixture