Browse Source

Complete AV1 distance-weighted compound decoding checkpoint

pull/2633/head
James Jackson-South 3 days ago
parent
commit
7e2de7a2c2
  1. 39
      HEIF_IMPLEMENTATION_PLAN.md
  2. 186
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.Operator.cs
  3. 127
      src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.cs
  4. 40
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs
  5. 88
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs
  6. 98
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundInterPredictorTests.cs
  7. 16
      tests/Images/Input/Heif/Av1/Conformance/README.md

39
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 <james_south@hotmail.com>`.
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:

186
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);
/// <summary>
/// Distance-weights and finalizes one pair of high-bit-depth compound intermediate samples.
/// </summary>
/// <param name="first">The first compound intermediate.</param>
/// <param name="second">The second compound intermediate.</param>
/// <param name="firstWeight">The first predictor weight.</param>
/// <param name="secondWeight">The second predictor weight.</param>
/// <param name="roundBits">The final reconstruction shift.</param>
/// <param name="roundOffset">The compound intermediate bias.</param>
/// <param name="maximum">The maximum reconstructed sample value.</param>
/// <returns>The reconstructed sample.</returns>
public static abstract ushort DistanceWeightedHighBitDepth(
ushort first,
ushort second,
int firstWeight,
int secondWeight,
int roundBits,
int roundOffset,
int maximum);
/// <summary>
/// Distance-weights and finalizes 128 bits of high-bit-depth compound intermediate samples.
/// </summary>
/// <param name="first">The first compound intermediates.</param>
/// <param name="second">The second compound intermediates.</param>
/// <param name="firstWeight">The first predictor weight.</param>
/// <param name="secondWeight">The second predictor weight.</param>
/// <param name="roundBits">The final reconstruction shift.</param>
/// <param name="roundOffset">The compound intermediate bias.</param>
/// <param name="maximum">The maximum reconstructed sample value.</param>
/// <returns>The reconstructed samples.</returns>
public static abstract Vector128<ushort> DistanceWeightedHighBitDepth(
Vector128<ushort> first,
Vector128<ushort> second,
int firstWeight,
int secondWeight,
int roundBits,
int roundOffset,
int maximum);
/// <summary>
/// Distance-weights and finalizes 256 bits of high-bit-depth compound intermediate samples.
/// </summary>
/// <param name="first">The first compound intermediates.</param>
/// <param name="second">The second compound intermediates.</param>
/// <param name="firstWeight">The first predictor weight.</param>
/// <param name="secondWeight">The second predictor weight.</param>
/// <param name="roundBits">The final reconstruction shift.</param>
/// <param name="roundOffset">The compound intermediate bias.</param>
/// <param name="maximum">The maximum reconstructed sample value.</param>
/// <returns>The reconstructed samples.</returns>
public static abstract Vector256<ushort> DistanceWeightedHighBitDepth(
Vector256<ushort> first,
Vector256<ushort> second,
int firstWeight,
int secondWeight,
int roundBits,
int roundOffset,
int maximum);
/// <summary>
/// Distance-weights and finalizes 512 bits of high-bit-depth compound intermediate samples.
/// </summary>
/// <param name="first">The first compound intermediates.</param>
/// <param name="second">The second compound intermediates.</param>
/// <param name="firstWeight">The first predictor weight.</param>
/// <param name="secondWeight">The second predictor weight.</param>
/// <param name="roundBits">The final reconstruction shift.</param>
/// <param name="roundOffset">The compound intermediate bias.</param>
/// <param name="maximum">The maximum reconstructed sample value.</param>
/// <returns>The reconstructed samples.</returns>
public static abstract Vector512<ushort> DistanceWeightedHighBitDepth(
Vector512<ushort> first,
Vector512<ushort> second,
int firstWeight,
int secondWeight,
int roundBits,
int roundOffset,
int maximum);
}
/// <summary>
@ -124,6 +204,22 @@ internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor
return (byte)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, byte.MaxValue);
}
/// <inheritdoc/>
[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);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> DistanceWeighted(
@ -169,6 +265,96 @@ internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor
DistanceWeighted(first0, second0, firstWeight, secondWeight, roundBits, roundOffset),
DistanceWeighted(first1, second1, firstWeight, secondWeight, roundBits, roundOffset));
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<ushort> DistanceWeightedHighBitDepth(
Vector128<ushort> first,
Vector128<ushort> second,
int firstWeight,
int secondWeight,
int roundBits,
int roundOffset,
int maximum)
{
Vector128<uint> firstLower = Vector128.WidenLower(first);
Vector128<uint> firstUpper = Vector128.WidenUpper(first);
Vector128<uint> secondLower = Vector128.WidenLower(second);
Vector128<uint> secondUpper = Vector128.WidenUpper(second);
Vector128<uint> lower =
((firstLower * Vector128.Create((uint)firstWeight)) +
(secondLower * Vector128.Create((uint)secondWeight))) >> DistanceWeightBits;
Vector128<uint> upper =
((firstUpper * Vector128.Create((uint)firstWeight)) +
(secondUpper * Vector128.Create((uint)secondWeight))) >> DistanceWeightBits;
return FinalizeHighBitDepthIntermediate(
Vector128.Narrow(lower, upper),
roundBits,
roundOffset,
maximum);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<ushort> DistanceWeightedHighBitDepth(
Vector256<ushort> first,
Vector256<ushort> second,
int firstWeight,
int secondWeight,
int roundBits,
int roundOffset,
int maximum)
{
Vector256<uint> firstLower = Vector256.WidenLower(first);
Vector256<uint> firstUpper = Vector256.WidenUpper(first);
Vector256<uint> secondLower = Vector256.WidenLower(second);
Vector256<uint> secondUpper = Vector256.WidenUpper(second);
Vector256<uint> lower =
((firstLower * Vector256.Create((uint)firstWeight)) +
(secondLower * Vector256.Create((uint)secondWeight))) >> DistanceWeightBits;
Vector256<uint> upper =
((firstUpper * Vector256.Create((uint)firstWeight)) +
(secondUpper * Vector256.Create((uint)secondWeight))) >> DistanceWeightBits;
return FinalizeHighBitDepthIntermediate(
Vector256.Narrow(lower, upper),
roundBits,
roundOffset,
maximum);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<ushort> DistanceWeightedHighBitDepth(
Vector512<ushort> first,
Vector512<ushort> second,
int firstWeight,
int secondWeight,
int roundBits,
int roundOffset,
int maximum)
{
Vector512<uint> firstLower = Vector512.WidenLower(first);
Vector512<uint> firstUpper = Vector512.WidenUpper(first);
Vector512<uint> secondLower = Vector512.WidenLower(second);
Vector512<uint> secondUpper = Vector512.WidenUpper(second);
Vector512<uint> lower =
((firstLower * Vector512.Create((uint)firstWeight)) +
(secondLower * Vector512.Create((uint)secondWeight))) >> DistanceWeightBits;
Vector512<uint> upper =
((firstUpper * Vector512.Create((uint)firstWeight)) +
(secondUpper * Vector512.Create((uint)secondWeight))) >> DistanceWeightBits;
return FinalizeHighBitDepthIntermediate(
Vector512.Narrow(lower, upper),
roundBits,
roundOffset,
maximum);
}
/// <summary>
/// Distance-weights 128-bit lanes without overflowing the unsigned intermediate range.
/// </summary>

127
src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1CompoundIntermediateDistanceWeightedPredictor.cs

@ -153,4 +153,131 @@ internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor
}
}
}
/// <summary>
/// Combines two high-bit-depth compound intermediates using the decoded display-distance weights.
/// </summary>
public static void DistanceWeightedIntermediate(
Span<ushort> destination,
int destinationStride,
ReadOnlySpan<ushort> first,
int firstStride,
ReadOnlySpan<ushort> second,
int secondStride,
int width,
int height,
int firstWeight,
int secondWeight,
int bitDepth)
=> DistanceWeightedIntermediate<CompoundIntermediateDistanceWeightedOperator>(
destination,
destinationStride,
first,
firstStride,
second,
secondStride,
width,
height,
firstWeight,
secondWeight,
bitDepth);
/// <summary>
/// Executes one closed high-bit-depth distance-weighted compound-intermediate operator.
/// </summary>
/// <typeparam name="TOperator">The compound-intermediate operator.</typeparam>
private static void DistanceWeightedIntermediate<TOperator>(
Span<ushort> destination,
int destinationStride,
ReadOnlySpan<ushort> first,
int firstStride,
ReadOnlySpan<ushort> 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<ushort> destinationRow = destination.Slice(row * destinationStride, width);
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width);
ReadOnlySpan<ushort> 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<ushort>.Count;
for (; column <= vectorEnd; column += Vector512<ushort>.Count)
{
Vector512<ushort> firstVector = Vector512.LoadUnsafe(ref firstReference, (nuint)column);
Vector512<ushort> 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<ushort>.Count;
for (; column <= vectorEnd; column += Vector256<ushort>.Count)
{
Vector256<ushort> firstVector = Vector256.LoadUnsafe(ref firstReference, (nuint)column);
Vector256<ushort> 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<ushort>.Count;
for (; column <= vectorEnd; column += Vector128<ushort>.Count)
{
Vector128<ushort> firstVector = Vector128.LoadUnsafe(ref firstReference, (nuint)column);
Vector128<ushort> 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);
}
}
}
}

40
src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs

@ -714,16 +714,36 @@ internal sealed class Av1BlockDecoder : IDisposable
Span<ushort> highBitDepthDestination = MemoryMarshal.Cast<short, ushort>(
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
{

88
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs

@ -132,6 +132,15 @@ public class Av1CompoundBlockDecoderTests
ValidateSubpixelHighBitDepthEqualAverageCompoundPrediction,
CompoundPredictionConfigurations);
/// <summary>
/// Verifies that high-bit-depth subpixel predictors retain no-round precision until distance weighting.
/// </summary>
[Fact]
public void DecodeBlockReconstructsSubpixelHighBitDepthDistanceWeightedCompoundPrediction()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateSubpixelHighBitDepthDistanceWeightedCompoundPrediction,
CompoundPredictionConfigurations);
/// <summary>
/// Verifies that both references of a GLOBAL_GLOBALMV block use their complete matrix before compound averaging.
/// </summary>
@ -649,7 +658,18 @@ public class Av1CompoundBlockDecoderTests
{
foreach (Av1BitDepth bitDepth in new[] { Av1BitDepth.TenBit, Av1BitDepth.TwelveBit })
{
ValidateSubpixelHighBitDepthEqualAverageCompoundPredictionAtBitDepth(bitDepth);
ValidateSubpixelHighBitDepthCompoundPredictionAtBitDepth(bitDepth, Av1CompoundType.Average);
}
}
/// <summary>
/// Reconstructs the high-bit-depth subpixel distance-weighted regression at every supported source precision.
/// </summary>
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.
/// </summary>
/// <param name="bitDepth">The native sample depth.</param>
private static void ValidateSubpixelHighBitDepthEqualAverageCompoundPredictionAtBitDepth(Av1BitDepth bitDepth)
/// <param name="compoundType">The final compound operation.</param>
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<byte> frameBuffer = new(
Configuration.Default,

98
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);
/// <summary>
/// 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.
/// </summary>
[Fact]
public void HighBitDepthIntermediateAverageMatchesIndependentOracleAcrossIntrinsicWidths()
public void HighBitDepthCompoundIntermediatesMatchIndependentOracleAcrossIntrinsicWidths()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(
ValidateHighBitDepthIntermediateAverage,
ValidateHighBitDepthCompoundIntermediates,
PredictorConfigurations);
/// <summary>
/// Verifies every current libaom display-distance quantization class in both temporal directions.
/// </summary>
/// <param name="firstOrderHint">The first reference order hint.</param>
/// <param name="secondOrderHint">The second reference order hint.</param>
/// <param name="expectedFirstWeight">The expected first predictor weight.</param>
/// <param name="expectedSecondWeight">The expected second predictor weight.</param>
[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);
}
/// <summary>
/// Verifies 8-bit distance and per-sample mask blending across every intrinsic width and scalar tail.
/// </summary>
@ -294,7 +343,7 @@ public class Av1CompoundInterPredictorTests
/// <summary>
/// Applies the high-bit-depth no-round convolution equations independently of the production operators.
/// </summary>
private static void ValidateHighBitDepthIntermediateAverage()
private static void ValidateHighBitDepthCompoundIntermediates()
{
ReadOnlySpan<int> widths = [9, 17, 33, 65];
ReadOnlySpan<(int Horizontal, int Vertical)> phases =
@ -503,6 +552,47 @@ public class Av1CompoundInterPredictorTests
bitDepth);
Assert.Equal(expectedDestination, actualDestination);
ReadOnlySpan<int> 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);
}
}
}
}

16
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

Loading…
Cancel
Save