Browse Source

Preserve AV1 rate and distortion through candidate aggregation

pull/2633/head
James Jackson-South 4 weeks ago
parent
commit
fff06700db
  1. 29
      HEIF_IMPLEMENTATION_PLAN.md
  2. 62
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1RateDistortionStatistics.cs
  3. 40
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs
  4. 8
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaPaletteModeDecision.cs
  5. 157
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs
  6. 24
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.PaletteModeDecision.cs
  7. 64
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
  8. 23
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
  9. 165
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs

29
HEIF_IMPLEMENTATION_PLAN.md

@ -370,6 +370,35 @@ Film-grain decoder source comparison after `ef8b1a823`:
passing managed tests. This is bounded same-bitstream decoder evidence, not complete conformance or encoder parity. passing managed tests. This is bounded same-bitstream decoder evidence, not complete conformance or encoder parity.
No benchmark ran, and no fixture, native integration, or generated comparison output was added to the repository. No benchmark ran, and no fixture, native integration, or generated comparison output was added to the repository.
RD accumulation correction after `4c7880d`:
- Numerical defect: before this correction, `Av1IntraSuperblockEncoder.ModeDecision.cs:349-381` added
rounded child costs, `:727-731` added rounded luma/chroma costs, and `:1193-1212` added separately rounded
block syntax. Reference `av1/encoder/rd.h:32-34,208-233` keeps raw rate and distortion; rectangle and split
accumulation in `partition_search.c:3487-3504,4605-4607` rounds the combined rate. For multiplier 128,
two rates of 2 cost 1 jointly but cost 2 when rounded separately; two rates of 1 show the opposite error.
- `Av1RateDistortionStatistics.cs:9-61` now carries raw rate, distortion, and the comparison cost through
spatial, tiled, filter-intra, palette, CfL, IBC, and inter winners. Mode/transform comparisons retain their
existing strict tie rules and scalar bounds. Bounded-out split candidates retain an invalid sentinel;
only valid selected statistics are accumulated. Existing per-transform raw accumulation is preserved.
- Partition evaluation now combines those statistics in `Av1IntraSuperblockEncoder.ModeDecision.cs:336-390`;
ordinary block syntax and luma/chroma aggregation use the same retained raw inputs. This adds value state,
not a buffer or allocation. It does not implement native partition pruning, mode order, cost refresh,
quantization stages, reference control, or adaptive rate multipliers. No performance improvement is claimed.
- Seven focused regressions pass in Release .NET 11 through serialized Visual Studio VSTest
(`rd-statistics-focused.trx`, 2.6557 seconds). Four production block cases independently recount selected
syntax and pixel-domain SSE for monochrome/color and zero/nonzero residuals. Three fixed arithmetic cases
cover both rounding directions and 64-bit distortion.
- After the final C# edit, the Release .NET 11 build completed with zero errors and 1,009 existing warnings;
Roslynk reported zero compiler errors. Serialized Visual Studio VSTest passed 2,259 entropy,
intra-superblock, HEIF encoder, and reconstruction-conformance cases in 2.1328 minutes
(`rd-statistics-final.trx`), then 132 encoder-frame cases in 18.7250 seconds (`rd-statistics-frames.trx`).
The seven new cases are included in those totals. Tests encoding the existing effort policy only verify
current behavior; their success does not validate that policy against libaom.
- Optimized libaom decoding of the regenerated eight partition, 23 palette, and twelve color-sequence
streams matches all 38,973 samples exactly: maximum error 0, zero samples exceeding one. This is
same-bitstream reconstruction evidence. Separate-encoder acceptance remains unmet; no benchmark ran.
Range-writer output-capacity correction, verified after `93aba785f`: Range-writer output-capacity correction, verified after `93aba785f`:
- `Av1SymbolWriter.cs:213-214,339` before correction sliced a fixed initial allocation for finalization - `Av1SymbolWriter.cs:213-214,339` before correction sliced a fixed initial allocation for finalization

62
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1RateDistortionStatistics.cs

@ -0,0 +1,62 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
/// <summary>
/// Holds the rate, distortion, and rounded cost of an encoder candidate.
/// </summary>
internal struct Av1RateDistortionStatistics
{
/// <summary>
/// Initializes a new instance of the <see cref="Av1RateDistortionStatistics"/> struct.
/// </summary>
/// <param name="rateMultiplier">The rate multiplier for the current block.</param>
/// <param name="rate">The estimated syntax rate in 1/512-bit units.</param>
/// <param name="distortion">The candidate distortion.</param>
public Av1RateDistortionStatistics(int rateMultiplier, int rate, long distortion)
{
this.Rate = rate;
this.Distortion = distortion;
this.Cost = Av1RateDistortion.GetCost(rateMultiplier, rate, distortion);
}
/// <summary>
/// Gets the sentinel for a candidate that cannot win a cost comparison.
/// </summary>
public static Av1RateDistortionStatistics Invalid => new()
{
Rate = int.MaxValue,
Distortion = long.MaxValue,
Cost = long.MaxValue
};
/// <summary>
/// Gets the estimated syntax rate in 1/512-bit units.
/// </summary>
public int Rate { get; private set; }
/// <summary>
/// Gets the candidate distortion.
/// </summary>
public long Distortion { get; private set; }
/// <summary>
/// Gets the rounded rate-distortion cost.
/// </summary>
public long Cost { get; private set; }
/// <summary>
/// Adds a valid candidate's rate and distortion and updates the combined cost.
/// </summary>
/// <param name="rateMultiplier">The rate multiplier for the combined candidate.</param>
/// <param name="other">The valid candidate to add.</param>
public void Add(int rateMultiplier, in Av1RateDistortionStatistics other)
{
// Round the combined rate only once. Adding the already rounded child costs can change
// partition and inter/intra decisions even when both children have the same reconstruction.
this.Rate += other.Rate;
this.Distortion += other.Distortion;
this.Cost = Av1RateDistortion.GetCost(rateMultiplier, this.Rate, this.Distortion);
}
}

40
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs

