diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index 3084fdbf43..894951df93 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/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.
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`:
- `Av1SymbolWriter.cs:213-214,339` before correction sliced a fixed initial allocation for finalization
diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1RateDistortionStatistics.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1RateDistortionStatistics.cs
new file mode 100644
index 0000000000..0ec646f05d
--- /dev/null
+++ b/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;
+
+///
+/// Holds the rate, distortion, and rounded cost of an encoder candidate.
+///
+internal struct Av1RateDistortionStatistics
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The rate multiplier for the current block.
+ /// The estimated syntax rate in 1/512-bit units.
+ /// The candidate distortion.
+ public Av1RateDistortionStatistics(int rateMultiplier, int rate, long distortion)
+ {
+ this.Rate = rate;
+ this.Distortion = distortion;
+ this.Cost = Av1RateDistortion.GetCost(rateMultiplier, rate, distortion);
+ }
+
+ ///
+ /// Gets the sentinel for a candidate that cannot win a cost comparison.
+ ///
+ public static Av1RateDistortionStatistics Invalid => new()
+ {
+ Rate = int.MaxValue,
+ Distortion = long.MaxValue,
+ Cost = long.MaxValue
+ };
+
+ ///
+ /// Gets the estimated syntax rate in 1/512-bit units.
+ ///
+ public int Rate { get; private set; }
+
+ ///
+ /// Gets the candidate distortion.
+ ///
+ public long Distortion { get; private set; }
+
+ ///
+ /// Gets the rounded rate-distortion cost.
+ ///
+ public long Cost { get; private set; }
+
+ ///
+ /// Adds a valid candidate's rate and distortion and updates the combined cost.
+ ///
+ /// The rate multiplier for the combined candidate.
+ /// The valid candidate to add.
+ 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);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs
index f60f263d8a..ae7283eced 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs
@@ -58,7 +58,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta,
out byte selectedChromaFromLumaIndex,
out sbyte selectedChromaFromLumaSigns,
- out long selectedCost)
+ out Av1RateDistortionStatistics selectedStatistics)
{
Av1EncoderModeDecisionWorkspace workspace =
this.blockWorkspace.GetModeDecisionWorkspace();
@@ -94,7 +94,7 @@ internal static partial class Av1IntraSuperblockEncoder
out selectedAngleDelta,
out selectedChromaFromLumaIndex,
out selectedChromaFromLumaSigns,
- out selectedCost);
+ out selectedStatistics);
}
int modeInfoRow = lumaOrigin.Y >> Av1Constants.ModeInfoSizeLog2;
@@ -201,7 +201,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span candidateRedReconstruction = workspace.GetCandidateReconstruction(1);
Span candidateBlueCoefficients = workspace.GetCandidateCoefficients(0);
Span candidateRedCoefficients = workspace.GetCandidateCoefficients(1);
- long bestCost = long.MaxValue;
+ Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1ChromaPredictionMode bestMode = Av1ChromaPredictionMode.DC;
selectedAngleDelta = 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
// retained result independently. Their complete rate and distortion compete jointly.
- long candidateCost = this.GetChromaCandidateCost(
+ Av1RateDistortionStatistics candidateStatistics = this.GetChromaCandidateCost(
writer,
modeInfo,
lumaMode,
@@ -277,7 +277,7 @@ internal static partial class Av1IntraSuperblockEncoder
ref candidateBlueState,
ref candidateRedState);
- if (candidateCost < bestCost)
+ if (candidateStatistics.Cost < bestStatistics.Cost)
{
CopyCandidate(
candidateBlueReconstruction,
@@ -299,7 +299,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateRedState,
ref retainedRedStates[0]);
- bestCost = candidateCost;
+ bestStatistics = candidateStatistics;
bestMode = chromaMode;
selectedAngleDelta = angleDelta;
}
@@ -430,15 +430,15 @@ internal static partial class Av1IntraSuperblockEncoder
+ writer.GetChromaFromLumaCost(packedIndex, jointSign);
long distortion = blueDistortions[blueCandidateIndex] + redDistortions[redCandidateIndex];
- long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
- bool winsSearchOrderTie = candidateCost == bestCost
+ Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
+ bool winsSearchOrderTie = candidateStatistics.Cost == bestStatistics.Cost
&& !chromaFromLumaSelected
&& bestMode != Av1ChromaPredictionMode.DC;
// 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;
selectedAngleDelta = 0;
selectedBlueCandidateIndex = blueCandidateIndex;
@@ -532,7 +532,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedRedCoefficients,
ref retainedBlueStates[0],
ref retainedRedStates[0],
- ref bestCost,
+ ref bestStatistics,
ref paletteInfo))
{
bestMode = Av1ChromaPredictionMode.DC;
@@ -541,7 +541,7 @@ internal static partial class Av1IntraSuperblockEncoder
selectedChromaFromLumaSigns = 0;
}
- selectedCost = bestCost;
+ selectedStatistics = bestStatistics;
return bestMode;
}
@@ -564,7 +564,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta,
out byte selectedChromaFromLumaIndex,
out sbyte selectedChromaFromLumaSigns,
- out long selectedCost)
+ out Av1RateDistortionStatistics selectedStatistics)
{
Av1EncoderModeDecisionWorkspace workspace =
this.blockWorkspace.GetModeDecisionWorkspace();
@@ -650,7 +650,7 @@ internal static partial class Av1IntraSuperblockEncoder
_ => baseModeCount
};
- long bestCost = long.MaxValue;
+ Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1ChromaPredictionMode bestMode = Av1ChromaPredictionMode.DC;
selectedAngleDelta = 0;
selectedChromaFromLumaIndex = 0;
@@ -745,8 +745,8 @@ internal static partial class Av1IntraSuperblockEncoder
rate += paletteDisabledCost;
}
- long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
- if (candidateCost < bestCost)
+ Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
+ if (candidateStatistics.Cost < bestStatistics.Cost)
{
CopyTiledCandidate(
candidateBlueReconstruction,
@@ -772,13 +772,13 @@ internal static partial class Av1IntraSuperblockEncoder
retainedRedCoefficients,
retainedRedStates);
- bestCost = candidateCost;
+ bestStatistics = candidateStatistics;
bestMode = chromaMode;
selectedAngleDelta = angleDelta;
}
}
- selectedCost = bestCost;
+ selectedStatistics = bestStatistics;
return bestMode;
}
@@ -1083,7 +1083,7 @@ internal static partial class Av1IntraSuperblockEncoder
&& modeInfo.UvMode is Av1ChromaPredictionMode.Smooth or Av1ChromaPredictionMode.SmoothVertical or Av1ChromaPredictionMode.SmoothHorizontal;
}
- private long GetChromaCandidateCost(
+ private Av1RateDistortionStatistics GetChromaCandidateCost(
Av1SymbolEncoder writer,
Av1MacroBlockModeInfo modeInfo,
Av1PredictionMode lumaMode,
@@ -1209,7 +1209,7 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
- return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
+ return new(this.rateMultiplier, rate, distortion);
}
///
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaPaletteModeDecision.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaPaletteModeDecision.cs
index e50dcef246..8ffdbe2cf2 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaPaletteModeDecision.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaPaletteModeDecision.cs
@@ -38,7 +38,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span retainedRedCoefficients,
ref Av1EncoderTransformBlockState retainedBlueState,
ref Av1EncoderTransformBlockState retainedRedState,
- ref long bestCost,
+ ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo)
{
const Av1BlockSize BlockSize = Av1BlockSize.Block8x8;
@@ -336,8 +336,8 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
- long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
- if (candidateCost < bestCost)
+ Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
+ if (candidateStatistics.Cost < bestStatistics.Cost)
{
// Every following palette size overwrites the shared maps and candidate spans, so a
// 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.SetColors(Av1Plane.U, bluePaletteColors);
paletteInfo.SetColors(Av1Plane.V, redPaletteColors);
- bestCost = candidateCost;
+ bestStatistics = candidateStatistics;
paletteSelected = true;
}
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs
index 8f76f64e25..05e148eb17 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs
@@ -139,7 +139,6 @@ internal static partial class Av1IntraSuperblockEncoder
private readonly int effort;
private int codedAreaLuma;
private int codedAreaChroma;
- private long selectedBlockCost;
///
/// Initializes a new instance of the struct.
@@ -178,9 +177,14 @@ internal static partial class Av1IntraSuperblockEncoder
this.effort = effort;
this.codedAreaLuma = 0;
this.codedAreaChroma = 0;
- this.selectedBlockCost = 0;
+ this.SelectedBlockStatistics = default;
}
+ ///
+ /// Gets the statistics of the most recently encoded block.
+ ///
+ public Av1RateDistortionStatistics SelectedBlockStatistics { get; private set; }
+
///
public Av1PartitionType SelectPartition(
Av1SymbolEncoder writer,
@@ -254,7 +258,7 @@ internal static partial class Av1IntraSuperblockEncoder
int savedLumaArea = this.codedAreaLuma;
int savedChromaArea = this.codedAreaChroma;
this.SavePartitionTrialContexts(blockOrigin, tileIndex, blockSize);
- long bestCost = long.MaxValue;
+ Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PartitionType selectedPartition = Av1PartitionType.None;
ReadOnlySpan searchOrder = PartitionSearchOrder;
int candidateCount = blockSize == Av1BlockSize.Block8x8 ? 4 : searchOrder.Length;
@@ -266,7 +270,7 @@ internal static partial class Av1IntraSuperblockEncoder
continue;
}
- long candidateCost = this.EvaluatePartitionCandidate(
+ Av1RateDistortionStatistics candidateStatistics = this.EvaluatePartitionCandidate(
writer,
macroBlock,
blockOrigin,
@@ -275,9 +279,9 @@ internal static partial class Av1IntraSuperblockEncoder
partitionType,
publishFinalContexts: false);
- if (candidateCost < bestCost)
+ if (candidateStatistics.Cost < bestStatistics.Cost)
{
- bestCost = candidateCost;
+ bestStatistics = candidateStatistics;
selectedPartition = partitionType;
}
@@ -292,7 +296,7 @@ internal static partial class Av1IntraSuperblockEncoder
return selectedPartition;
}
- private long EvaluateSelectedPartitionTree(
+ private Av1RateDistortionStatistics EvaluateSelectedPartitionTree(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
@@ -307,7 +311,7 @@ internal static partial class Av1IntraSuperblockEncoder
tileIndex,
blockSize);
- long cost = this.EvaluatePartitionCandidate(
+ Av1RateDistortionStatistics statistics = this.EvaluatePartitionCandidate(
writer,
macroBlock,
blockOrigin,
@@ -326,10 +330,10 @@ internal static partial class Av1IntraSuperblockEncoder
selectedPartition);
}
- return cost;
+ return statistics;
}
- private long EvaluatePartitionCandidate(
+ private Av1RateDistortionStatistics EvaluatePartitionCandidate(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
@@ -346,7 +350,7 @@ internal static partial class Av1IntraSuperblockEncoder
blockOrigin,
this.picture.PartitionContexts[tileIndex]);
- long cost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, 0);
+ Av1RateDistortionStatistics statistics = new(this.rateMultiplier, rate, 0);
int leafCount = GetPartitionLeafCount(partitionType);
// 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);
bool publishContexts = leafIndex < leafCount - 1 || publishFinalContexts;
- cost += partitionType == Av1PartitionType.Split && blockSize > Av1BlockSize.Block8x8
+ Av1RateDistortionStatistics childStatistics = partitionType == Av1PartitionType.Split && blockSize > Av1BlockSize.Block8x8
? this.EvaluateSelectedPartitionTree(
writer,
macroBlock,
@@ -378,9 +382,11 @@ internal static partial class Av1IntraSuperblockEncoder
leafSize,
partitionType == Av1PartitionType.Split ? Av1PartitionType.None : partitionType,
publishContexts);
+
+ statistics.Add(this.rateMultiplier, in childStatistics);
}
- return cost;
+ return statistics;
}
private void ResetPartitionTrial(
@@ -613,7 +619,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int lumaAngleDelta,
out Av1FilterIntraMode filterIntraMode,
out Av1TransformSize lumaTransformSize,
- out long lumaCost);
+ out Av1RateDistortionStatistics lumaStatistics);
block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] = (sbyte)lumaAngleDelta;
block.FilterIntraMode = filterIntraMode;
@@ -626,37 +632,37 @@ internal static partial class Av1IntraSuperblockEncoder
bool allowIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 &&
this.picture.Parent.FrameHeader.AllowIntraBlockCopy;
- long regularCost = this.GetRegularBlockCost(
+ Av1RateDistortionStatistics regularStatistics = this.GetRegularBlockCost(
writer,
macroBlock,
- lumaCost,
+ lumaStatistics,
allowIntraBlockCopy);
if (!this.picture.Parent.FrameHeader.IsIntra)
{
- this.selectedBlockCost = this.SelectInterPrediction(
+ this.SelectedBlockStatistics = this.SelectInterPrediction(
writer,
macroBlock,
blockOrigin,
tileIndex,
- regularCost,
+ regularStatistics,
ref modeInfo,
ref block,
ref paletteInfo);
}
else
{
- this.selectedBlockCost = allowIntraBlockCopy
+ this.SelectedBlockStatistics = allowIntraBlockCopy
? this.SelectIntraBlockCopy(
writer,
macroBlock,
blockOrigin,
tileIndex,
- regularCost,
+ regularStatistics,
ref modeInfo,
ref block,
ref paletteInfo)
- : regularCost;
+ : regularStatistics;
}
this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight();
@@ -693,7 +699,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span retainedBlueStates = blueTransformBlocks[chromaTransformIndex..];
Span retainedRedStates = redTransformBlocks[chromaTransformIndex..];
- long chromaCost = 0;
+ Av1RateDistortionStatistics chromaStatistics = default;
if (block.HasChroma)
{
modeInfo.Block.UvMode = this.SelectChromaMode(
@@ -714,7 +720,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int chromaAngleDelta,
out byte chromaFromLumaIndex,
out sbyte chromaFromLumaSigns,
- out chromaCost);
+ out chromaStatistics);
block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = (sbyte)chromaAngleDelta;
block.PredictionUnit.ChromaFromLumaIndex = chromaFromLumaIndex;
@@ -724,37 +730,38 @@ internal static partial class Av1IntraSuperblockEncoder
bool allowColorIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 &&
this.picture.Parent.FrameHeader.AllowIntraBlockCopy;
- long regularColorCost = this.GetRegularBlockCost(
+ lumaStatistics.Add(this.rateMultiplier, in chromaStatistics);
+ Av1RateDistortionStatistics regularColorStatistics = this.GetRegularBlockCost(
writer,
macroBlock,
- lumaCost + chromaCost,
+ lumaStatistics,
allowColorIntraBlockCopy);
if (!this.picture.Parent.FrameHeader.IsIntra)
{
- this.selectedBlockCost = this.SelectInterPrediction(
+ this.SelectedBlockStatistics = this.SelectInterPrediction(
writer,
macroBlock,
blockOrigin,
tileIndex,
- regularColorCost,
+ regularColorStatistics,
ref modeInfo,
ref block,
ref paletteInfo);
}
else
{
- this.selectedBlockCost = allowColorIntraBlockCopy
+ this.SelectedBlockStatistics = allowColorIntraBlockCopy
? this.SelectIntraBlockCopy(
writer,
macroBlock,
blockOrigin,
tileIndex,
- regularColorCost,
+ regularColorStatistics,
ref modeInfo,
ref block,
ref paletteInfo)
- : regularColorCost;
+ : regularColorStatistics;
}
this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight();
@@ -764,7 +771,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
- private long EvaluatePartitionLeaf(
+ private Av1RateDistortionStatistics EvaluatePartitionLeaf(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
@@ -812,7 +819,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo);
}
- return this.selectedBlockCost;
+ return this.SelectedBlockStatistics;
}
private void SetBlockGeometry(
@@ -1190,10 +1197,10 @@ internal static partial class Av1IntraSuperblockEncoder
offset += left.Length;
}
- private long GetRegularBlockCost(
+ private Av1RateDistortionStatistics GetRegularBlockCost(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
- long modeCost,
+ Av1RateDistortionStatistics modeStatistics,
bool allowIntraBlockCopy)
{
int rateAdjustment = writer.GetSkipCost(false, Av1TileWriter.GetSkipContext(macroBlock));
@@ -1208,7 +1215,7 @@ internal static partial class Av1IntraSuperblockEncoder
rateAdjustment += writer.GetUseIntraBlockCopyCost(false);
}
- return modeCost + Av1RateDistortion.GetCost(this.rateMultiplier, rateAdjustment, 0);
+ return new(this.rateMultiplier, modeStatistics.Rate + rateAdjustment, modeStatistics.Distortion);
}
private Av1PredictionMode SelectLumaMode(
@@ -1223,7 +1230,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta,
out Av1FilterIntraMode selectedFilterIntraMode,
out Av1TransformSize selectedTransformSize,
- out long selectedCost)
+ out Av1RateDistortionStatistics selectedStatistics)
{
bool codedLossless = this.picture.Parent.FrameHeader.CodedLossless;
Av1TransformSize transformSize = codedLossless
@@ -1251,7 +1258,7 @@ internal static partial class Av1IntraSuperblockEncoder
out selectedAngleDelta,
out selectedFilterIntraMode,
out selectedTransformSize,
- out selectedCost);
+ out selectedStatistics);
}
bool hasLeft = macroBlock.IsLeftAvailable;
@@ -1347,7 +1354,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span candidateCoefficients = workspace.GetCandidateCoefficients(0);
Span prediction = workspace.Prediction;
Span residual = workspace.Residual;
- long bestCost = long.MaxValue;
+ Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PredictionMode bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0;
selectedFilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes;
@@ -1439,7 +1446,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
Av1EncoderTransformBlockState candidateState = default;
- long candidateCost = this.GetLumaCandidateCost(
+ Av1RateDistortionStatistics candidateStatistics = this.GetLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@@ -1458,7 +1465,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients,
ref candidateState);
- if (candidateCost < bestCost)
+ if (candidateStatistics.Cost < bestStatistics.Cost)
{
// 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.
@@ -1472,7 +1479,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState,
ref retainedStates[0]);
- bestCost = candidateCost;
+ bestStatistics = candidateStatistics;
bestMode = mode;
selectedAngleDelta = angleDelta;
selectedTransformSize = transformSize;
@@ -1484,7 +1491,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (searchEveryTransformSize &&
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select)
{
- long splitCost = this.GetSplitLumaCandidateCost(
+ Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@@ -1499,12 +1506,12 @@ internal static partial class Av1IntraSuperblockEncoder
0,
paletteDisabledCost,
transformSizeContext,
- bestCost,
+ bestStatistics.Cost,
candidateReconstruction,
candidateCoefficients,
workspace.CandidateTransformBlocks);
- if (splitCost < bestCost)
+ if (splitStatistics.Cost < bestStatistics.Cost)
{
CopySplitCandidate(
candidateReconstruction,
@@ -1515,7 +1522,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients,
retainedStates);
- bestCost = splitCost;
+ bestStatistics = splitStatistics;
bestMode = mode;
selectedAngleDelta = angleDelta;
selectedTransformSize = Av1TransformSize.Size4x4;
@@ -1525,7 +1532,7 @@ internal static partial class Av1IntraSuperblockEncoder
// 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.
- long bestTransformCost = bestCost;
+ Av1RateDistortionStatistics bestTransformStatistics = bestStatistics;
if (!codedLossless && this.effort >= 3 && !searchEveryTransformType)
{
// 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;
- long candidateCost = this.GetLumaCandidateCost(
+ Av1RateDistortionStatistics candidateStatistics = this.GetLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@@ -1576,7 +1583,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients,
ref candidateState);
- if (candidateCost < bestTransformCost)
+ if (candidateStatistics.Cost < bestTransformStatistics.Cost)
{
CopyCandidate(
candidateReconstruction,
@@ -1588,7 +1595,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState,
ref retainedStates[0]);
- bestTransformCost = candidateCost;
+ bestTransformStatistics = candidateStatistics;
}
}
}
@@ -1630,7 +1637,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
Av1EncoderTransformBlockState candidateState = default;
- long candidateCost = this.GetFilterIntraCandidateCost(
+ Av1RateDistortionStatistics candidateStatistics = this.GetFilterIntraCandidateCost(
writer,
macroBlock,
sourcePlane,
@@ -1648,7 +1655,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients,
ref candidateState);
- if (candidateCost < bestTransformCost)
+ if (candidateStatistics.Cost < bestTransformStatistics.Cost)
{
CopyCandidate(
candidateReconstruction,
@@ -1660,7 +1667,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateState,
ref retainedStates[0]);
- bestTransformCost = candidateCost;
+ bestTransformStatistics = candidateStatistics;
bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0;
selectedFilterIntraMode = filterIntraMode;
@@ -1673,7 +1680,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (searchEveryTransformSize &&
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select)
{
- long splitCost = this.GetSplitLumaCandidateCost(
+ Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@@ -1688,12 +1695,12 @@ internal static partial class Av1IntraSuperblockEncoder
0,
paletteDisabledCost,
transformSizeContext,
- bestTransformCost,
+ bestTransformStatistics.Cost,
candidateReconstruction,
candidateCoefficients,
workspace.CandidateTransformBlocks);
- if (splitCost < bestTransformCost)
+ if (splitStatistics.Cost < bestTransformStatistics.Cost)
{
CopySplitCandidate(
candidateReconstruction,
@@ -1704,7 +1711,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients,
retainedStates);
- bestTransformCost = splitCost;
+ bestTransformStatistics = splitStatistics;
bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0;
selectedFilterIntraMode = filterIntraMode;
@@ -1732,7 +1739,7 @@ internal static partial class Av1IntraSuperblockEncoder
candidateCoefficients,
retainedCoefficients,
retainedStates,
- ref bestTransformCost,
+ ref bestTransformStatistics,
ref paletteInfo,
ref selectedTransformSize))
{
@@ -1749,7 +1756,7 @@ internal static partial class Av1IntraSuperblockEncoder
this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select &&
paletteInfo.PaletteSizes[0] == 0)
{
- long splitCost = this.GetSplitLumaCandidateCost(
+ Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer,
macroBlock,
sourcePlane,
@@ -1764,12 +1771,12 @@ internal static partial class Av1IntraSuperblockEncoder
0,
paletteDisabledCost,
transformSizeContext,
- bestTransformCost,
+ bestTransformStatistics.Cost,
candidateReconstruction,
candidateCoefficients,
workspace.CandidateTransformBlocks);
- if (splitCost < bestTransformCost)
+ if (splitStatistics.Cost < bestTransformStatistics.Cost)
{
CopySplitCandidate(
candidateReconstruction,
@@ -1780,12 +1787,12 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients,
retainedStates);
- bestTransformCost = splitCost;
+ bestTransformStatistics = splitStatistics;
selectedTransformSize = Av1TransformSize.Size4x4;
}
}
- selectedCost = bestTransformCost;
+ selectedStatistics = bestTransformStatistics;
return bestMode;
}
@@ -1801,7 +1808,7 @@ internal static partial class Av1IntraSuperblockEncoder
out int selectedAngleDelta,
out Av1FilterIntraMode selectedFilterIntraMode,
out Av1TransformSize selectedTransformSize,
- out long selectedCost)
+ out Av1RateDistortionStatistics selectedStatistics)
{
Av1EncoderModeDecisionWorkspace workspace =
this.blockWorkspace.GetModeDecisionWorkspace();
@@ -1868,7 +1875,7 @@ internal static partial class Av1IntraSuperblockEncoder
Buffer2DRegion sourcePlane = this.source.GetPlane(Av1Plane.Y);
Buffer2DRegion reconstructionPlane = this.reconstruction.GetPlane(Av1Plane.Y);
- long bestCost = long.MaxValue;
+ Av1RateDistortionStatistics bestStatistics = Av1RateDistortionStatistics.Invalid;
Av1PredictionMode bestMode = Av1PredictionMode.DC;
selectedAngleDelta = 0;
selectedFilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes;
@@ -1942,8 +1949,8 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
- long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
- if (candidateCost < bestCost)
+ Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, rate, distortion);
+ if (candidateStatistics.Cost < bestStatistics.Cost)
{
CopyTiledCandidate(
candidateReconstruction,
@@ -1957,17 +1964,17 @@ internal static partial class Av1IntraSuperblockEncoder
retainedCoefficients,
retainedStates);
- bestCost = candidateCost;
+ bestStatistics = candidateStatistics;
bestMode = mode;
selectedAngleDelta = angleDelta;
}
}
- selectedCost = bestCost;
+ selectedStatistics = bestStatistics;
return bestMode;
}
- private long GetSplitLumaCandidateCost(
+ private Av1RateDistortionStatistics GetSplitLumaCandidateCost(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Buffer2DRegion sourcePlane,
@@ -2267,12 +2274,12 @@ internal static partial class Av1IntraSuperblockEncoder
// Every remaining transform can only add nonnegative rate and distortion.
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(
@@ -2471,7 +2478,7 @@ internal static partial class Av1IntraSuperblockEncoder
leftStorage[0] = corner;
}
- private long GetLumaCandidateCost(
+ private Av1RateDistortionStatistics GetLumaCandidateCost(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Buffer2DRegion sourcePlane,
@@ -2546,10 +2553,10 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
- return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
+ return new(this.rateMultiplier, rate, distortion);
}
- private long GetFilterIntraCandidateCost(
+ private Av1RateDistortionStatistics GetFilterIntraCandidateCost(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Buffer2DRegion sourcePlane,
@@ -2608,7 +2615,7 @@ internal static partial class Av1IntraSuperblockEncoder
filterIntraMode,
usesInterTransformSet: false);
- return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
+ return new(this.rateMultiplier, rate, distortion);
}
private static void CopyCandidate(
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.PaletteModeDecision.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.PaletteModeDecision.cs
index b0ccb8ee9c..756042f0b3 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.PaletteModeDecision.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.PaletteModeDecision.cs
@@ -34,7 +34,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span candidateCoefficients,
Span retainedCoefficients,
Span retainedStates,
- ref long bestCost,
+ ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo,
ref Av1TransformSize selectedTransformSize)
{
@@ -164,7 +164,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates,
retainedColorIndexMap,
reconstructionPlane,
- ref bestCost,
+ ref bestStatistics,
ref paletteInfo,
ref selectedTransformSize,
ref paletteSelected);
@@ -197,7 +197,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates,
retainedColorIndexMap,
reconstructionPlane,
- ref bestCost,
+ ref bestStatistics,
ref paletteInfo,
ref selectedTransformSize,
ref paletteSelected);
@@ -239,7 +239,7 @@ internal static partial class Av1IntraSuperblockEncoder
retainedStates,
retainedColorIndexMap,
reconstructionPlane,
- ref bestCost,
+ ref bestStatistics,
ref paletteInfo,
ref selectedTransformSize,
ref paletteSelected);
@@ -281,7 +281,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span retainedStates,
Span retainedColorIndexMap,
Buffer2DRegion reconstructionPlane,
- ref long bestCost,
+ ref Av1RateDistortionStatistics bestStatistics,
ref Av1EncoderPaletteInfo paletteInfo,
ref Av1TransformSize selectedTransformSize,
ref bool paletteSelected)
@@ -429,8 +429,8 @@ internal static partial class Av1IntraSuperblockEncoder
Av1FilterIntraMode.AllFilterIntraModes,
usesInterTransformSet: false);
- long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, candidateRate, distortion);
- if (candidateCost < bestCost)
+ Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, candidateRate, distortion);
+ if (candidateStatistics.Cost < bestStatistics.Cost)
{
// 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.
@@ -453,7 +453,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo.PaletteSizes[0] = (byte)paletteSize;
paletteInfo.SetColors(Av1Plane.Y, paletteColors);
selectedTransformSize = TransformSize;
- bestCost = candidateCost;
+ bestStatistics = candidateStatistics;
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
// candidates whose 4x4 residual partition wins even when their 8x8 result does not.
- long splitCost = this.GetSplitLumaCandidateCost(
+ Av1RateDistortionStatistics splitStatistics = this.GetSplitLumaCandidateCost(
writer,
macroBlock,
this.source.GetPlane(Av1Plane.Y),
@@ -478,12 +478,12 @@ internal static partial class Av1IntraSuperblockEncoder
rate,
0,
transformSizeContext,
- bestCost,
+ bestStatistics.Cost,
candidateReconstruction,
candidateCoefficients,
modeDecisionWorkspace.CandidateTransformBlocks);
- if (splitCost < bestCost)
+ if (splitStatistics.Cost < bestStatistics.Cost)
{
CopySplitCandidate(
candidateReconstruction,
@@ -503,7 +503,7 @@ internal static partial class Av1IntraSuperblockEncoder
paletteInfo.PaletteSizes[0] = (byte)paletteSize;
paletteInfo.SetColors(Av1Plane.Y, paletteColors);
selectedTransformSize = Av1TransformSize.Size4x4;
- bestCost = splitCost;
+ bestStatistics = splitStatistics;
paletteSelected = true;
}
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
index 45824e474d..229b809730 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
@@ -57,12 +57,12 @@ internal static partial class Av1IntraSuperblockEncoder
where TSample : unmanaged
where TOperator : struct, IBlockEncodingOperator
{
- private long SelectIntraBlockCopy(
+ private Av1RateDistortionStatistics SelectIntraBlockCopy(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
ushort tileIndex,
- long regularCost,
+ Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo)
@@ -135,11 +135,11 @@ internal static partial class Av1IntraSuperblockEncoder
if (uniqueCandidateCount == 0)
{
- return regularCost;
+ return regularStatistics;
}
int skipContext = Av1TileWriter.GetSkipContext(macroBlock);
- long bestCost = regularCost;
+ Av1RateDistortionStatistics bestStatistics = regularStatistics;
bool hasSelectedCandidate = false;
bool selectedSkip = false;
Av1MotionVector selectedVector = default;
@@ -336,7 +336,7 @@ internal static partial class Av1IntraSuperblockEncoder
redRate;
long candidateDistortion = lumaDistortion + blueDistortion + redDistortion;
- long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, candidateRate, candidateDistortion);
+ Av1RateDistortionStatistics candidateStatistics = new(this.rateMultiplier, candidateRate, candidateDistortion);
bool candidateSkip = false;
// 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);
long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion;
- long skipCost = Av1RateDistortion.GetCost(this.rateMultiplier, skipRate, skipDistortion);
- if (skipCost < candidateCost)
+ Av1RateDistortionStatistics skipStatistics = new(this.rateMultiplier, skipRate, skipDistortion);
+ if (skipStatistics.Cost < candidateStatistics.Cost)
{
- candidateCost = skipCost;
+ candidateStatistics = skipStatistics;
candidateSkip = true;
}
}
// Conventional intra and earlier IBC vectors retain strict search-order precedence on equal RD.
- if (candidateCost >= bestCost)
+ if (candidateStatistics.Cost >= bestStatistics.Cost)
{
continue;
}
- bestCost = candidateCost;
+ bestStatistics = candidateStatistics;
hasSelectedCandidate = true;
selectedSkip = candidateSkip;
selectedVector = candidate;
@@ -410,7 +410,7 @@ internal static partial class Av1IntraSuperblockEncoder
if (!hasSelectedCandidate)
{
- return bestCost;
+ return bestStatistics;
}
// 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;
paletteInfo = default;
this.picture.SetDisplacementVector(modeInfoPosition, selectedVector);
- return bestCost;
+ return bestStatistics;
}
///
/// Compares the retained intra result with an inter candidate without disturbing the intra result on loss.
///
- private long SelectInterPrediction(
+ private Av1RateDistortionStatistics SelectInterPrediction(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
ushort tileIndex,
- long regularCost,
+ Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo)
@@ -500,35 +500,35 @@ internal static partial class Av1IntraSuperblockEncoder
Av1MacroBlockModeInfo interModeInfo = modeInfo;
Av1EncoderBlockStruct interBlock = block;
Av1EncoderPaletteInfo interPaletteInfo = default;
- long selectedCost = this.SelectInterBlock(
+ Av1RateDistortionStatistics selectedStatistics = this.SelectInterBlock(
writer,
macroBlock,
blockOrigin,
tileIndex,
- regularCost,
+ regularStatistics,
ref interModeInfo,
ref interBlock,
ref interPaletteInfo);
- if (selectedCost < regularCost)
+ if (selectedStatistics.Cost < regularStatistics.Cost)
{
modeInfo = interModeInfo;
block = interBlock;
paletteInfo = interPaletteInfo;
}
- return selectedCost;
+ return selectedStatistics;
}
///
/// Evaluates the supported LAST_FRAME modes and publishes only a strict improvement over the intra result.
///
- private long SelectInterBlock(
+ private Av1RateDistortionStatistics SelectInterBlock(
Av1SymbolEncoder writer,
Av1MacroBlockD macroBlock,
Point blockOrigin,
ushort tileIndex,
- long regularCost,
+ Av1RateDistortionStatistics regularStatistics,
ref Av1MacroBlockModeInfo modeInfo,
ref Av1EncoderBlockStruct block,
ref Av1EncoderPaletteInfo paletteInfo)
@@ -651,7 +651,7 @@ internal static partial class Av1IntraSuperblockEncoder
transformPartitionRate = writer.GetTransformPartitionCost(false, transformPartitionContext);
}
- long selectedCost = regularCost;
+ Av1RateDistortionStatistics selectedStatistics = regularStatistics;
Av1MotionVector selectedVector = default;
Av1PredictionMode selectedMode = default;
int selectedReferenceIndex = 0;
@@ -785,7 +785,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
- long candidateCost = this.EvaluateInterCandidate(
+ Av1RateDistortionStatistics candidateStatistics = this.EvaluateInterCandidate(
writer,
blockOrigin,
tileIndex,
@@ -812,7 +812,7 @@ internal static partial class Av1IntraSuperblockEncoder
out Av1EncoderTransformBlockState candidateRedState);
// Strict replacement preserves predictor-stack, global, then new-motion order on equal RD cost.
- if (candidateCost >= selectedCost)
+ if (candidateStatistics.Cost >= selectedStatistics.Cost)
{
continue;
}
@@ -841,7 +841,7 @@ internal static partial class Av1IntraSuperblockEncoder
selectedRedCoefficients = candidateRedCoefficients;
candidateRedCoefficients = previousRedCoefficients;
- selectedCost = candidateCost;
+ selectedStatistics = candidateStatistics;
selectedVector = candidateVectors[candidateIndex];
selectedMode = candidateModes[candidateIndex];
selectedHorizontalFilter = horizontalFilter;
@@ -858,7 +858,7 @@ internal static partial class Av1IntraSuperblockEncoder
// when no inter candidate strictly improves its rate-distortion cost.
if (!hasInterWinner)
{
- return regularCost;
+ return regularStatistics;
}
Span retainedLumaCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.Y);
@@ -930,7 +930,7 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfo.Block.HorizontalInterpolationFilter = selectedHorizontalFilter;
block.ReferenceMotionVectorIndex = selectedReferenceIndex;
this.picture.SetDisplacementVector(modeInfoPosition, selectedVector);
- return selectedCost;
+ return selectedStatistics;
}
///
@@ -1034,7 +1034,7 @@ internal static partial class Av1IntraSuperblockEncoder
///
/// Evaluates one inter mode through prediction, transform, coefficient, skip, and distortion selection.
///
- private long EvaluateInterCandidate(
+ private Av1RateDistortionStatistics EvaluateInterCandidate(
Av1SymbolEncoder writer,
Point blockOrigin,
ushort tileIndex,
@@ -1238,16 +1238,16 @@ internal static partial class Av1IntraSuperblockEncoder
redRate;
long codedDistortion = lumaDistortion + blueDistortion + redDistortion;
- long selectedCost = Av1RateDistortion.GetCost(this.rateMultiplier, codedRate, codedDistortion);
+ Av1RateDistortionStatistics selectedStatistics = new(this.rateMultiplier, codedRate, codedDistortion);
skip = false;
if (hasEmptyLuma && hasEmptyBlue && hasEmptyRed)
{
int skipRate = predictionRate + writer.GetSkipCost(true, skipContext);
long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion;
- long skipCost = Av1RateDistortion.GetCost(this.rateMultiplier, skipRate, skipDistortion);
- if (skipCost < selectedCost)
+ Av1RateDistortionStatistics skipStatistics = new(this.rateMultiplier, skipRate, skipDistortion);
+ if (skipStatistics.Cost < selectedStatistics.Cost)
{
- selectedCost = skipCost;
+ selectedStatistics = skipStatistics;
skip = true;
workspace.LumaPrediction[..LumaTransformSize.GetSize2d()].CopyTo(lumaReconstruction);
lumaCoefficients[..LumaTransformSize.GetSize2d()].Clear();
@@ -1265,7 +1265,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
- return selectedCost;
+ return selectedStatistics;
}
///
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
index bbaddfb107..b9187859a9 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs
@@ -817,6 +817,29 @@ public class Av1EntropyTests
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]
[InlineData(1, 255, 0L, 0L)]
[InlineData(1, 256, 0L, 1L)]
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs
index 7d5c558aea..36f25efbb1 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs
@@ -888,6 +888,171 @@ public class Av1IntraSuperblockEncoderTests
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 source = new(Configuration.Default, Width, Height, 8, colorFormat, 1, 1);
+ using Av1EncoderFrameBuffer reconstruction = new(Configuration.Default, Width, Height, 8, colorFormat, 1, 1);
+ int planeCount = isMonochrome ? 1 : 3;
+ for (int planeIndex = 0; planeIndex < planeCount; planeIndex++)
+ {
+ Buffer2DRegion plane = source.Frame.CodedView.GetPlane((Av1Plane)planeIndex);
+ int length = planeIndex == 0 ? 8 : 4;
+ for (int y = 0; y < length; y++)
+ {
+ Span 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 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 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 sourcePlane = source.Frame.CodedView.GetPlane(plane);
+ Buffer2DRegion reconstructedPlane = reconstruction.Frame.CodedView.GetPlane(plane);
+ int length = planeIndex == 0 ? 8 : 4;
+ for (int y = 0; y < length; y++)
+ {
+ ReadOnlySpan sourceRow = sourcePlane.DangerousGetRowSpan(y);
+ ReadOnlySpan 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]
public void BlockDecisionObservesLiveCdfInWriterOrder()
{