diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 8612fa4e00..58cb57e1bc 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -23,6 +23,19 @@ maximum error zero, differing samples zero, and samples exceeding one zero. Temp uncommitted Hadamard-screening experiment remain excluded. Decoder-wide coverage and performance acceptance remain open; these results verify the recorded cases and do not establish complete codec conformance. +The inter-mode ordering checkpoint evaluates inter candidates before intra and retains only the winning syntax +and transform choices across those trials. Final inter reconstruction regenerates the selected transforms using +existing scratch storage; empty planes retain prediction without re-quantization. Equal-cost intra candidates +do not replace the preceding inter winner. The source basis is `av1/encoder/rdopt.c:6355-6399,6491-6494` +and `av1/encoder/partition_search.c:474-503`. This corrects ordering and winner publication, but does not implement +the reference's full intra gating, TPL decisions, winner refinement, larger inter partitions, or additional references. +After the final code edit, Release .NET 11 built with zero errors and zero warnings; serialized VSTest passed +379/379 focused encoder, superblock, intra-copy, and public HEIF cases (`inter-first-r2.trx`, 41.5269 seconds). +Fresh optimized-native comparisons matched 86,859 samples across 39 motion streams and 477,243 samples across +three larger row-refresh streams. Encoder reconstruction also matched native decoding over 102,390 samples in +12 deblocking streams. Each comparison reported maximum error zero, differing samples zero, and counts exceeding +one zero. These checks establish the recorded same-bitstream behavior, not separate-encoder parity or performance. + Acceptance criteria are separate for encoding and decoding: - Encoder parity permits at most one component unit per sample when comparing separately encoded results @@ -331,7 +344,7 @@ Line numbers describe the inspected starting tree, before subsequent corrections | Missing functionality | `Av1FrameEncoder.cs:372-408`, under `src/ImageSharp/Formats/Heif/Av1/Pipeline` | `av1/encoder/encoder.c:641-646`; `av1/av1_cx_iface.c:287-288,1284-1286,1561-1562` | Sequence setup unconditionally disables CDEF, restoration, and intra-edge filtering. These are not equivalent to the reference's configured tool decisions. | | Architectural deviation | `Av1FrameEncoder.cs:508-542,1551-1557` | `av1/encoder/encode_strategy.c:168-230,1664-1669` | Every frame is error resilient, refreshes all slots, disables frame-end CDF publication, and resets probabilities. The reference selects retained primary-reference state. | | Missing functionality | `Av1IntraSuperblockEncoder.ModeDecision.cs:185-245`; `Av1IntraSuperblockEncoder.ReferenceModeDecision.cs:526-566` | `av1/encoder/partition_search.c:3320` onward; `av1/encoder/rdopt.c:6196-6236` | Inter frames retain a fixed 8x8 partition tree and search only LAST. Larger partitions and additional reference roles are not implemented by this path. | -| Architectural deviation | `Av1IntraSuperblockEncoder.ModeDecision.cs:597-609,662-672,824-834` | `av1/encoder/rdopt.c:111-142,6186-6236`; `av1/encoder/intra_mode_search.c:1291-1344` | Managed coding finishes intra search before inter evaluation. Reference inter search has its own ordered candidates, pruning state, bounds, and later intra evaluation. | +| Architectural deviation, partly corrected | `Av1IntraSuperblockEncoder.ModeDecision.cs:606-753` | `av1/encoder/rdopt.c:6355-6399,6491-6494`; `av1/encoder/partition_search.c:474-503` | Inter now precedes intra and the final winner is reconstructed after competition. Full intra gating, pruning state, bounds, and winner refinement remain incomplete. | | Architectural deviation | `Av1IntraSuperblockEncoder.ReferenceModeDecision.cs:576-617` | `av1/encoder/rdopt.c:111-142` | Managed single-reference mode order is NEAREST, NEAR, GLOBAL, NEW. The reference default order is NEAREST, NEW, NEAR, GLOBAL across eligible references. | | Architectural deviation | `Av1IntraSuperblockEncoder.ReferenceModeDecision.cs:1278-1414` | `av1/encoder/mcomp.c`; caller policy in `av1/encoder/motion_search_facade.c` | The managed radius is an effort-shifted value capped by its border; each scale visits eight offsets once. This is a simplified search controller whose full reference-policy reconciliation remains open. | | Missing functionality | `Av1TransformBlockEncoder.cs:1047-1097` | `av1/encoder/encodemb.c:842-885` | Lossy transform coding ends at fast quantization. Reference coding selects quantization with trellis policy and can optimize coefficients before reconstruction. Native primitive arithmetic alone does not establish encoder parity. | diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs index 8846589978..324d1866bb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; @@ -602,6 +603,36 @@ internal static partial class Av1IntraSuperblockEncoder block.QuantizationIndex = qIndex; block.SegmentId = 0; + bool isInterFrame = !this.picture.Parent.FrameHeader.IsIntra; + Av1RateDistortionStatistics interStatistics = Av1RateDistortionStatistics.Invalid; + Av1MacroBlockModeInfo interModeInfo = default; + Av1EncoderBlockStruct interBlock = default; + InlineArray3 interStates = default; + Av1MotionVector interVector = default; + if (isInterFrame) + { + Av1MacroBlockModeInfo initialModeInfo = modeInfo; + Av1EncoderBlockStruct initialBlock = block; + interStatistics = this.SelectInterBlock( + writer, + macroBlock, + blockOrigin, + tileIndex, + ref modeInfo, + ref block, + out interVector, + out interStates); + + interModeInfo = modeInfo; + interBlock = block; + + // Only the winning syntax and transform choices survive across mode families. Intra trials + // reuse prediction and coefficient scratch; the selected inter block is reconstructed afterward. + modeInfo = initialModeInfo; + block = initialBlock; + paletteInfo = default; + } + Span lumaCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.Y); Span lumaTransformBlocks = this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.Y); @@ -628,83 +659,39 @@ internal static partial class Av1IntraSuperblockEncoder block.FilterIntraMode = filterIntraMode; modeInfo.Block.TransformSize = lumaTransformSize; - // Ordinary intra keeps the block non-skipped, including when all transforms are empty. Its RD cost - // includes those transform symbols and the non-skip flag; only inter or IBC winners can replace this state. - if (this.source.IsMonochrome) + int chromaArea = 0; + if (block.HasChroma) { - bool allowIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 && - this.picture.Parent.FrameHeader.AllowIntraBlockCopy; - - Av1RateDistortionStatistics regularStatistics = this.GetRegularBlockCost( - writer, - macroBlock, - lumaStatistics, - allowIntraBlockCopy); - - if (!this.picture.Parent.FrameHeader.IsIntra) - { - this.SelectedBlockStatistics = this.SelectInterPrediction( - writer, - macroBlock, - blockOrigin, - tileIndex, - regularStatistics, - ref modeInfo, - ref block, - ref paletteInfo); - } - else - { - this.SelectedBlockStatistics = allowIntraBlockCopy - ? this.SelectIntraBlockCopy( - writer, - macroBlock, - blockOrigin, - tileIndex, - regularStatistics, - ref modeInfo, - ref block, - ref paletteInfo) - : regularStatistics; - } - - this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight(); - return; - } + ObuColorConfig colorConfig = this.picture.Sequence.SequenceHeader.ColorConfig; + int subsamplingX = colorConfig.SubSamplingX ? 1 : 0; + int subsamplingY = colorConfig.SubSamplingY ? 1 : 0; + Point chromaOrigin = Av1TileWriter.GetChromaBlockOrigin( + blockOrigin, + subsamplingX, + subsamplingY); - ObuColorConfig colorConfig = this.picture.Sequence.SequenceHeader.ColorConfig; - int subsamplingX = colorConfig.SubSamplingX ? 1 : 0; - int subsamplingY = colorConfig.SubSamplingY ? 1 : 0; - Point chromaOrigin = Av1TileWriter.GetChromaBlockOrigin( - blockOrigin, - subsamplingX, - subsamplingY); + Av1TransformSize chromaTransformSize = this.picture.Parent.FrameHeader.CodedLossless + ? Av1TransformSize.Size4x4 + : blockSize.GetMaxUvTransformSize( + colorConfig.SubSamplingX, + colorConfig.SubSamplingY); - Av1TransformSize chromaTransformSize = this.picture.Parent.FrameHeader.CodedLossless - ? Av1TransformSize.Size4x4 - : blockSize.GetMaxUvTransformSize( + Av1BlockSize chromaBlockSize = blockSize.GetSubsampled( colorConfig.SubSamplingX, colorConfig.SubSamplingY); - Av1BlockSize chromaBlockSize = blockSize.GetSubsampled( - colorConfig.SubSamplingX, - colorConfig.SubSamplingY); - - Span blueCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.U); - Span redCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.V); - Span blueTransformBlocks = - this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.U); - Span redTransformBlocks = - this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.V); + Span blueCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.U); + Span redCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.V); + Span blueTransformBlocks = + this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.U); + Span redTransformBlocks = + this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.V); - int chromaTransformIndex = this.codedAreaChroma / - Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; + int chromaTransformIndex = this.codedAreaChroma / + Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; - Span retainedBlueStates = blueTransformBlocks[chromaTransformIndex..]; - Span retainedRedStates = redTransformBlocks[chromaTransformIndex..]; - Av1RateDistortionStatistics chromaStatistics = default; - if (block.HasChroma) - { + Span retainedBlueStates = blueTransformBlocks[chromaTransformIndex..]; + Span retainedRedStates = redTransformBlocks[chromaTransformIndex..]; modeInfo.Block.UvMode = this.SelectChromaMode( writer, macroBlock, @@ -723,55 +710,51 @@ internal static partial class Av1IntraSuperblockEncoder out int chromaAngleDelta, out byte chromaFromLumaIndex, out sbyte chromaFromLumaSigns, - out chromaStatistics); + out Av1RateDistortionStatistics chromaStatistics); block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = (sbyte)chromaAngleDelta; block.PredictionUnit.ChromaFromLumaIndex = chromaFromLumaIndex; block.PredictionUnit.ChromaFromLumaSigns = chromaFromLumaSigns; + chromaArea = chromaBlockSize.GetWidth() * chromaBlockSize.GetHeight(); + lumaStatistics.Add(this.rateMultiplier, in chromaStatistics); } - bool allowColorIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 && + bool allowIntraBlockCopy = blockSize == Av1BlockSize.Block8x8 && this.picture.Parent.FrameHeader.AllowIntraBlockCopy; - lumaStatistics.Add(this.rateMultiplier, in chromaStatistics); - Av1RateDistortionStatistics regularColorStatistics = this.GetRegularBlockCost( + Av1RateDistortionStatistics regularStatistics = this.GetRegularBlockCost( writer, macroBlock, lumaStatistics, - allowColorIntraBlockCopy); + allowIntraBlockCopy); - if (!this.picture.Parent.FrameHeader.IsIntra) + if (isInterFrame && interStatistics.Cost <= regularStatistics.Cost) { - this.SelectedBlockStatistics = this.SelectInterPrediction( - writer, - macroBlock, - blockOrigin, - tileIndex, - regularColorStatistics, - ref modeInfo, - ref block, - ref paletteInfo); + // Inter candidates precede intra candidates, so an equal cost retains the inter winner. + modeInfo = interModeInfo; + block = interBlock; + paletteInfo = default; + this.ReconstructSelectedInterBlock(blockOrigin, modeInfo, block, interVector, interStates); + this.picture.SetDisplacementVector(modeInfoPosition, interVector); + this.SelectedBlockStatistics = interStatistics; } else { - this.SelectedBlockStatistics = allowColorIntraBlockCopy + this.SelectedBlockStatistics = allowIntraBlockCopy ? this.SelectIntraBlockCopy( writer, macroBlock, blockOrigin, tileIndex, - regularColorStatistics, + regularStatistics, ref modeInfo, ref block, ref paletteInfo) - : regularColorStatistics; + : regularStatistics; } this.codedAreaLuma += blockSize.GetWidth() * blockSize.GetHeight(); - if (block.HasChroma) - { - this.codedAreaChroma += chromaBlockSize.GetWidth() * chromaBlockSize.GetHeight(); - } + this.codedAreaChroma += chromaArea; } private Av1RateDistortionStatistics EvaluatePartitionLeaf( diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs index be582a4a60..717d3c19fe 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs @@ -14,7 +14,7 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; /// -/// Provides full rate-distortion selection for reference-frame and intra-block-copy candidates. +/// Provides reference-frame and intra-block-copy mode decisions. /// internal static partial class Av1IntraSuperblockEncoder { @@ -449,53 +449,17 @@ internal static partial class Av1IntraSuperblockEncoder } /// - /// Compares the retained intra result with an inter candidate without disturbing the intra result on loss. - /// - private Av1RateDistortionStatistics SelectInterPrediction( - Av1SymbolEncoder writer, - Av1MacroBlockD macroBlock, - Point blockOrigin, - ushort tileIndex, - Av1RateDistortionStatistics regularStatistics, - ref Av1MacroBlockModeInfo modeInfo, - ref Av1EncoderBlockStruct block, - ref Av1EncoderPaletteInfo paletteInfo) - { - Av1MacroBlockModeInfo interModeInfo = modeInfo; - Av1EncoderBlockStruct interBlock = block; - Av1EncoderPaletteInfo interPaletteInfo = default; - Av1RateDistortionStatistics selectedStatistics = this.SelectInterBlock( - writer, - macroBlock, - blockOrigin, - tileIndex, - regularStatistics, - ref interModeInfo, - ref interBlock, - ref interPaletteInfo); - - if (selectedStatistics.Cost < regularStatistics.Cost) - { - modeInfo = interModeInfo; - block = interBlock; - paletteInfo = interPaletteInfo; - } - - return selectedStatistics; - } - - /// - /// Evaluates the supported LAST_FRAME modes and publishes only a strict improvement over the intra result. + /// Evaluates reference-frame modes and retains the winning syntax and transform choices. /// private Av1RateDistortionStatistics SelectInterBlock( Av1SymbolEncoder writer, Av1MacroBlockD macroBlock, Point blockOrigin, ushort tileIndex, - Av1RateDistortionStatistics regularStatistics, ref Av1MacroBlockModeInfo modeInfo, ref Av1EncoderBlockStruct block, - ref Av1EncoderPaletteInfo paletteInfo) + out Av1MotionVector selectedVector, + out InlineArray3 selectedStates) { const Av1BlockSize BlockSize = Av1BlockSize.Block8x8; const Av1TransformSize LumaTransformSize = Av1TransformSize.Size8x8; @@ -511,7 +475,6 @@ internal static partial class Av1IntraSuperblockEncoder block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = 0; block.PredictionUnit.ChromaFromLumaIndex = 0; block.PredictionUnit.ChromaFromLumaSigns = 0; - paletteInfo = default; Av1EncoderInterPredictionWorkspace workspace = this.blockWorkspace.GetInterPredictionWorkspace(); @@ -601,15 +564,15 @@ internal static partial class Av1IntraSuperblockEncoder transformPartitionRate = writer.GetTransformPartitionCost(false, transformPartitionContext); } - Av1RateDistortionStatistics selectedStatistics = regularStatistics; - Av1MotionVector selectedVector = default; + Av1RateDistortionStatistics selectedStatistics = Av1RateDistortionStatistics.Invalid; + selectedVector = default; + selectedStates = default; Av1PredictionMode selectedMode = default; int selectedReferenceIndex = 0; bool selectedSkip = false; Av1EncoderTransformBlockState selectedLumaState = default; Av1EncoderTransformBlockState selectedBlueState = default; Av1EncoderTransformBlockState selectedRedState = default; - bool hasInterWinner = false; ObuSequenceHeader sequenceHeader = this.picture.Sequence.SequenceHeader; bool isSwitchable = frameHeader.InterpolationFilter == Av1InterpolationFilter.Switchable; @@ -939,88 +902,122 @@ internal static partial class Av1IntraSuperblockEncoder selectedLumaState = candidateLumaState; selectedBlueState = candidateBlueState; selectedRedState = candidateRedState; - hasInterWinner = true; - } - - // Inter trials never overwrite retained picture state. The complete intra result remains authoritative - // when no inter candidate strictly improves its rate-distortion cost. - if (!hasInterWinner) - { - return regularStatistics; } - Span retainedLumaCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.Y); - Span retainedLumaTransformBlocks = - this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.Y); - - int lumaTransformIndex = this.codedAreaLuma / - Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; - - CopyCandidate( - selectedLumaReconstruction, - selectedLumaCoefficients, - this.reconstruction.GetPlane(Av1Plane.Y), - blockOrigin, - retainedLumaCoefficients[this.codedAreaLuma..], - LumaTransformSize, - selectedLumaState, - ref retainedLumaTransformBlocks[lumaTransformIndex]); - - if (block.HasChroma) - { - ObuColorConfig colorConfig = this.picture.Sequence.SequenceHeader.ColorConfig; - int subsamplingX = colorConfig.SubSamplingX ? 1 : 0; - int subsamplingY = colorConfig.SubSamplingY ? 1 : 0; - Point chromaOrigin = Av1TileWriter.GetChromaBlockOrigin( - blockOrigin, - subsamplingX, - subsamplingY); - - Av1TransformSize chromaTransformSize = BlockSize.GetMaxUvTransformSize( - colorConfig.SubSamplingX, - colorConfig.SubSamplingY); - - Span retainedBlueCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.U); - Span retainedRedCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.V); - Span retainedBlueTransformBlocks = - this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.U); - - Span retainedRedTransformBlocks = - this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.V); - - int chromaTransformIndex = this.codedAreaChroma / - Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; - - CopyCandidate( - selectedBlueReconstruction, - selectedBlueCoefficients, - this.reconstruction.GetPlane(Av1Plane.U), - chromaOrigin, - retainedBlueCoefficients[this.codedAreaChroma..], - chromaTransformSize, - selectedBlueState, - ref retainedBlueTransformBlocks[chromaTransformIndex]); - - CopyCandidate( - selectedRedReconstruction, - selectedRedCoefficients, - this.reconstruction.GetPlane(Av1Plane.V), - chromaOrigin, - retainedRedCoefficients[this.codedAreaChroma..], - chromaTransformSize, - selectedRedState, - ref retainedRedTransformBlocks[chromaTransformIndex]); - } + // Candidate pixels and coefficients remain scratch. Preserve the transform decisions so final + // reconstruction can regenerate only the winner after other mode families reuse this storage. + selectedStates[0] = selectedLumaState; + selectedStates[1] = selectedBlueState; + selectedStates[2] = selectedRedState; modeInfo.Block.Mode = selectedMode; modeInfo.Block.Skip = selectedSkip; modeInfo.Block.VerticalInterpolationFilter = selectedVerticalFilter; modeInfo.Block.HorizontalInterpolationFilter = selectedHorizontalFilter; block.ReferenceMotionVectorIndex = selectedReferenceIndex; - this.picture.SetDisplacementVector(modeInfoPosition, selectedVector); return selectedStatistics; } + /// + /// Reconstructs the selected inter mode after intra trials have reused its arithmetic storage. + /// + /// The luma block origin. + /// The selected prediction and interpolation syntax. + /// The selected block parameters. + /// The selected motion vector in eighth-luma-sample units. + /// The selected transform choices, indexed by plane. + private void ReconstructSelectedInterBlock( + Point blockOrigin, + Av1MacroBlockModeInfo modeInfo, + Av1EncoderBlockStruct block, + Av1MotionVector vector, + ReadOnlySpan states) + { + Av1EncoderInterPredictionWorkspace workspace = this.blockWorkspace.GetInterPredictionWorkspace(); + ObuColorConfig colorConfig = this.picture.Sequence.SequenceHeader.ColorConfig; + int planeCount = block.HasChroma ? 3 : 1; + for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) + { + Av1Plane plane = (Av1Plane)planeIndex; + int subX = planeIndex == 0 ? 0 : this.source.ChromaSubsamplingX; + int subY = planeIndex == 0 ? 0 : this.source.ChromaSubsamplingY; + Point planeOrigin = new(blockOrigin.X >> subX, blockOrigin.Y >> subY); + Av1TransformSize transformSize = planeIndex == 0 + ? modeInfo.Block.TransformSize + : modeInfo.Block.BlockSize.GetMaxUvTransformSize(colorConfig.SubSamplingX, colorConfig.SubSamplingY); + + int sampleCount = transformSize.GetSize2d(); + Span reconstruction = workspace.LumaCandidateReconstruction[..sampleCount]; + Span coefficients = workspace.LumaCandidateCoefficients[..sampleCount]; + Span prediction = workspace.LumaPrediction[..sampleCount]; + Span residual = workspace.Residual[..sampleCount]; + + // Convert eighth-luma-sample motion into the plane's sixteenth-sample interpolation + // coordinates. The low four bits carry the phase; the remaining bits locate the reference. + int columnQ4 = (planeOrigin.X << 4) + (vector.Column << (1 - subX)); + int rowQ4 = (planeOrigin.Y << 4) + (vector.Row << (1 - subY)); + TOperator.PrepareTranslationalInterPrediction( + this.source.GetPlane(plane), + planeOrigin, + this.reference.GetPlane(plane), + new Point(columnQ4 >> 4, rowQ4 >> 4), + modeInfo.Block.HorizontalInterpolationFilter, + modeInfo.Block.VerticalInterpolationFilter, + columnQ4 & 15, + rowQ4 & 15, + prediction, + residual, + workspace.PredictionScratch, + transformSize, + this.bitDepth); + + Av1EncoderTransformBlockState state = default; + if (modeInfo.Block.Skip || states[planeIndex].EndOfBlock == 0) + { + // An empty transform retains prediction even when other planes have coded residuals. + // Re-quantizing its inferred DCT could otherwise introduce coefficients absent in the winner. + reconstruction = prediction; + coefficients.Clear(); + } + else + { + // Regenerate only the selected transform. Motion, transform choice, coefficient-rate + // measurement, and skip decisions are complete before this final reconstruction. + _ = TOperator.EncodePredictionCandidate( + this.blockWorkspace, + this.source.GetPlane(plane), + planeOrigin, + prediction, + residual, + reconstruction, + transformSize.GetWidth(), + coefficients, + transformSize, + states[planeIndex].TransformType, + plane, + this.quantization.QIndex[0], + this.quantization.DeltaQDc[planeIndex], + this.quantization.DeltaQAc[planeIndex], + this.bitDepth, + ref state); + } + + int codedArea = planeIndex == 0 ? this.codedAreaLuma : this.codedAreaChroma; + int transformIndex = codedArea / Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; + Span retainedCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, plane); + Span retainedStates = this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, plane); + CopyCandidate( + reconstruction, + coefficients, + this.reconstruction.GetPlane(plane), + planeOrigin, + retainedCoefficients[codedArea..], + transformSize, + state, + ref retainedStates[transformIndex]); + } + } + /// /// Ranks a filter pair from visible prediction error using the reference curve model, without transforming samples. ///