@ -58,7 +58,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta, out int selectedAngleDelta,
out byte selectedChromaFromLumaIndex, out byte selectedChromaFromLumaIndex,
out sbyte selectedChromaFromLumaSigns, out sbyte selectedChromaFromLumaSigns,
out long selectedCost) out Av1RateDistortionStatistics selectedStatistics)
{ {
Av1EncoderModeDecisionWorkspace<TSample> workspace = Av1EncoderModeDecisionWorkspace<TSample> workspace =
this.blockWorkspace.GetModeDecisionWorkspace<TSample>(); this.blockWorkspace.GetModeDecisionWorkspace<TSample>();
@ -94,7 +94,7 @@ internal static partial class Av1IntraSuperblockEncoder
out selectedAngleDelta, out selectedAngleDelta,
out selectedChromaFromLumaIndex, out selectedChromaFromLumaIndex,
out selectedChromaFromLumaSigns, out selectedChromaFromLumaSigns,
out selectedCost); out selectedStatistics);
} }
int modeInfoRow = lumaOrigin.Y >> Av1Constants.ModeInfoSizeLog2; int modeInfoRow = lumaOrigin.Y >> Av1Constants.ModeInfoSizeLog2;
@ -201,7 +201,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<TSample> candidateRedReconstruction = workspace.GetCandidateReconstruction(1); Span<TSample> candidateRedReconstruction = workspace.GetCandidateReconstruction(1);
Span<int> candidateBlueCoefficients = workspace.GetCandidateCoefficients(0); Span<int> candidateBlueCoefficients = workspace.GetCandidateCoefficients(0);
Span<int> candidateRedCoefficients = workspace.GetCandidateCoefficients(1); Span<int> candidateRedCoefficients = workspace.GetCandidateCoefficients(1);
long bestCost = long.MaxValue; Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1ChromaPredictionMode bestMode = Av1ChromaPredictionMode.DC; Av1ChromaPredictionMode bestMode = Av1ChromaPredictionMode.DC;
selectedAngleDelta = 0; selectedAngleDelta = 0;
selectedChromaFromLumaIndex = 0; selectedChromaFromLumaIndex = 0;
@ -249,7 +249,7 @@ internal static partial class Av1IntraSuperblockEncoder
// A chroma mode and angle are shared by U and V, so neither plane can replace the // A chroma mode and angle are shared by U and V, so neither plane can replace the
// retained result independently. Their complete rate and distortion compete jointly. // retained result independently. Their complete rate and distortion compete jointly.
long candidateCost = this.GetChromaCandidateCost( Av1RateDistortionStatistics candidateStatistics = this.GetChromaCandidateCost(
writer, writer,
modeInfo, modeInfo,
lumaMode, lumaMode,
@ -277,7 +277,7 @@ internal static partial class Av1IntraSuperblockEncoder
ref candidateBlueState, ref candidateBlueState,
ref candidateRedState); ref candidateRedState);
if (candidateCost < bestCost) if (candidateStatistics.Cost < bestStatistics.Cost)
{ {
CopyCandidate( CopyCandidate(
candidateBlueReconstruction, candidateBlueReconstruction,
@ -299,7 +299,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateRedState, candidateRedState,
ref retainedRedStates[0]); ref retainedRedStates[0]);
bestCost = candidateCost; bestStatistics = candidateStatistics;
bestMode = chromaMode; bestMode = chromaMode;
selectedAngleDelta = angleDelta; selectedAngleDelta = angleDelta;
} }
@ -430,15 +430,15 @@ internal static partial class Av1IntraSuperblockEncoder
+ writer.GetChromaFromLumaCost(packedIndex, jointSign); + writer.GetChromaFromLumaCost(packedIndex, jointSign);
long distortion = blueDistortions[blueCandidateIndex] + redDistortions[redCandidateIndex]; long distortion = blueDistortions[blueCandidateIndex] + redDistortions[redCandidateIndex];
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion); Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
bool winsSearchOrderTie = candidateCost == bestCost bool winsSearchOrderTie = candidateStatistics.Cost == bestStatistics.Cost
&& !chromaFromLumaSelected && !chromaFromLumaSelected
&& bestMode != Av1ChromaPredictionMode.DC; && bestMode != Av1ChromaPredictionMode.DC;
// CfL follows DC and precedes every other chroma mode in the reference search order. // CfL follows DC and precedes every other chroma mode in the reference search order.
if (candidateCost < bestCost || winsSearchOrderTie) if (candidateStatistics.Cost < bestStatistics.Cost || winsSearchOrderTie)
{ {
bestCost = candidateCost; bestStatistics = candidateStatistics;
bestMode = Av1ChromaPredictionMode.ChromaFromLuma; bestMode = Av1ChromaPredictionMode.ChromaFromLuma;
selectedAngleDelta = 0; selectedAngleDelta = 0;
selectedBlueCandidateIndex = blueCandidateIndex; selectedBlueCandidateIndex = blueCandidateIndex;
@ -532,7 +532,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedRedCoefficients, retainedRedCoefficients,
ref retainedBlueStates[0], ref retainedBlueStates[0],
ref retainedRedStates[0], ref retainedRedStates[0],
ref bestCost, ref bestStatistics,
ref paletteInfo)) ref paletteInfo))
{ {
bestMode = Av1ChromaPredictionMode.DC; bestMode = Av1ChromaPredictionMode.DC;
@ -541,7 +541,7 @@ internal static partial class Av1IntraSuperblockEncoder
selectedChromaFromLumaSigns = 0; selectedChromaFromLumaSigns = 0;
} }
selectedCost = bestCost; selectedStatistics = bestStatistics;
return bestMode; return bestMode;
} }
@ -564,7 +564,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta, out int selectedAngleDelta,
out byte selectedChromaFromLumaIndex, out byte selectedChromaFromLumaIndex,
out sbyte selectedChromaFromLumaSigns, out sbyte selectedChromaFromLumaSigns,
out long selectedCost) out Av1RateDistortionStatistics selectedStatistics)
{ {
Av1EncoderModeDecisionWorkspace<TSample> workspace = Av1EncoderModeDecisionWorkspace<TSample> workspace =
this.blockWorkspace.GetModeDecisionWorkspace<TSample>(); this.blockWorkspace.GetModeDecisionWorkspace<TSample>();
@ -650,7 +650,7 @@ internal static partial class Av1IntraSuperblockEncoder
_ => baseModeCount _ => baseModeCount
}; };
long bestCost = long.MaxValue; Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1ChromaPredictionMode bestMode = Av1ChromaPredictionMode.DC; Av1ChromaPredictionMode bestMode = Av1ChromaPredictionMode.DC;
selectedAngleDelta = 0; selectedAngleDelta = 0;
selectedChromaFromLumaIndex = 0; selectedChromaFromLumaIndex = 0;
@ -745,8 +745,8 @@ internal static partial class Av1IntraSuperblockEncoder
rate += paletteDisabledCost; rate += paletteDisabledCost;
} }
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion); Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
if (candidateCost < bestCost) if (candidateStatistics.Cost < bestStatistics.Cost)
{ {
CopyTiledCandidate( CopyTiledCandidate(
candidateBlueReconstruction, candidateBlueReconstruction,
@ -772,13 +772,13 @@ internal static partial class Av1IntraSuperblockEncoder
retainedRedCoefficients, retainedRedCoefficients,
retainedRedStates); retainedRedStates);
bestCost = candidateCost; bestStatistics = candidateStatistics;
bestMode = chromaMode; bestMode = chromaMode;
selectedAngleDelta = angleDelta; selectedAngleDelta = angleDelta;
} }
} }
selectedCost = bestCost; selectedStatistics = bestStatistics;
return bestMode; return bestMode;
} }
@ -1083,7 +1083,7 @@ internal static partial class Av1IntraSuperblockEncoder
&& modeInfo.UvMode is Av1ChromaPredictionMode.Smooth or Av1ChromaPredictionMode.SmoothVertical or Av1ChromaPredictionMode.SmoothHorizontal; && modeInfo.UvMode is Av1ChromaPredictionMode.Smooth or Av1ChromaPredictionMode.SmoothVertical or Av1ChromaPredictionMode.SmoothHorizontal;
} }
private long GetChromaCandidateCost( private Av1RateDistortionStatistics GetChromaCandidateCost(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockModeInfo modeInfo, Av1MacroBlockModeInfo modeInfo,
Av1PredictionMode lumaMode, Av1PredictionMode lumaMode,
@ -1209,7 +1209,7 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes, Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false); usesInterTransformSet: false);
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion); return new(this.rateMultiplier, rate, distortion);
} }
/// <summary> /// <summary>

8
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaPaletteModeDecision.cs

@ -38,7 +38,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<int> retainedRedCoefficients, Span<int> retainedRedCoefficients,
ref Av1EncoderTransformBlockState retainedBlueState, ref Av1EncoderTransformBlockState retainedBlueState,
ref Av1EncoderTransformBlockState retainedRedState, ref Av1EncoderTransformBlockState retainedRedState,
ref long bestCost, ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo) ref Av1EncoderPaletteInfo paletteInfo)
{ {
const Av1BlockSize BlockSize = Av1BlockSize.Block8x8; const Av1BlockSize BlockSize = Av1BlockSize.Block8x8;
@ -336,8 +336,8 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes, Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false); usesInterTransformSet: false);
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion); Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
if (candidateCost < bestCost) if (candidateStatistics.Cost < bestStatistics.Cost)
{ {
// Every following palette size overwrites the shared maps and candidate spans, so a // Every following palette size overwrites the shared maps and candidate spans, so a
// global improvement must retain reconstruction, coefficients, colors, and indices together. // global improvement must retain reconstruction, coefficients, colors, and indices together.
@ -372,7 +372,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo.PaletteSizes[1] = (byte)paletteSize; paletteInfo.PaletteSizes[1] = (byte)paletteSize;
paletteInfo.SetColors(Av1Plane.U, bluePaletteColors); paletteInfo.SetColors(Av1Plane.U, bluePaletteColors);
paletteInfo.SetColors(Av1Plane.V, redPaletteColors); paletteInfo.SetColors(Av1Plane.V, redPaletteColors);
bestCost = candidateCost; bestStatistics = candidateStatistics;
paletteSelected = true; paletteSelected = true;
} }
} }

157
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs

@ -139,7 +139,6 @@ internal static partial class Av1IntraSuperblockEncoder
private readonly int effort; private readonly int effort;
private int codedAreaLuma; private int codedAreaLuma;
private int codedAreaChroma; private int codedAreaChroma;
private long selectedBlockCost;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ModeDecision{TSample, TOperator}"/> struct. /// Initializes a new instance of the <see cref="ModeDecision{TSample, TOperator}"/> struct.
@ -178,9 +177,14 @@ internal static partial class Av1IntraSuperblockEncoder
this.effort = effort; this.effort = effort;
this.codedAreaLuma = 0; this.codedAreaLuma = 0;
this.codedAreaChroma = 0; this.codedAreaChroma = 0;
this.selectedBlockCost = 0; this.SelectedBlockStatistics = default;
} }
/// <summary>
/// Gets the statistics of the most recently encoded block.
/// </summary>
public Av1RateDistortionStatistics SelectedBlockStatistics { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public Av1PartitionType SelectPartition( public Av1PartitionType SelectPartition(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
@ -254,7 +258,7 @@ internal static partial class Av1IntraSuperblockEncoder
int savedLumaArea = this.codedAreaLuma; int savedLumaArea = this.codedAreaLuma;
int savedChromaArea = this.codedAreaChroma; int savedChromaArea = this.codedAreaChroma;
this.SavePartitionTrialContexts(blockOrigin, tileIndex, blockSize); this.SavePartitionTrialContexts(blockOrigin, tileIndex, blockSize);
long bestCost = long.MaxValue; Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PartitionType selectedPartition = Av1PartitionType.None; Av1PartitionType selectedPartition = Av1PartitionType.None;
ReadOnlySpan<Av1PartitionType> searchOrder = PartitionSearchOrder; ReadOnlySpan<Av1PartitionType> searchOrder = PartitionSearchOrder;
int candidateCount = blockSize == Av1BlockSize.Block8x8 ? 4 : searchOrder.Length; int candidateCount = blockSize == Av1BlockSize.Block8x8 ? 4 : searchOrder.Length;
@ -266,7 +270,7 @@ internal static partial class Av1IntraSuperblockEncoder
continue; continue;
} }
long candidateCost = this.EvaluatePartitionCandidate( Av1RateDistortionStatistics candidateStatistics = this.EvaluatePartitionCandidate(
writer, writer,
macroBlock, macroBlock,
blockOrigin, blockOrigin,
@ -275,9 +279,9 @@ internal static partial class Av1IntraSuperblockEncoder
partitionType, partitionType,
publishFinalContexts: false); publishFinalContexts: false);
if (candidateCost < bestCost) if (candidateStatistics.Cost < bestStatistics.Cost)
{ {
bestCost = candidateCost; bestStatistics = candidateStatistics;
selectedPartition = partitionType; selectedPartition = partitionType;
} }
@ -292,7 +296,7 @@ internal static partial class Av1IntraSuperblockEncoder
return selectedPartition; return selectedPartition;
} }
private long EvaluateSelectedPartitionTree( private Av1RateDistortionStatistics EvaluateSelectedPartitionTree(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Point blockOrigin, Point blockOrigin,
@ -307,7 +311,7 @@ internal static partial class Av1IntraSuperblockEncoder
tileIndex, tileIndex,
blockSize); blockSize);
long cost = this.EvaluatePartitionCandidate( Av1RateDistortionStatistics statistics = this.EvaluatePartitionCandidate(
writer, writer,
macroBlock, macroBlock,
blockOrigin, blockOrigin,
@ -326,10 +330,10 @@ internal static partial class Av1IntraSuperblockEncoder
selectedPartition); selectedPartition);
} }
return cost; return statistics;
} }
private long EvaluatePartitionCandidate( private Av1RateDistortionStatistics EvaluatePartitionCandidate(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Point blockOrigin, Point blockOrigin,
@ -346,7 +350,7 @@ internal static partial class Av1IntraSuperblockEncoder
blockOrigin, blockOrigin,
this.picture.PartitionContexts[tileIndex]); this.picture.PartitionContexts[tileIndex]);
long cost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, 0); Av1RateDistortionStatistics statistics = new(this.rateMultiplier, rate, 0);
int leafCount = GetPartitionLeafCount(partitionType); int leafCount = GetPartitionLeafCount(partitionType);
// Child reconstruction and syntax contexts become input to the next child. Publishing only // Child reconstruction and syntax contexts become input to the next child. Publishing only
@ -362,7 +366,7 @@ internal static partial class Av1IntraSuperblockEncoder
out Av1BlockSize leafSize); out Av1BlockSize leafSize);
bool publishContexts = leafIndex < leafCount - 1 || publishFinalContexts; bool publishContexts = leafIndex < leafCount - 1 || publishFinalContexts;
cost += partitionType == Av1PartitionType.Split && blockSize > Av1BlockSize.Block8x8 Av1RateDistortionStatistics childStatistics = partitionType == Av1PartitionType.Split && blockSize > Av1BlockSize.Block8x8
? this.EvaluateSelectedPartitionTree( ? this.EvaluateSelectedPartitionTree(
writer, writer,
macroBlock, macroBlock,
@ -378,9 +382,11 @@ internal static partial class Av1IntraSuperblockEncoder
leafSize, leafSize,
partitionType == Av1PartitionType.Split ? Av1PartitionType.None : partitionType, partitionType == Av1PartitionType.Split ? Av1PartitionType.None : partitionType,
publishContexts); publishContexts);
statistics.Add(this.rateMultiplier, in childStatistics);
} }
return cost; return statistics;
} }
private void ResetPartitionTrial( private void ResetPartitionTrial(
@ -613,7 +619,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int lumaAngleDelta, out int lumaAngleDelta,
out Av1FilterIntraMode filterIntraMode, out Av1FilterIntraMode filterIntraMode,
out Av1TransformSize lumaTransformSize, out Av1TransformSize lumaTransformSize,
out long lumaCost); out Av1RateDistortionStatistics lumaStatistics);
block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] = (sbyte)lumaAngleDelta; block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] = (sbyte)lumaAngleDelta;
block.FilterIntraMode = filterIntraMode; block.FilterIntraMode = filterIntraMode;
@ -626,37 +632,37 @@ internal static partial class Av1IntraSuperblockEncoder
bool allowIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 && bool allowIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 &&
this.picture.Parent.FrameHeader.AllowIntraBlockCopy; this.picture.Parent.FrameHeader.AllowIntraBlockCopy;
long regularCost = this.GetRegularBlockCost( Av1RateDistortionStatistics regularStatistics = this.GetRegularBlockCost(
writer, writer,
macroBlock, macroBlock,
lumaCost, lumaStatistics,
allowIntraBlockCopy); allowIntraBlockCopy);
if (!this.picture.Parent.FrameHeader.IsIntra) if (!this.picture.Parent.FrameHeader.IsIntra)
{ {
this.selectedBlockCost = this.SelectInterPrediction( this.SelectedBlockStatistics = this.SelectInterPrediction(
writer, writer,
macroBlock, macroBlock,
blockOrigin, blockOrigin,
tileIndex, tileIndex,
regularCost, regularStatistics,
ref modeInfo, ref modeInfo,
ref block, ref block,
ref paletteInfo); ref paletteInfo);
} }
else else
{ {
this.selectedBlockCost = allowIntraBlockCopy this.SelectedBlockStatistics = allowIntraBlockCopy
? this.SelectIntraBlockCopy( ? this.SelectIntraBlockCopy(
writer, writer,
macroBlock, macroBlock,
blockOrigin, blockOrigin,
tileIndex, tileIndex,
regularCost, regularStatistics,
ref modeInfo, ref modeInfo,
ref block, ref block,
ref paletteInfo) ref paletteInfo)
: regularCost; : regularStatistics;
} }
this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight(); this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight();
@ -693,7 +699,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<Av1EncoderTransformBlockState> retainedBlueStates = blueTransformBlocks[chromaTransformIndex..]; Span<Av1EncoderTransformBlockState> retainedBlueStates = blueTransformBlocks[chromaTransformIndex..];
Span<Av1EncoderTransformBlockState> retainedRedStates = redTransformBlocks[chromaTransformIndex..]; Span<Av1EncoderTransformBlockState> retainedRedStates = redTransformBlocks[chromaTransformIndex..];
long chromaCost = 0; Av1RateDistortionStatistics chromaStatistics = default;
if (block.HasChroma) if (block.HasChroma)
{ {
modeInfo.Block.UvMode = this.SelectChromaMode( modeInfo.Block.UvMode = this.SelectChromaMode(
@ -714,7 +720,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int chromaAngleDelta, out int chromaAngleDelta,
out byte chromaFromLumaIndex, out byte chromaFromLumaIndex,
out sbyte chromaFromLumaSigns, out sbyte chromaFromLumaSigns,
out chromaCost); out chromaStatistics);
block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = (sbyte)chromaAngleDelta; block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = (sbyte)chromaAngleDelta;
block.PredictionUnit.ChromaFromLumaIndex = chromaFromLumaIndex; block.PredictionUnit.ChromaFromLumaIndex = chromaFromLumaIndex;
@ -724,37 +730,38 @@ internal static partial class Av1IntraSuperblockEncoder
bool allowColorIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 && bool allowColorIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 &&
this.picture.Parent.FrameHeader.AllowIntraBlockCopy; this.picture.Parent.FrameHeader.AllowIntraBlockCopy;
long regularColorCost = this.GetRegularBlockCost( lumaStatistics.Add(this.rateMultiplier, in chromaStatistics);
Av1RateDistortionStatistics regularColorStatistics = this.GetRegularBlockCost(
writer, writer,
macroBlock, macroBlock,
lumaCost + chromaCost, lumaStatistics,
allowColorIntraBlockCopy); allowColorIntraBlockCopy);
if (!this.picture.Parent.FrameHeader.IsIntra) if (!this.picture.Parent.FrameHeader.IsIntra)
{ {
this.selectedBlockCost = this.SelectInterPrediction( this.SelectedBlockStatistics = this.SelectInterPrediction(
writer, writer,
macroBlock, macroBlock,
blockOrigin, blockOrigin,
tileIndex, tileIndex,
regularColorCost, regularColorStatistics,
ref modeInfo, ref modeInfo,
ref block, ref block,
ref paletteInfo); ref paletteInfo);
} }
else else
{ {
this.selectedBlockCost = allowColorIntraBlockCopy this.SelectedBlockStatistics = allowColorIntraBlockCopy
? this.SelectIntraBlockCopy( ? this.SelectIntraBlockCopy(
writer, writer,
macroBlock, macroBlock,
blockOrigin, blockOrigin,
tileIndex, tileIndex,
regularColorCost, regularColorStatistics,
ref modeInfo, ref modeInfo,
ref block, ref block,
ref paletteInfo) ref paletteInfo)
: regularColorCost; : regularColorStatistics;
} }
this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight(); this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight();
@ -764,7 +771,7 @@ internal static partial class Av1IntraSuperblockEncoder
} }
} }
private long EvaluatePartitionLeaf( private Av1RateDistortionStatistics EvaluatePartitionLeaf(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Point blockOrigin, Point blockOrigin,
@ -812,7 +819,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo); paletteInfo);
} }
return this.selectedBlockCost; return this.SelectedBlockStatistics;
} }
private void SetBlockGeometry( private void SetBlockGeometry(
@ -1190,10 +1197,10 @@ internal static partial class Av1IntraSuperblockEncoder
offset += left.Length; offset += left.Length;
} }
private long GetRegularBlockCost( private Av1RateDistortionStatistics GetRegularBlockCost(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
long modeCost, Av1RateDistortionStatistics modeStatistics,
bool allowIntraBlockCopy) bool allowIntraBlockCopy)
{ {
int rateAdjustment = writer.GetSkipCost(false, Av1TileWriter.GetSkipContext(macroBlock)); int rateAdjustment = writer.GetSkipCost(false, Av1TileWriter.GetSkipContext(macroBlock));
@ -1208,7 +1215,7 @@ internal static partial class Av1IntraSuperblockEncoder
rateAdjustment += writer.GetUseIntraBlockCopyCost(false); rateAdjustment += writer.GetUseIntraBlockCopyCost(false);
} }
return modeCost + Av1RateDistortion.GetCost(this.rateMultiplier, rateAdjustment, 0); return new(this.rateMultiplier, modeStatistics.Rate + rateAdjustment, modeStatistics.Distortion);
} }
private Av1PredictionMode SelectLumaMode( private Av1PredictionMode SelectLumaMode(
@ -1223,7 +1230,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta, out int selectedAngleDelta,
out Av1FilterIntraMode selectedFilterIntraMode, out Av1FilterIntraMode selectedFilterIntraMode,
out Av1TransformSize selectedTransformSize, out Av1TransformSize selectedTransformSize,
out long selectedCost) out Av1RateDistortionStatistics selectedStatistics)
{ {
bool codedLossless = this.picture.Parent.FrameHeader.CodedLossless; bool codedLossless = this.picture.Parent.FrameHeader.CodedLossless;
Av1TransformSize transformSize = codedLossless Av1TransformSize transformSize = codedLossless
@ -1251,7 +1258,7 @@ internal static partial class Av1IntraSuperblockEncoder
out selectedAngleDelta, out selectedAngleDelta,
out selectedFilterIntraMode, out selectedFilterIntraMode,
out selectedTransformSize, out selectedTransformSize,
out selectedCost); out selectedStatistics);
} }
bool hasLeft = macroBlock.IsLeftAvailable; bool hasLeft = macroBlock.IsLeftAvailable;
@ -1347,7 +1354,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<int> candidateCoefficients = workspace.GetCandidateCoefficients(0); Span<int> candidateCoefficients = workspace.GetCandidateCoefficients(0);
Span<TSample> prediction = workspace.Prediction; Span<TSample> prediction = workspace.Prediction;
Span<short> residual = workspace.Residual; Span<short> residual = workspace.Residual;
long bestCost = long.MaxValue; Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PredictionMode bestMode = Av1PredictionMode.DC; Av1PredictionMode bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0; selectedAngleDelta = 0;
selectedFilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes; selectedFilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes;
@ -1439,7 +1446,7 @@ internal static partial class Av1IntraSuperblockEncoder
} }
Av1EncoderTransformBlockState candidateState = default; Av1EncoderTransformBlockState candidateState = default;
long candidateCost = this.GetLumaCandidateCost( Av1RateDistortionStatistics candidateStatistics = this.GetLumaCandidateCost(
writer, writer,
macroBlock, macroBlock,
sourcePlane, sourcePlane,
@ -1458,7 +1465,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients, candidateCoefficients,
ref candidateState); ref candidateState);
if (candidateCost < bestCost) if (candidateStatistics.Cost < bestStatistics.Cost)
{ {
// The shared candidate spans are overwritten by the next transform. Copy only a // The shared candidate spans are overwritten by the next transform. Copy only a
// global improvement into final block storage so no per-mode retained buffer is needed. // global improvement into final block storage so no per-mode retained buffer is needed.
@ -1472,7 +1479,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState, candidateState,
ref retainedStates[0]); ref retainedStates[0]);
bestCost = candidateCost; bestStatistics = candidateStatistics;
bestMode = mode; bestMode = mode;
selectedAngleDelta = angleDelta; selectedAngleDelta = angleDelta;
selectedTransformSize = transformSize; selectedTransformSize = transformSize;
@ -1484,7 +1491,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (searchEveryTransformSize && if (searchEveryTransformSize &&
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select) this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select)
{ {
long splitCost = this.GetSplitLumaCandidateCost( Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer, writer,
macroBlock, macroBlock,
sourcePlane, sourcePlane,
@ -1499,12 +1506,12 @@ internal static partial class Av1IntraSuperblockEncoder
0, 0,
paletteDisabledCost, paletteDisabledCost,
transformSizeContext, transformSizeContext,
bestCost, bestStatistics.Cost,
candidateReconstruction, candidateReconstruction,
candidateCoefficients, candidateCoefficients,
workspace.CandidateTransformBlocks); workspace.CandidateTransformBlocks);
if (splitCost < bestCost) if (splitStatistics.Cost < bestStatistics.Cost)
{ {
CopySplitCandidate( CopySplitCandidate(
candidateReconstruction, candidateReconstruction,
@ -1515,7 +1522,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients, retainedCoefficients,
retainedStates); retainedStates);
bestCost = splitCost; bestStatistics = splitStatistics;
bestMode = mode; bestMode = mode;
selectedAngleDelta = angleDelta; selectedAngleDelta = angleDelta;
selectedTransformSize = Av1TransformSize.Size4x4; selectedTransformSize = Av1TransformSize.Size4x4;
@ -1525,7 +1532,7 @@ internal static partial class Av1IntraSuperblockEncoder
// Midrange effort refines the preliminary mode only. Higher effort already searched every // Midrange effort refines the preliminary mode only. Higher effort already searched every
// mode-transform pair above, so repeating the winning mode would add no candidates. // mode-transform pair above, so repeating the winning mode would add no candidates.
long bestTransformCost = bestCost; Av1RateDistortionStatistics bestTransformStatistics = bestStatistics;
if (!codedLossless && this.effort >= 3 && !searchEveryTransformType) if (!codedLossless && this.effort >= 3 && !searchEveryTransformType)
{ {
// The shared spans now contain the last mode visited above, so rebuild the preliminary // The shared spans now contain the last mode visited above, so rebuild the preliminary
@ -1557,7 +1564,7 @@ internal static partial class Av1IntraSuperblockEncoder
} }
Av1EncoderTransformBlockState candidateState = default; Av1EncoderTransformBlockState candidateState = default;
long candidateCost = this.GetLumaCandidateCost( Av1RateDistortionStatistics candidateStatistics = this.GetLumaCandidateCost(
writer, writer,
macroBlock, macroBlock,
sourcePlane, sourcePlane,
@ -1576,7 +1583,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients, candidateCoefficients,
ref candidateState); ref candidateState);
if (candidateCost < bestTransformCost) if (candidateStatistics.Cost < bestTransformStatistics.Cost)
{ {
CopyCandidate( CopyCandidate(
candidateReconstruction, candidateReconstruction,
@ -1588,7 +1595,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState, candidateState,
ref retainedStates[0]); ref retainedStates[0]);
bestTransformCost = candidateCost; bestTransformStatistics = candidateStatistics;
} }
} }
} }
@ -1630,7 +1637,7 @@ internal static partial class Av1IntraSuperblockEncoder
} }
Av1EncoderTransformBlockState candidateState = default; Av1EncoderTransformBlockState candidateState = default;
long candidateCost = this.GetFilterIntraCandidateCost( Av1RateDistortionStatistics candidateStatistics = this.GetFilterIntraCandidateCost(
writer, writer,
macroBlock, macroBlock,
sourcePlane, sourcePlane,
@ -1648,7 +1655,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients, candidateCoefficients,
ref candidateState); ref candidateState);
if (candidateCost < bestTransformCost) if (candidateStatistics.Cost < bestTransformStatistics.Cost)
{ {
CopyCandidate( CopyCandidate(
candidateReconstruction, candidateReconstruction,
@ -1660,7 +1667,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState, candidateState,
ref retainedStates[0]); ref retainedStates[0]);
bestTransformCost = candidateCost; bestTransformStatistics = candidateStatistics;
bestMode = Av1PredictionMode.DC; bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0; selectedAngleDelta = 0;
selectedFilterIntraMode = filterIntraMode; selectedFilterIntraMode = filterIntraMode;
@ -1673,7 +1680,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (searchEveryTransformSize && if (searchEveryTransformSize &&
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select) this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select)
{ {
long splitCost = this.GetSplitLumaCandidateCost( Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer, writer,
macroBlock, macroBlock,
sourcePlane, sourcePlane,
@ -1688,12 +1695,12 @@ internal static partial class Av1IntraSuperblockEncoder
0, 0,
paletteDisabledCost, paletteDisabledCost,
transformSizeContext, transformSizeContext,
bestTransformCost, bestTransformStatistics.Cost,
candidateReconstruction, candidateReconstruction,
candidateCoefficients, candidateCoefficients,
workspace.CandidateTransformBlocks); workspace.CandidateTransformBlocks);
if (splitCost < bestTransformCost) if (splitStatistics.Cost < bestTransformStatistics.Cost)
{ {
CopySplitCandidate( CopySplitCandidate(
candidateReconstruction, candidateReconstruction,
@ -1704,7 +1711,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients, retainedCoefficients,
retainedStates); retainedStates);
bestTransformCost = splitCost; bestTransformStatistics = splitStatistics;
bestMode = Av1PredictionMode.DC; bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0; selectedAngleDelta = 0;
selectedFilterIntraMode = filterIntraMode; selectedFilterIntraMode = filterIntraMode;
@ -1732,7 +1739,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients, candidateCoefficients,
retainedCoefficients, retainedCoefficients,
retainedStates, retainedStates,
ref bestTransformCost, ref bestTransformStatistics,
ref paletteInfo, ref paletteInfo,
ref selectedTransformSize)) ref selectedTransformSize))
{ {
@ -1749,7 +1756,7 @@ internal static partial class Av1IntraSuperblockEncoder
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select && this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select &&
paletteInfo.PaletteSizes[0] == 0) paletteInfo.PaletteSizes[0] == 0)
{ {
long splitCost = this.GetSplitLumaCandidateCost( Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer, writer,
macroBlock, macroBlock,
sourcePlane, sourcePlane,
@ -1764,12 +1771,12 @@ internal static partial class Av1IntraSuperblockEncoder
0, 0,
paletteDisabledCost, paletteDisabledCost,
transformSizeContext, transformSizeContext,
bestTransformCost, bestTransformStatistics.Cost,
candidateReconstruction, candidateReconstruction,
candidateCoefficients, candidateCoefficients,
workspace.CandidateTransformBlocks); workspace.CandidateTransformBlocks);
if (splitCost < bestTransformCost) if (splitStatistics.Cost < bestTransformStatistics.Cost)
{ {
CopySplitCandidate( CopySplitCandidate(
candidateReconstruction, candidateReconstruction,
@ -1780,12 +1787,12 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients, retainedCoefficients,
retainedStates); retainedStates);
bestTransformCost = splitCost; bestTransformStatistics = splitStatistics;
selectedTransformSize = Av1TransformSize.Size4x4; selectedTransformSize = Av1TransformSize.Size4x4;
} }
} }
selectedCost = bestTransformCost; selectedStatistics = bestTransformStatistics;
return bestMode; return bestMode;
} }
@ -1801,7 +1808,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta, out int selectedAngleDelta,
out Av1FilterIntraMode selectedFilterIntraMode, out Av1FilterIntraMode selectedFilterIntraMode,
out Av1TransformSize selectedTransformSize, out Av1TransformSize selectedTransformSize,
out long selectedCost) out Av1RateDistortionStatistics selectedStatistics)
{ {
Av1EncoderModeDecisionWorkspace<TSample> workspace = Av1EncoderModeDecisionWorkspace<TSample> workspace =
this.blockWorkspace.GetModeDecisionWorkspace<TSample>(); this.blockWorkspace.GetModeDecisionWorkspace<TSample>();
@ -1868,7 +1875,7 @@ internal static partial class Av1IntraSuperblockEncoder
Buffer2DRegion<TSample> sourcePlane = this.source.GetPlane(Av1Plane.Y); Buffer2DRegion<TSample> sourcePlane = this.source.GetPlane(Av1Plane.Y);
Buffer2DRegion<TSample> reconstructionPlane = this.reconstruction.GetPlane(Av1Plane.Y); Buffer2DRegion<TSample> reconstructionPlane = this.reconstruction.GetPlane(Av1Plane.Y);
long bestCost = long.MaxValue; Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PredictionMode bestMode = Av1PredictionMode.DC; Av1PredictionMode bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0; selectedAngleDelta = 0;
selectedFilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes; selectedFilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes;
@ -1942,8 +1949,8 @@ internal static partial class Av1IntraSuperblockEncoder
} }
} }
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion); Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
if (candidateCost < bestCost) if (candidateStatistics.Cost < bestStatistics.Cost)
{ {
CopyTiledCandidate( CopyTiledCandidate(
candidateReconstruction, candidateReconstruction,
@ -1957,17 +1964,17 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients, retainedCoefficients,
retainedStates); retainedStates);
bestCost = candidateCost; bestStatistics = candidateStatistics;
bestMode = mode; bestMode = mode;
selectedAngleDelta = angleDelta; selectedAngleDelta = angleDelta;
} }
} }
selectedCost = bestCost; selectedStatistics = bestStatistics;
return bestMode; return bestMode;
} }
private long GetSplitLumaCandidateCost( private Av1RateDistortionStatistics GetSplitLumaCandidateCost(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Buffer2DRegion<TSample> sourcePlane, Buffer2DRegion<TSample> sourcePlane,
@ -2267,12 +2274,12 @@ internal static partial class Av1IntraSuperblockEncoder
// Every remaining transform can only add nonnegative rate and distortion. // Every remaining transform can only add nonnegative rate and distortion.
if (Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion) >= costLimit) if (Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion) >= costLimit)
{ {
return long.MaxValue; return Av1RateDistortionStatistics.Invalid;
} }
} }
} }
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion); return new(this.rateMultiplier, rate, distortion);
} }
private void PrepareTransformReferenceSamples( private void PrepareTransformReferenceSamples(
@ -2471,7 +2478,7 @@ internal static partial class Av1IntraSuperblockEncoder
leftStorage[0] = corner; leftStorage[0] = corner;
} }
private long GetLumaCandidateCost( private Av1RateDistortionStatistics GetLumaCandidateCost(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Buffer2DRegion<TSample> sourcePlane, Buffer2DRegion<TSample> sourcePlane,
@ -2546,10 +2553,10 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes, Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false); usesInterTransformSet: false);
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion); return new(this.rateMultiplier, rate, distortion);
} }
private long GetFilterIntraCandidateCost( private Av1RateDistortionStatistics GetFilterIntraCandidateCost(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Buffer2DRegion<TSample> sourcePlane, Buffer2DRegion<TSample> sourcePlane,
@ -2608,7 +2615,7 @@ internal static partial class Av1IntraSuperblockEncoder
filterIntraMode, filterIntraMode,
usesInterTransformSet: false); usesInterTransformSet: false);
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion); return new(this.rateMultiplier, rate, distortion);
} }
private static void CopyCandidate( private static void CopyCandidate(

24
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.PaletteModeDecision.cs

@ -34,7 +34,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<int> candidateCoefficients, Span<int> candidateCoefficients,
Span<int> retainedCoefficients, Span<int> retainedCoefficients,
Span<Av1EncoderTransformBlockState> retainedStates, Span<Av1EncoderTransformBlockState> retainedStates,
ref long bestCost, ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo, ref Av1EncoderPaletteInfo paletteInfo,
ref Av1TransformSize selectedTransformSize) ref Av1TransformSize selectedTransformSize)
{ {
@ -164,7 +164,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates, retainedStates,
retainedColorIndexMap, retainedColorIndexMap,
reconstructionPlane, reconstructionPlane,
ref bestCost, ref bestStatistics,
ref paletteInfo, ref paletteInfo,
ref selectedTransformSize, ref selectedTransformSize,
ref paletteSelected); ref paletteSelected);
@ -197,7 +197,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates, retainedStates,
retainedColorIndexMap, retainedColorIndexMap,
reconstructionPlane, reconstructionPlane,
ref bestCost, ref bestStatistics,
ref paletteInfo, ref paletteInfo,
ref selectedTransformSize, ref selectedTransformSize,
ref paletteSelected); ref paletteSelected);
@ -239,7 +239,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates, retainedStates,
retainedColorIndexMap, retainedColorIndexMap,
reconstructionPlane, reconstructionPlane,
ref bestCost, ref bestStatistics,
ref paletteInfo, ref paletteInfo,
ref selectedTransformSize, ref selectedTransformSize,
ref paletteSelected); ref paletteSelected);
@ -281,7 +281,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<Av1EncoderTransformBlockState> retainedStates, Span<Av1EncoderTransformBlockState> retainedStates,
Span<byte> retainedColorIndexMap, Span<byte> retainedColorIndexMap,
Buffer2DRegion<TSample> reconstructionPlane, Buffer2DRegion<TSample> reconstructionPlane,
ref long bestCost, ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo, ref Av1EncoderPaletteInfo paletteInfo,
ref Av1TransformSize selectedTransformSize, ref Av1TransformSize selectedTransformSize,
ref bool paletteSelected) ref bool paletteSelected)
@ -429,8 +429,8 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes, Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false); usesInterTransformSet: false);
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, candidateRate, distortion); Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, candidateRate, distortion);
if (candidateCost < bestCost) if (candidateStatistics.Cost < bestStatistics.Cost)
{ {
// Later palette sizes reuse every candidate span and the shared color map. Publish the // Later palette sizes reuse every candidate span and the shared color map. Publish the
// complete palette state only when this candidate improves the global luma decision. // complete palette state only when this candidate improves the global luma decision.
@ -453,7 +453,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo.PaletteSizes[0] = (byte)paletteSize; paletteInfo.PaletteSizes[0] = (byte)paletteSize;
paletteInfo.SetColors(Av1Plane.Y, paletteColors); paletteInfo.SetColors(Av1Plane.Y, paletteColors);
selectedTransformSize = TransformSize; selectedTransformSize = TransformSize;
bestCost = candidateCost; bestStatistics = candidateStatistics;
paletteSelected = true; paletteSelected = true;
} }
} }
@ -463,7 +463,7 @@ internal static partial class Av1IntraSuperblockEncoder
{ {
// Transform size is part of each palette candidate's RD result. Searching it here preserves // Transform size is part of each palette candidate's RD result. Searching it here preserves
// candidates whose 4x4 residual partition wins even when their 8x8 result does not. // candidates whose 4x4 residual partition wins even when their 8x8 result does not.
long splitCost = this.GetSplitLumaCandidateCost( Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer, writer,
macroBlock, macroBlock,
this.source.GetPlane(Av1Plane.Y), this.source.GetPlane(Av1Plane.Y),
@ -478,12 +478,12 @@ internal static partial class Av1IntraSuperblockEncoder
rate, rate,
0, 0,
transformSizeContext, transformSizeContext,
bestCost, bestStatistics.Cost,
candidateReconstruction, candidateReconstruction,
candidateCoefficients, candidateCoefficients,
modeDecisionWorkspace.CandidateTransformBlocks); modeDecisionWorkspace.CandidateTransformBlocks);
if (splitCost < bestCost) if (splitStatistics.Cost < bestStatistics.Cost)
{ {
CopySplitCandidate( CopySplitCandidate(
candidateReconstruction, candidateReconstruction,
@ -503,7 +503,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo.PaletteSizes[0] = (byte)paletteSize; paletteInfo.PaletteSizes[0] = (byte)paletteSize;
paletteInfo.SetColors(Av1Plane.Y, paletteColors); paletteInfo.SetColors(Av1Plane.Y, paletteColors);
selectedTransformSize = Av1TransformSize.Size4x4; selectedTransformSize = Av1TransformSize.Size4x4;
bestCost = splitCost; bestStatistics = splitStatistics;
paletteSelected = true; paletteSelected = true;
} }
} }

64
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs

@ -57,12 +57,12 @@ internal static partial class Av1IntraSuperblockEncoder
where TSample : unmanaged where TSample : unmanaged
where TOperator : struct, IBlockEncodingOperator<TSample> where TOperator : struct, IBlockEncodingOperator<TSample>
{ {
private long SelectIntraBlockCopy( private Av1RateDistortionStatistics SelectIntraBlockCopy(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Point blockOrigin, Point blockOrigin,
ushort tileIndex, ushort tileIndex,
long regularCost, Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo, ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block, ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo) ref Av1EncoderPaletteInfo paletteInfo)
@ -135,11 +135,11 @@ internal static partial class Av1IntraSuperblockEncoder
if (uniqueCandidateCount == 0) if (uniqueCandidateCount == 0)
{ {
return regularCost; return regularStatistics;
} }
int skipContext = Av1TileWriter.GetSkipContext(macroBlock); int skipContext = Av1TileWriter.GetSkipContext(macroBlock);
long bestCost = regularCost; Av1RateDistortionStatistics bestStatistics = regularStatistics;
bool hasSelectedCandidate = false; bool hasSelectedCandidate = false;
bool selectedSkip = false; bool selectedSkip = false;
Av1MotionVector selectedVector = default; Av1MotionVector selectedVector = default;
@ -336,7 +336,7 @@ internal static partial class Av1IntraSuperblockEncoder
redRate; redRate;
long candidateDistortion = lumaDistortion + blueDistortion + redDistortion; long candidateDistortion = lumaDistortion + blueDistortion + redDistortion;
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, candidateRate, candidateDistortion); Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, candidateRate, candidateDistortion);
bool candidateSkip = false; bool candidateSkip = false;
// The skip alternative is available only when every coded plane has an empty transform. Its // The skip alternative is available only when every coded plane has an empty transform. Its
@ -348,21 +348,21 @@ internal static partial class Av1IntraSuperblockEncoder
writer.GetSkipCost(true, skipContext); writer.GetSkipCost(true, skipContext);
long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion; long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion;
long skipCost = Av1RateDistortion.GetCost(this.rateMultiplier, skipRate, skipDistortion); Av1RateDistortionStatistics skipStatistics = new(this.rateMultiplier, skipRate, skipDistortion);
if (skipCost < candidateCost) if (skipStatistics.Cost < candidateStatistics.Cost)
{ {
candidateCost = skipCost; candidateStatistics = skipStatistics;
candidateSkip = true; candidateSkip = true;
} }
} }
// Conventional intra and earlier IBC vectors retain strict search-order precedence on equal RD. // Conventional intra and earlier IBC vectors retain strict search-order precedence on equal RD.
if (candidateCost >= bestCost) if (candidateStatistics.Cost >= bestStatistics.Cost)
{ {
continue; continue;
} }
bestCost = candidateCost; bestStatistics = candidateStatistics;
hasSelectedCandidate = true; hasSelectedCandidate = true;
selectedSkip = candidateSkip; selectedSkip = candidateSkip;
selectedVector = candidate; selectedVector = candidate;
@ -410,7 +410,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (!hasSelectedCandidate) if (!hasSelectedCandidate)
{ {
return bestCost; return bestStatistics;
} }
// Only the winning vector is now visible to later coding blocks. This single publication keeps // Only the winning vector is now visible to later coding blocks. This single publication keeps
@ -481,18 +481,18 @@ internal static partial class Av1IntraSuperblockEncoder
block.PredictionUnit.ChromaFromLumaSigns = 0; block.PredictionUnit.ChromaFromLumaSigns = 0;
paletteInfo = default; paletteInfo = default;
this.picture.SetDisplacementVector(modeInfoPosition, selectedVector); this.picture.SetDisplacementVector(modeInfoPosition, selectedVector);
return bestCost; return bestStatistics;
} }
/// <summary> /// <summary>
/// Compares the retained intra result with an inter candidate without disturbing the intra result on loss. /// Compares the retained intra result with an inter candidate without disturbing the intra result on loss.
/// </summary> /// </summary>
private long SelectInterPrediction( private Av1RateDistortionStatistics SelectInterPrediction(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Point blockOrigin, Point blockOrigin,
ushort tileIndex, ushort tileIndex,
long regularCost, Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo, ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block, ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo) ref Av1EncoderPaletteInfo paletteInfo)
@ -500,35 +500,35 @@ internal static partial class Av1IntraSuperblockEncoder
Av1MacroBlockModeInfo interModeInfo = modeInfo; Av1MacroBlockModeInfo interModeInfo = modeInfo;
Av1EncoderBlockStruct interBlock = block; Av1EncoderBlockStruct interBlock = block;
Av1EncoderPaletteInfo interPaletteInfo = default; Av1EncoderPaletteInfo interPaletteInfo = default;
long selectedCost = this.SelectInterBlock( Av1RateDistortionStatistics selectedStatistics = this.SelectInterBlock(
writer, writer,
macroBlock, macroBlock,
blockOrigin, blockOrigin,
tileIndex, tileIndex,
regularCost, regularStatistics,
ref interModeInfo, ref interModeInfo,
ref interBlock, ref interBlock,
ref interPaletteInfo); ref interPaletteInfo);
if (selectedCost < regularCost) if (selectedStatistics.Cost < regularStatistics.Cost)
{ {
modeInfo = interModeInfo; modeInfo = interModeInfo;
block = interBlock; block = interBlock;
paletteInfo = interPaletteInfo; paletteInfo = interPaletteInfo;
} }
return selectedCost; return selectedStatistics;
} }
/// <summary> /// <summary>
/// Evaluates the supported LAST_FRAME modes and publishes only a strict improvement over the intra result. /// Evaluates the supported LAST_FRAME modes and publishes only a strict improvement over the intra result.
/// </summary> /// </summary>
private long SelectInterBlock( private Av1RateDistortionStatistics SelectInterBlock(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock, Av1MacroBlockD macroBlock,
Point blockOrigin, Point blockOrigin,
ushort tileIndex, ushort tileIndex,
long regularCost, Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo, ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block, ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo) ref Av1EncoderPaletteInfo paletteInfo)
@ -651,7 +651,7 @@ internal static partial class Av1IntraSuperblockEncoder
transformPartitionRate = writer.GetTransformPartitionCost(false, transformPartitionContext); transformPartitionRate = writer.GetTransformPartitionCost(false, transformPartitionContext);
} }
long selectedCost = regularCost; Av1RateDistortionStatistics selectedStatistics = regularStatistics;
Av1MotionVector selectedVector = default; Av1MotionVector selectedVector = default;
Av1PredictionMode selectedMode = default; Av1PredictionMode selectedMode = default;
int selectedReferenceIndex = 0; int selectedReferenceIndex = 0;
@ -785,7 +785,7 @@ internal static partial class Av1IntraSuperblockEncoder
} }
} }
long candidateCost = this.EvaluateInterCandidate( Av1RateDistortionStatistics candidateStatistics = this.EvaluateInterCandidate(
writer, writer,
blockOrigin, blockOrigin,
tileIndex, tileIndex,
@ -812,7 +812,7 @@ internal static partial class Av1IntraSuperblockEncoder
out Av1EncoderTransformBlockState candidateRedState); out Av1EncoderTransformBlockState candidateRedState);
// Strict replacement preserves predictor-stack, global, then new-motion order on equal RD cost. // Strict replacement preserves predictor-stack, global, then new-motion order on equal RD cost.
if (candidateCost >= selectedCost) if (candidateStatistics.Cost >= selectedStatistics.Cost)
{ {
continue; continue;
} }
@ -841,7 +841,7 @@ internal static partial class Av1IntraSuperblockEncoder
selectedRedCoefficients = candidateRedCoefficients; selectedRedCoefficients = candidateRedCoefficients;
candidateRedCoefficients = previousRedCoefficients; candidateRedCoefficients = previousRedCoefficients;
selectedCost = candidateCost; selectedStatistics = candidateStatistics;
selectedVector = candidateVectors[candidateIndex]; selectedVector = candidateVectors[candidateIndex];
selectedMode = candidateModes[candidateIndex]; selectedMode = candidateModes[candidateIndex];
selectedHorizontalFilter = horizontalFilter; selectedHorizontalFilter = horizontalFilter;
@ -858,7 +858,7 @@ internal static partial class Av1IntraSuperblockEncoder
// when no inter candidate strictly improves its rate-distortion cost. // when no inter candidate strictly improves its rate-distortion cost.
if (!hasInterWinner) if (!hasInterWinner)
{ {
return regularCost; return regularStatistics;
} }
Span<int> retainedLumaCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.Y); Span<int> retainedLumaCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.Y);
@ -930,7 +930,7 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfo.Block.HorizontalInterpolationFilter = selectedHorizontalFilter; modeInfo.Block.HorizontalInterpolationFilter = selectedHorizontalFilter;
block.ReferenceMotionVectorIndex = selectedReferenceIndex; block.ReferenceMotionVectorIndex = selectedReferenceIndex;
this.picture.SetDisplacementVector(modeInfoPosition, selectedVector); this.picture.SetDisplacementVector(modeInfoPosition, selectedVector);
return selectedCost; return selectedStatistics;
} }
/// <summary> /// <summary>
@ -1034,7 +1034,7 @@ internal static partial class Av1IntraSuperblockEncoder
/// <summary> /// <summary>
/// Evaluates one inter mode through prediction, transform, coefficient, skip, and distortion selection. /// Evaluates one inter mode through prediction, transform, coefficient, skip, and distortion selection.
/// </summary> /// </summary>
private long EvaluateInterCandidate( private Av1RateDistortionStatistics EvaluateInterCandidate(
Av1SymbolEncoder writer, Av1SymbolEncoder writer,
Point blockOrigin, Point blockOrigin,
ushort tileIndex, ushort tileIndex,
@ -1238,16 +1238,16 @@ internal static partial class Av1IntraSuperblockEncoder
redRate; redRate;
long codedDistortion = lumaDistortion + blueDistortion + redDistortion; long codedDistortion = lumaDistortion + blueDistortion + redDistortion;
long selectedCost = Av1RateDistortion.GetCost(this.rateMultiplier, codedRate, codedDistortion); Av1RateDistortionStatistics selectedStatistics = new(this.rateMultiplier, codedRate, codedDistortion);
skip = false; skip = false;
if (hasEmptyLuma && hasEmptyBlue && hasEmptyRed) if (hasEmptyLuma && hasEmptyBlue && hasEmptyRed)
{ {
int skipRate = predictionRate + writer.GetSkipCost(true, skipContext); int skipRate = predictionRate + writer.GetSkipCost(true, skipContext);
long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion; long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion;
long skipCost = Av1RateDistortion.GetCost(this.rateMultiplier, skipRate, skipDistortion); Av1RateDistortionStatistics skipStatistics = new(this.rateMultiplier, skipRate, skipDistortion);
if (skipCost < selectedCost) if (skipStatistics.Cost < selectedStatistics.Cost)
{ {
selectedCost = skipCost; selectedStatistics = skipStatistics;
skip = true; skip = true;
workspace.LumaPrediction[..LumaTransformSize.GetSize2d()].CopyTo(lumaReconstruction); workspace.LumaPrediction[..LumaTransformSize.GetSize2d()].CopyTo(lumaReconstruction);
lumaCoefficients[..LumaTransformSize.GetSize2d()].Clear(); lumaCoefficients[..LumaTransformSize.GetSize2d()].Clear();
@ -1265,7 +1265,7 @@ internal static partial class Av1IntraSuperblockEncoder
} }
} }
return selectedCost; return selectedStatistics;
} }
/// <summary> /// <summary>

23
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs

@ -817,6 +817,29 @@ public class Av1EntropyTests
Assert.Equal(before, after); Assert.Equal(before, after);
} }
[Theory]
[InlineData(128, 2, 0L, 2, 0L, 1L)]
[InlineData(128, 1, 0L, 1, 0L, 1L)]
[InlineData(64, 512, 2_000_000_000L, 512, 2_000_000_000L, 512_000_000_128L)]
public void RateDistortionStatisticsRoundCombinedRate(
int multiplier,
int firstRate,
long firstDistortion,
int secondRate,
long secondDistortion,
long expectedCost)
{
// The first two cases cross opposite sides of the half-unit boundary: independently rounded
// child costs would be two and zero, while the combined reference cost is one in both cases.
Av1RateDistortionStatistics combined = new(multiplier, firstRate, firstDistortion);
Av1RateDistortionStatistics second = new(multiplier, secondRate, secondDistortion);
combined.Add(multiplier, in second);
Assert.Equal(firstRate + secondRate, combined.Rate);
Assert.Equal(firstDistortion + secondDistortion, combined.Distortion);
Assert.Equal(expectedCost, combined.Cost);
}
[Theory] [Theory]
[InlineData(1, 255, 0L, 0L)] [InlineData(1, 255, 0L, 0L)]
[InlineData(1, 256, 0L, 1L)] [InlineData(1, 256, 0L, 1L)]

165
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs

@ -888,6 +888,171 @@ public class Av1IntraSuperblockEncoderTests
Assert.InRange(reconstructedSample, (ushort)(byte.MaxValue + 1), (ushort)4095); Assert.InRange(reconstructedSample, (ushort)(byte.MaxValue + 1), (ushort)4095);
} }
[Theory]
[InlineData(true, false)]
[InlineData(false, false)]
[InlineData(true, true)]
[InlineData(false, true)]
public void BlockDecisionRetainsUnroundedRateAndDistortion(bool isMonochrome, bool textured)
{
const int Width = 8;
const int Height = 8;
const int QIndex = 37;
Av1ColorFormat colorFormat = isMonochrome ? Av1ColorFormat.Yuv400 : Av1ColorFormat.Yuv420;
ObuColorConfig colorConfig = new()
{
IsMonochrome = isMonochrome,
SubSamplingX = true,
SubSamplingY = true,
BitDepth = Av1BitDepth.EightBit
};
using Av1EncoderFrameBuffer<byte> source = new(Configuration.Default, Width, Height, 8, colorFormat, 1, 1);
using Av1EncoderFrameBuffer<byte> reconstruction = new(Configuration.Default, Width, Height, 8, colorFormat, 1, 1);
int planeCount = isMonochrome ? 1 : 3;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++)
{
Buffer2DRegion<byte> plane = source.Frame.CodedView.GetPlane((Av1Plane)planeIndex);
int length = planeIndex == 0 ? 8 : 4;
for (int y = 0; y < length; y++)
{
Span<byte> row = plane.DangerousGetRowSpan(y);
for (int x = 0; x < length; x++)
{
row[x] = textured ? (byte)(114 + (((x * 13) + (y * 7) + (planeIndex * 5)) % 29)) : (byte)128;
}
}
}
using Av1EncoderModeInfoBuffer modeInfoBuffer = new(Configuration.Default, Width, Height, disallow4x4AllFrames: true);
Av1PictureControlSet template = CreatePicture(modeInfoBuffer, colorConfig, use128x128Superblock: false, QIndex);
template.Parent.FrameHeader.TransformMode = Av1TransformMode.Largest;
using Av1EncoderPictureBuffer pictureBuffer = new(
Configuration.Default,
template.Sequence.SequenceHeader,
template.Parent.FrameHeader,
Width,
Height,
disallow4x4AllFrames: true);
Av1PictureControlSet picture = pictureBuffer.Picture;
using Av1EncoderCoefficientBuffer coefficients = new(Configuration.Default, template.Sequence.SequenceHeader, Width, Height);
using Av1EncoderSuperblockWorkspace superblockWorkspace = new(Configuration.Default);
using Av1EncoderBlockWorkspace blockWorkspace = new(Configuration.Default);
Av1Superblock superblock = new()
{
Workspace = superblockWorkspace,
TileInfo = new Av1TileInfo(0, 0, picture.Parent.FrameHeader),
Index = 0
};
Av1IntraSuperblockEncoder.Prepare(picture, superblock, Point.Empty);
Av1MacroBlockD macroBlock = new() { Tile = superblock.TileInfo };
Av1TileWriter.SetModeInfoRowAndColumn(
picture,
macroBlock,
superblock.TileInfo,
Point.Empty,
Av1BlockSize.Block8x8,
picture.Parent.Common.ModeInfoStride,
picture.Parent.Common.ModeInfoRowCount,
picture.Parent.Common.ModeInfoColumnCount);
Av1IntraSuperblockEncoder.ModeDecision<byte, Av1IntraSuperblockEncoder.ByteOperator> decision = new(
source.Frame,
reconstruction.Frame,
reconstruction.Frame,
picture,
superblock,
coefficients,
blockWorkspace,
effort: 0);
ref Av1MacroBlockModeInfo modeInfo = ref picture.GetMacroBlockModeInfo(Point.Empty);
Av1EncoderBlockStruct block = default;
Av1EncoderPaletteInfo palette = default;
using Av1SymbolEncoder writer = new(Configuration.Default, 256, QIndex, updateCdf: true);
decision.EncodeBlock(writer, macroBlock, Point.Empty, 0, ref modeInfo, ref block, ref palette);
Assert.Equal(Av1PredictionMode.DC, modeInfo.Block.Mode);
Assert.Equal(Av1TransformSize.Size8x8, modeInfo.Block.TransformSize);
Assert.False(modeInfo.Block.Skip);
int expectedRate = writer.GetSkipCost(false, Av1TileWriter.GetSkipContext(macroBlock));
expectedRate += Av1TileWriter.GetLumaModeCost(
writer,
macroBlock,
Av1BlockSize.Block8x8,
Av1PredictionMode.DC,
0,
isIntraFrame: true);
if (!isMonochrome)
{
Assert.Equal(Av1ChromaPredictionMode.DC, modeInfo.Block.UvMode);
expectedRate += Av1TileWriter.GetChromaModeCost(
writer,
picture.Parent.FrameHeader,
colorConfig,
modeInfo,
Av1BlockSize.Block8x8,
Av1PredictionMode.DC,
Av1ChromaPredictionMode.DC,
0);
}
long squaredError = 0;
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++)
{
Av1Plane plane = (Av1Plane)planeIndex;
Av1TransformSize transformSize = planeIndex == 0 ? Av1TransformSize.Size8x8 : Av1TransformSize.Size4x4;
Av1BlockSize blockSize = planeIndex == 0 ? Av1BlockSize.Block8x8 : Av1BlockSize.Block4x4;
Av1ComponentType component = planeIndex == 0 ? Av1ComponentType.Luminance : Av1ComponentType.Chroma;
Av1NeighborArrayUnit<byte> neighbors = planeIndex switch
{
0 => picture.LuminanceDcSignLevelCoefficientNeighbors[0],
1 => picture.CbDcSignLevelCoefficientNeighbors[0],
_ => picture.CrDcSignLevelCoefficientNeighbors[0]
};
Av1EncoderTransformBlockState state = coefficients.GetTransformBlockSpan(0, plane)[0];
expectedRate += writer.GetCoefficientCost(
transformSize,
state.TransformType,
Av1PredictionMode.DC,
coefficients.GetPlaneSpan(0, plane)[..transformSize.GetSize2d()],
component,
Av1TileWriter.GetTransformBlockContexts(component, neighbors, Point.Empty, blockSize, transformSize),
state.EndOfBlock,
picture.Parent.FrameHeader.UseReducedTransformSet,
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
Buffer2DRegion<byte> sourcePlane = source.Frame.CodedView.GetPlane(plane);
Buffer2DRegion<byte> reconstructedPlane = reconstruction.Frame.CodedView.GetPlane(plane);
int length = planeIndex == 0 ? 8 : 4;
for (int y = 0; y < length; y++)
{
ReadOnlySpan<byte> sourceRow = sourcePlane.DangerousGetRowSpan(y);
ReadOnlySpan<byte> reconstructedRow = reconstructedPlane.DangerousGetRowSpan(y);
for (int x = 0; x < length; x++)
{
int difference = sourceRow[x] - reconstructedRow[x];
squaredError += difference * difference;
}
}
}
// Pixel-domain SSE uses the reference's four fractional distortion bits. The probability rate
// is rounded after all planes and block syntax have been counted, before adding scaled distortion.
long expectedDistortion = squaredError * 16;
int multiplier = Av1RateDistortion.GetKeyFrameRateMultiplier(QIndex, Av1BitDepth.EightBit);
long expectedCost = ((((long)expectedRate * multiplier) + 256) / 512) + (expectedDistortion * 128);
Assert.Equal(textured, squaredError > 0);
Assert.Equal(expectedRate, decision.SelectedBlockStatistics.Rate);
Assert.Equal(expectedDistortion, decision.SelectedBlockStatistics.Distortion);
Assert.Equal(expectedCost, decision.SelectedBlockStatistics.Cost);
}
[Fact] [Fact]
public void BlockDecisionObservesLiveCdfInWriterOrder() public void BlockDecisionObservesLiveCdfInWriterOrder()
{ {

Loading…
Cancel
Save