mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Preserve the current encoder, container sequence, and decoder integration work. Record focused Release verification and the remaining interpolation/conformance work in the implementation plan. Non-regular interpolation runtime verification remains open.pull/2633/head
66 changed files with 10630 additions and 2356 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
@ -1,586 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
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; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
|
||||
|
|
||||
/// <content>
|
|
||||
/// Provides full rate-distortion selection for intra-block-copy candidates.
|
|
||||
/// </content>
|
|
||||
internal static partial class Av1IntraSuperblockEncoder |
|
||||
{ |
|
||||
internal partial struct ModeDecision<TSample, TOperator> |
|
||||
where TSample : unmanaged |
|
||||
where TOperator : struct, IBlockEncodingOperator<TSample> |
|
||||
{ |
|
||||
private long SelectIntraBlockCopy( |
|
||||
Av1SymbolEncoder writer, |
|
||||
Av1MacroBlockD macroBlock, |
|
||||
Point blockOrigin, |
|
||||
ushort tileIndex, |
|
||||
long regularCost, |
|
||||
ref Av1MacroBlockModeInfo modeInfo, |
|
||||
ref Av1EncoderBlockStruct block, |
|
||||
ref Av1EncoderPaletteInfo paletteInfo) |
|
||||
{ |
|
||||
const Av1BlockSize BlockSize = Av1BlockSize.Block8x8; |
|
||||
const Av1TransformSize LumaTransformSize = Av1TransformSize.Size8x8; |
|
||||
Buffer2DRegion<TSample> lumaSource = this.source.GetPlane(Av1Plane.Y); |
|
||||
Buffer2DRegion<TSample> lumaReconstruction = this.reconstruction.GetPlane(Av1Plane.Y); |
|
||||
Point modeInfoPosition = new( |
|
||||
blockOrigin.X >> Av1Constants.ModeInfoSizeLog2, |
|
||||
blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2); |
|
||||
|
|
||||
Span<Av1MotionVector> referenceCandidates = stackalloc Av1MotionVector[8]; |
|
||||
Span<int> referenceWeights = stackalloc int[8]; |
|
||||
Av1MotionVector reference = Av1IntraBlockCopy.FindReference( |
|
||||
this.picture, |
|
||||
macroBlock, |
|
||||
modeInfoPosition, |
|
||||
BlockSize, |
|
||||
Av1PartitionType.None, |
|
||||
referenceCandidates, |
|
||||
referenceWeights); |
|
||||
|
|
||||
Span<Av1MotionVector> candidates = stackalloc Av1MotionVector[4]; |
|
||||
Av1IntraBlockCopySearchIndex search = this.picture.IntraBlockCopySearch; |
|
||||
int candidateCount = search.FindCandidates<TSample, TOperator>( |
|
||||
lumaSource, |
|
||||
lumaReconstruction, |
|
||||
blockOrigin, |
|
||||
macroBlock.Tile, |
|
||||
this.picture.Sequence.SequenceHeader, |
|
||||
writer, |
|
||||
reference, |
|
||||
this.rateMultiplier, |
|
||||
candidates); |
|
||||
|
|
||||
candidateCount += search.FindPixelCandidates<TSample, TOperator>( |
|
||||
lumaSource, |
|
||||
lumaReconstruction, |
|
||||
blockOrigin, |
|
||||
macroBlock.Tile, |
|
||||
this.picture.Sequence.SequenceHeader, |
|
||||
writer, |
|
||||
reference, |
|
||||
this.quantization.QIndex[0], |
|
||||
this.rateMultiplier, |
|
||||
candidates[candidateCount..]); |
|
||||
|
|
||||
// Hash and full-pixel searches can converge on the same vector. Preserve the first search-order
|
|
||||
// occurrence so repeated vectors do not pay for duplicate transform searches or alter ties.
|
|
||||
int uniqueCandidateCount = 0; |
|
||||
for (int candidateIndex = 0; candidateIndex < candidateCount; candidateIndex++) |
|
||||
{ |
|
||||
Av1MotionVector candidate = candidates[candidateIndex]; |
|
||||
bool duplicate = false; |
|
||||
for (int uniqueIndex = 0; uniqueIndex < uniqueCandidateCount; uniqueIndex++) |
|
||||
{ |
|
||||
if (candidate == candidates[uniqueIndex]) |
|
||||
{ |
|
||||
duplicate = true; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!duplicate) |
|
||||
{ |
|
||||
candidates[uniqueCandidateCount++] = candidate; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (uniqueCandidateCount == 0) |
|
||||
{ |
|
||||
return regularCost; |
|
||||
} |
|
||||
|
|
||||
int skipContext = Av1TileWriter.GetSkipContext(macroBlock); |
|
||||
long bestCost = regularCost; |
|
||||
bool hasSelectedCandidate = false; |
|
||||
bool selectedSkip = false; |
|
||||
Av1MotionVector selectedVector = default; |
|
||||
Av1EncoderTransformBlockState selectedLumaState = default; |
|
||||
Av1EncoderTransformBlockState selectedBlueState = default; |
|
||||
Av1EncoderTransformBlockState selectedRedState = default; |
|
||||
Av1EncoderIntraBlockCopyWorkspace<TSample> workspace = |
|
||||
this.blockWorkspace.GetIntraBlockCopyWorkspace<TSample>(); |
|
||||
|
|
||||
Av1TransformBlockContext lumaContext = Av1TileWriter.GetTransformBlockContexts( |
|
||||
Av1ComponentType.Luminance, |
|
||||
this.picture.LuminanceDcSignLevelCoefficientNeighbors[tileIndex], |
|
||||
blockOrigin, |
|
||||
BlockSize, |
|
||||
LumaTransformSize); |
|
||||
|
|
||||
// A coded IBC residual uses the unsplit transform root at this fixed block size. A skipped block
|
|
||||
// omits both the transform-partition bit and coefficient syntax, so this rate is added only below.
|
|
||||
int transformPartitionRate = 0; |
|
||||
if (this.picture.Parent.FrameHeader.TransformMode == Av1TransformMode.Select) |
|
||||
{ |
|
||||
Av1NeighborArrayUnit<byte> transformContexts = this.picture.TransformFunctionContexts[tileIndex]; |
|
||||
int topIndex = transformContexts.GetTopIndex(blockOrigin); |
|
||||
int leftIndex = transformContexts.GetLeftIndex(blockOrigin); |
|
||||
int transformPartitionContext = Av1SymbolContextHelper.GetTransformPartitionContext( |
|
||||
transformContexts.Top[topIndex], |
|
||||
transformContexts.Left[leftIndex], |
|
||||
BlockSize, |
|
||||
LumaTransformSize); |
|
||||
|
|
||||
transformPartitionRate = writer.GetTransformPartitionCost( |
|
||||
false, |
|
||||
transformPartitionContext); |
|
||||
} |
|
||||
|
|
||||
ObuColorConfig colorConfig = this.picture.Sequence.SequenceHeader.ColorConfig; |
|
||||
int subsamplingX = colorConfig.SubSamplingX ? 1 : 0; |
|
||||
int subsamplingY = colorConfig.SubSamplingY ? 1 : 0; |
|
||||
Point chromaOrigin = new(blockOrigin.X >> subsamplingX, blockOrigin.Y >> subsamplingY); |
|
||||
Av1TransformSize chromaTransformSize = BlockSize.GetMaxUvTransformSize( |
|
||||
colorConfig.SubSamplingX, |
|
||||
colorConfig.SubSamplingY); |
|
||||
|
|
||||
Av1TransformBlockContext blueContext = default; |
|
||||
Av1TransformBlockContext redContext = default; |
|
||||
if (!this.source.IsMonochrome) |
|
||||
{ |
|
||||
Av1BlockSize chromaBlockSize = BlockSize.GetSubsampled( |
|
||||
colorConfig.SubSamplingX, |
|
||||
colorConfig.SubSamplingY); |
|
||||
|
|
||||
blueContext = Av1TileWriter.GetTransformBlockContexts( |
|
||||
Av1ComponentType.Chroma, |
|
||||
this.picture.CbDcSignLevelCoefficientNeighbors[tileIndex], |
|
||||
chromaOrigin, |
|
||||
chromaBlockSize, |
|
||||
chromaTransformSize); |
|
||||
|
|
||||
redContext = Av1TileWriter.GetTransformBlockContexts( |
|
||||
Av1ComponentType.Chroma, |
|
||||
this.picture.CrDcSignLevelCoefficientNeighbors[tileIndex], |
|
||||
chromaOrigin, |
|
||||
chromaBlockSize, |
|
||||
chromaTransformSize); |
|
||||
} |
|
||||
|
|
||||
// Per-vector plane results reuse candidate scratch. Separate selected spans retain only a new
|
|
||||
// global winner, allowing the complete search to finish before committed reconstruction changes.
|
|
||||
for (int candidateIndex = 0; candidateIndex < uniqueCandidateCount; candidateIndex++) |
|
||||
{ |
|
||||
Av1MotionVector candidate = candidates[candidateIndex]; |
|
||||
this.EvaluateIntraBlockCopyPlane( |
|
||||
writer, |
|
||||
candidate, |
|
||||
Av1Plane.Y, |
|
||||
Av1ComponentType.Luminance, |
|
||||
blockOrigin, |
|
||||
0, |
|
||||
0, |
|
||||
LumaTransformSize, |
|
||||
Av1TransformType.AllTransformTypes, |
|
||||
lumaContext, |
|
||||
workspace.LumaPrediction, |
|
||||
workspace.Residual, |
|
||||
workspace.TransformReconstruction, |
|
||||
workspace.TransformCoefficients, |
|
||||
workspace.LumaCandidateReconstruction, |
|
||||
workspace.LumaCandidateCoefficients, |
|
||||
out Av1EncoderTransformBlockState lumaCandidateState, |
|
||||
out int lumaRate, |
|
||||
out long lumaDistortion, |
|
||||
out bool hasEmptyLuma, |
|
||||
out Av1EncoderTransformBlockState emptyLumaState, |
|
||||
out long emptyLumaDistortion); |
|
||||
|
|
||||
int blueRate = 0; |
|
||||
int redRate = 0; |
|
||||
long blueDistortion = 0; |
|
||||
long redDistortion = 0; |
|
||||
long emptyBlueDistortion = 0; |
|
||||
long emptyRedDistortion = 0; |
|
||||
bool hasEmptyBlue = true; |
|
||||
bool hasEmptyRed = true; |
|
||||
Av1EncoderTransformBlockState blueCandidateState = default; |
|
||||
Av1EncoderTransformBlockState redCandidateState = default; |
|
||||
Av1EncoderTransformBlockState emptyBlueState = default; |
|
||||
Av1EncoderTransformBlockState emptyRedState = default; |
|
||||
if (!this.source.IsMonochrome) |
|
||||
{ |
|
||||
Av1TransformType chromaTransformType = lumaCandidateState.TransformType; |
|
||||
Av1TransformSetType chromaTransformSet = Av1SymbolContextHelper.GetExtendedTransformSetType( |
|
||||
chromaTransformSize, |
|
||||
isInter: true, |
|
||||
this.picture.Parent.FrameHeader.UseReducedTransformSet); |
|
||||
|
|
||||
// Inter prediction does not signal an independent chroma transform type. Chroma reuses the
|
|
||||
// selected luma type when that type belongs to its transform set and otherwise falls back to DCT.
|
|
||||
if (!chromaTransformType.IsExtendedSetUsed(chromaTransformSet)) |
|
||||
{ |
|
||||
chromaTransformType = Av1TransformType.DctDct; |
|
||||
} |
|
||||
|
|
||||
this.EvaluateIntraBlockCopyPlane( |
|
||||
writer, |
|
||||
candidate, |
|
||||
Av1Plane.U, |
|
||||
Av1ComponentType.Chroma, |
|
||||
blockOrigin, |
|
||||
subsamplingX, |
|
||||
subsamplingY, |
|
||||
chromaTransformSize, |
|
||||
chromaTransformType, |
|
||||
blueContext, |
|
||||
workspace.BluePrediction, |
|
||||
workspace.Residual, |
|
||||
workspace.TransformReconstruction, |
|
||||
workspace.TransformCoefficients, |
|
||||
workspace.BlueCandidateReconstruction, |
|
||||
workspace.BlueCandidateCoefficients, |
|
||||
out blueCandidateState, |
|
||||
out blueRate, |
|
||||
out blueDistortion, |
|
||||
out hasEmptyBlue, |
|
||||
out emptyBlueState, |
|
||||
out emptyBlueDistortion); |
|
||||
|
|
||||
this.EvaluateIntraBlockCopyPlane( |
|
||||
writer, |
|
||||
candidate, |
|
||||
Av1Plane.V, |
|
||||
Av1ComponentType.Chroma, |
|
||||
blockOrigin, |
|
||||
subsamplingX, |
|
||||
subsamplingY, |
|
||||
chromaTransformSize, |
|
||||
chromaTransformType, |
|
||||
redContext, |
|
||||
workspace.RedPrediction, |
|
||||
workspace.Residual, |
|
||||
workspace.TransformReconstruction, |
|
||||
workspace.TransformCoefficients, |
|
||||
workspace.RedCandidateReconstruction, |
|
||||
workspace.RedCandidateCoefficients, |
|
||||
out redCandidateState, |
|
||||
out redRate, |
|
||||
out redDistortion, |
|
||||
out hasEmptyRed, |
|
||||
out emptyRedState, |
|
||||
out emptyRedDistortion); |
|
||||
} |
|
||||
|
|
||||
int displacementRate = writer.GetDisplacementVectorCost(candidate, reference); |
|
||||
int candidateRate = writer.GetUseIntraBlockCopyCost(true) + |
|
||||
displacementRate + |
|
||||
writer.GetSkipCost(false, skipContext) + |
|
||||
transformPartitionRate + |
|
||||
lumaRate + |
|
||||
blueRate + |
|
||||
redRate; |
|
||||
|
|
||||
long candidateDistortion = lumaDistortion + blueDistortion + redDistortion; |
|
||||
long candidateCost = Av1RateDistortion.GetCost(this.rateMultiplier, candidateRate, candidateDistortion); |
|
||||
bool candidateSkip = false; |
|
||||
|
|
||||
// The skip alternative is available only when every coded plane has an empty transform. Its
|
|
||||
// distortion comes from prediction alone and its rate excludes the transform tree and coefficients.
|
|
||||
if (hasEmptyLuma && hasEmptyBlue && hasEmptyRed) |
|
||||
{ |
|
||||
int skipRate = writer.GetUseIntraBlockCopyCost(true) + |
|
||||
displacementRate + |
|
||||
writer.GetSkipCost(true, skipContext); |
|
||||
|
|
||||
long skipDistortion = emptyLumaDistortion + emptyBlueDistortion + emptyRedDistortion; |
|
||||
long skipCost = Av1RateDistortion.GetCost(this.rateMultiplier, skipRate, skipDistortion); |
|
||||
if (skipCost < candidateCost) |
|
||||
{ |
|
||||
candidateCost = skipCost; |
|
||||
candidateSkip = true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Conventional intra and earlier IBC vectors retain strict search-order precedence on equal RD.
|
|
||||
if (candidateCost >= bestCost) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
bestCost = candidateCost; |
|
||||
hasSelectedCandidate = true; |
|
||||
selectedSkip = candidateSkip; |
|
||||
selectedVector = candidate; |
|
||||
if (candidateSkip) |
|
||||
{ |
|
||||
workspace.LumaPrediction.CopyTo(workspace.SelectedLumaReconstruction); |
|
||||
workspace.SelectedLumaCoefficients.Clear(); |
|
||||
selectedLumaState = emptyLumaState; |
|
||||
if (!this.source.IsMonochrome) |
|
||||
{ |
|
||||
int chromaSampleCount = chromaTransformSize.GetSize2d(); |
|
||||
workspace.BluePrediction[..chromaSampleCount].CopyTo(workspace.SelectedBlueReconstruction); |
|
||||
workspace.RedPrediction[..chromaSampleCount].CopyTo(workspace.SelectedRedReconstruction); |
|
||||
workspace.SelectedBlueCoefficients[..chromaSampleCount].Clear(); |
|
||||
workspace.SelectedRedCoefficients[..chromaSampleCount].Clear(); |
|
||||
selectedBlueState = emptyBlueState; |
|
||||
selectedRedState = emptyRedState; |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
workspace.LumaCandidateReconstruction.CopyTo(workspace.SelectedLumaReconstruction); |
|
||||
workspace.LumaCandidateCoefficients.CopyTo(workspace.SelectedLumaCoefficients); |
|
||||
selectedLumaState = lumaCandidateState; |
|
||||
if (!this.source.IsMonochrome) |
|
||||
{ |
|
||||
int chromaSampleCount = chromaTransformSize.GetSize2d(); |
|
||||
workspace.BlueCandidateReconstruction[..chromaSampleCount] |
|
||||
.CopyTo(workspace.SelectedBlueReconstruction); |
|
||||
|
|
||||
workspace.RedCandidateReconstruction[..chromaSampleCount] |
|
||||
.CopyTo(workspace.SelectedRedReconstruction); |
|
||||
|
|
||||
workspace.BlueCandidateCoefficients[..chromaSampleCount] |
|
||||
.CopyTo(workspace.SelectedBlueCoefficients); |
|
||||
|
|
||||
workspace.RedCandidateCoefficients[..chromaSampleCount] |
|
||||
.CopyTo(workspace.SelectedRedCoefficients); |
|
||||
|
|
||||
selectedBlueState = blueCandidateState; |
|
||||
selectedRedState = redCandidateState; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!hasSelectedCandidate) |
|
||||
{ |
|
||||
return bestCost; |
|
||||
} |
|
||||
|
|
||||
// Only the winning vector is now visible to later coding blocks. This single publication keeps
|
|
||||
// rejected motion vectors from contaminating intra references or entropy contexts.
|
|
||||
Span<int> retainedLumaCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.Y); |
|
||||
Span<Av1EncoderTransformBlockState> retainedLumaTransformBlocks = |
|
||||
this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.Y); |
|
||||
|
|
||||
int lumaTransformIndex = this.codedAreaLuma / |
|
||||
Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; |
|
||||
|
|
||||
ref Av1EncoderTransformBlockState retainedLumaState = ref retainedLumaTransformBlocks[lumaTransformIndex]; |
|
||||
CopyCandidate( |
|
||||
workspace.SelectedLumaReconstruction, |
|
||||
workspace.SelectedLumaCoefficients, |
|
||||
lumaReconstruction, |
|
||||
blockOrigin, |
|
||||
retainedLumaCoefficients[this.codedAreaLuma..], |
|
||||
LumaTransformSize, |
|
||||
selectedLumaState, |
|
||||
ref retainedLumaState); |
|
||||
|
|
||||
if (!this.source.IsMonochrome) |
|
||||
{ |
|
||||
Span<int> retainedBlueCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.U); |
|
||||
Span<int> retainedRedCoefficients = this.coefficientBuffer.GetPlaneSpan(this.superblock.Index, Av1Plane.V); |
|
||||
Span<Av1EncoderTransformBlockState> retainedBlueTransformBlocks = |
|
||||
this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.U); |
|
||||
|
|
||||
Span<Av1EncoderTransformBlockState> retainedRedTransformBlocks = |
|
||||
this.coefficientBuffer.GetTransformBlockSpan(this.superblock.Index, Av1Plane.V); |
|
||||
|
|
||||
int chromaTransformIndex = this.codedAreaChroma / |
|
||||
Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; |
|
||||
|
|
||||
ref Av1EncoderTransformBlockState retainedBlueState = ref retainedBlueTransformBlocks[chromaTransformIndex]; |
|
||||
ref Av1EncoderTransformBlockState retainedRedState = ref retainedRedTransformBlocks[chromaTransformIndex]; |
|
||||
CopyCandidate( |
|
||||
workspace.SelectedBlueReconstruction, |
|
||||
workspace.SelectedBlueCoefficients, |
|
||||
this.reconstruction.GetPlane(Av1Plane.U), |
|
||||
chromaOrigin, |
|
||||
retainedBlueCoefficients[this.codedAreaChroma..], |
|
||||
chromaTransformSize, |
|
||||
selectedBlueState, |
|
||||
ref retainedBlueState); |
|
||||
|
|
||||
CopyCandidate( |
|
||||
workspace.SelectedRedReconstruction, |
|
||||
workspace.SelectedRedCoefficients, |
|
||||
this.reconstruction.GetPlane(Av1Plane.V), |
|
||||
chromaOrigin, |
|
||||
retainedRedCoefficients[this.codedAreaChroma..], |
|
||||
chromaTransformSize, |
|
||||
selectedRedState, |
|
||||
ref retainedRedState); |
|
||||
} |
|
||||
|
|
||||
modeInfo.Block.Mode = Av1PredictionMode.DC; |
|
||||
modeInfo.Block.UvMode = Av1ChromaPredictionMode.DC; |
|
||||
modeInfo.Block.TransformSize = LumaTransformSize; |
|
||||
modeInfo.Block.Skip = selectedSkip; |
|
||||
modeInfo.Block.UseIntraBlockCopy = true; |
|
||||
block.FilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes; |
|
||||
block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] = 0; |
|
||||
block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = 0; |
|
||||
block.PredictionUnit.ChromaFromLumaIndex = 0; |
|
||||
block.PredictionUnit.ChromaFromLumaSigns = 0; |
|
||||
paletteInfo = default; |
|
||||
this.picture.SetDisplacementVector(modeInfoPosition, selectedVector); |
|
||||
return bestCost; |
|
||||
} |
|
||||
|
|
||||
private void EvaluateIntraBlockCopyPlane( |
|
||||
Av1SymbolEncoder writer, |
|
||||
Av1MotionVector vector, |
|
||||
Av1Plane plane, |
|
||||
Av1ComponentType componentType, |
|
||||
Point lumaOrigin, |
|
||||
int subsamplingX, |
|
||||
int subsamplingY, |
|
||||
Av1TransformSize transformSize, |
|
||||
Av1TransformType transformTypeSelection, |
|
||||
Av1TransformBlockContext blockContext, |
|
||||
Span<TSample> prediction, |
|
||||
Span<short> residual, |
|
||||
Span<TSample> transformReconstruction, |
|
||||
Span<int> transformCoefficients, |
|
||||
Span<TSample> selectedReconstruction, |
|
||||
Span<int> selectedCoefficients, |
|
||||
out Av1EncoderTransformBlockState selectedState, |
|
||||
out int selectedRate, |
|
||||
out long selectedDistortion, |
|
||||
out bool hasEmptyTransform, |
|
||||
out Av1EncoderTransformBlockState emptyState, |
|
||||
out long emptyDistortion) |
|
||||
{ |
|
||||
Point planeOrigin = new(lumaOrigin.X >> subsamplingX, lumaOrigin.Y >> subsamplingY); |
|
||||
int sourceColumnQ4 = (planeOrigin.X << 4) + (vector.Column << (1 - subsamplingX)); |
|
||||
int sourceRowQ4 = (planeOrigin.Y << 4) + (vector.Row << (1 - subsamplingY)); |
|
||||
Point predictionOrigin = new(sourceColumnQ4 >> 4, sourceRowQ4 >> 4); |
|
||||
int sampleCount = transformSize.GetSize2d(); |
|
||||
Buffer2DRegion<TSample> sourcePlane = this.source.GetPlane(plane); |
|
||||
Buffer2DRegion<TSample> reconstructionPlane = this.reconstruction.GetPlane(plane); |
|
||||
TOperator.PrepareIntraBlockCopy( |
|
||||
sourcePlane, |
|
||||
planeOrigin, |
|
||||
reconstructionPlane, |
|
||||
predictionOrigin, |
|
||||
(sourceColumnQ4 & 15) != 0, |
|
||||
(sourceRowQ4 & 15) != 0, |
|
||||
prediction[..sampleCount], |
|
||||
residual[..sampleCount], |
|
||||
transformSize); |
|
||||
|
|
||||
// Motion compensation and subtraction do not depend on transform type. Keep them outside the
|
|
||||
// transform loop so exhaustive luma search traverses the source and reference blocks only once.
|
|
||||
Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType( |
|
||||
transformSize, |
|
||||
isInter: true, |
|
||||
this.picture.Parent.FrameHeader.UseReducedTransformSet); |
|
||||
|
|
||||
Av1TransformType firstTransformType = transformTypeSelection == Av1TransformType.AllTransformTypes |
|
||||
? Av1TransformType.DctDct |
|
||||
: transformTypeSelection; |
|
||||
Av1TransformType transformTypeLimit = transformTypeSelection == Av1TransformType.AllTransformTypes |
|
||||
? Av1TransformType.AllTransformTypes |
|
||||
: (Av1TransformType)((int)transformTypeSelection + 1); |
|
||||
|
|
||||
long bestCost = long.MaxValue; |
|
||||
selectedState = default; |
|
||||
selectedRate = 0; |
|
||||
selectedDistortion = 0; |
|
||||
hasEmptyTransform = false; |
|
||||
emptyState = default; |
|
||||
emptyDistortion = 0; |
|
||||
|
|
||||
// The candidate and best spans alternate ownership whenever a transform improves the result.
|
|
||||
// This mirrors the reference's buffer-pointer swap and replaces a copy on every improvement
|
|
||||
// with at most one normalization copy after the transform search.
|
|
||||
Span<TSample> candidateReconstruction = transformReconstruction[..sampleCount]; |
|
||||
Span<int> candidateCoefficients = transformCoefficients[..sampleCount]; |
|
||||
Span<TSample> bestReconstruction = selectedReconstruction[..sampleCount]; |
|
||||
Span<int> bestCoefficients = selectedCoefficients[..sampleCount]; |
|
||||
bool bestUsesSelectedStorage = true; |
|
||||
for (Av1TransformType transformType = firstTransformType; |
|
||||
transformType < transformTypeLimit; |
|
||||
transformType++) |
|
||||
{ |
|
||||
if (!transformType.IsExtendedSetUsed(transformSetType)) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
Av1EncoderTransformBlockState candidateState = default; |
|
||||
long candidateDistortion = TOperator.EncodePredictionCandidate( |
|
||||
this.blockWorkspace, |
|
||||
sourcePlane, |
|
||||
planeOrigin, |
|
||||
prediction[..sampleCount], |
|
||||
residual[..sampleCount], |
|
||||
candidateReconstruction, |
|
||||
transformSize.GetWidth(), |
|
||||
candidateCoefficients, |
|
||||
transformSize, |
|
||||
transformType, |
|
||||
plane, |
|
||||
this.quantization.QIndex[0], |
|
||||
this.quantization.DeltaQDc[(int)plane], |
|
||||
this.quantization.DeltaQAc[(int)plane], |
|
||||
this.bitDepth, |
|
||||
ref candidateState); |
|
||||
|
|
||||
int candidateRate = writer.GetCoefficientCost( |
|
||||
transformSize, |
|
||||
transformType, |
|
||||
Av1PredictionMode.DC, |
|
||||
candidateCoefficients, |
|
||||
componentType, |
|
||||
blockContext, |
|
||||
candidateState.EndOfBlock, |
|
||||
this.picture.Parent.FrameHeader.UseReducedTransformSet, |
|
||||
Av1FilterIntraMode.AllFilterIntraModes, |
|
||||
usesInterTransformSet: true); |
|
||||
|
|
||||
long candidateCost = Av1RateDistortion.GetCost( |
|
||||
this.rateMultiplier, |
|
||||
candidateRate, |
|
||||
candidateDistortion); |
|
||||
|
|
||||
if (candidateCost < bestCost) |
|
||||
{ |
|
||||
Span<TSample> previousBestReconstruction = bestReconstruction; |
|
||||
bestReconstruction = candidateReconstruction; |
|
||||
candidateReconstruction = previousBestReconstruction; |
|
||||
|
|
||||
Span<int> previousBestCoefficients = bestCoefficients; |
|
||||
bestCoefficients = candidateCoefficients; |
|
||||
candidateCoefficients = previousBestCoefficients; |
|
||||
bestUsesSelectedStorage = !bestUsesSelectedStorage; |
|
||||
bestCost = candidateCost; |
|
||||
selectedState = candidateState; |
|
||||
selectedRate = candidateRate; |
|
||||
selectedDistortion = candidateDistortion; |
|
||||
} |
|
||||
|
|
||||
if (candidateState.EndOfBlock == 0 && |
|
||||
(!hasEmptyTransform || candidateDistortion < emptyDistortion)) |
|
||||
{ |
|
||||
hasEmptyTransform = true; |
|
||||
emptyState = candidateState; |
|
||||
emptyDistortion = candidateDistortion; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Callers retain the designated selected spans after this scratch workspace is reused by the
|
|
||||
// next plane or motion vector, so normalize only when the final best result occupies scratch.
|
|
||||
if (!bestUsesSelectedStorage) |
|
||||
{ |
|
||||
bestReconstruction.CopyTo(selectedReconstruction); |
|
||||
bestCoefficients.CopyTo(selectedCoefficients); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
File diff suppressed because it is too large
@ -1,170 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
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.Tiling; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Encodes one range-coded all-intra tile payload.
|
|
||||
/// </summary>
|
|
||||
internal sealed partial class Av1IntraTileWriter : IAv1TileWriter |
|
||||
{ |
|
||||
private readonly ReadOnlyMemory<byte> tileData; |
|
||||
private readonly int tileDataLength; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1IntraTileWriter"/> class for eight-bit samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="writer">The operation-owned symbol encoder that retains the tile output memory.</param>
|
|
||||
/// <param name="source">The coded source frame.</param>
|
|
||||
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
|
||||
/// <param name="picture">The frame coding and mode-information state.</param>
|
|
||||
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
|
||||
/// <param name="superblockWorkspace">The reusable partition and final-block decision workspace.</param>
|
|
||||
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
|
||||
/// <param name="effort">The mode-search effort in the inclusive range zero through ten.</param>
|
|
||||
public Av1IntraTileWriter( |
|
||||
Av1SymbolEncoder writer, |
|
||||
Av1EncoderFrame<byte> source, |
|
||||
Av1EncoderFrame<byte> reconstruction, |
|
||||
Av1PictureControlSet picture, |
|
||||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|
||||
Av1EncoderSuperblockWorkspace superblockWorkspace, |
|
||||
Av1EncoderBlockWorkspace blockWorkspace, |
|
||||
int effort) |
|
||||
{ |
|
||||
this.tileData = Encode<byte, Av1IntraSuperblockEncoder.ByteOperator>( |
|
||||
writer, |
|
||||
source, |
|
||||
reconstruction, |
|
||||
picture, |
|
||||
coefficientBuffer, |
|
||||
superblockWorkspace, |
|
||||
blockWorkspace, |
|
||||
effort, |
|
||||
out this.tileDataLength); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1IntraTileWriter"/> class for high-bit-depth samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="writer">The operation-owned symbol encoder that retains the tile output memory.</param>
|
|
||||
/// <param name="source">The coded source frame.</param>
|
|
||||
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
|
||||
/// <param name="picture">The frame coding and mode-information state.</param>
|
|
||||
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
|
||||
/// <param name="superblockWorkspace">The reusable partition and final-block decision workspace.</param>
|
|
||||
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
|
||||
/// <param name="effort">The mode-search effort in the inclusive range zero through ten.</param>
|
|
||||
public Av1IntraTileWriter( |
|
||||
Av1SymbolEncoder writer, |
|
||||
Av1EncoderFrame<ushort> source, |
|
||||
Av1EncoderFrame<ushort> reconstruction, |
|
||||
Av1PictureControlSet picture, |
|
||||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|
||||
Av1EncoderSuperblockWorkspace superblockWorkspace, |
|
||||
Av1EncoderBlockWorkspace blockWorkspace, |
|
||||
int effort) |
|
||||
{ |
|
||||
this.tileData = Encode<ushort, Av1IntraSuperblockEncoder.UInt16Operator>( |
|
||||
writer, |
|
||||
source, |
|
||||
reconstruction, |
|
||||
picture, |
|
||||
coefficientBuffer, |
|
||||
superblockWorkspace, |
|
||||
blockWorkspace, |
|
||||
effort, |
|
||||
out this.tileDataLength); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public ReadOnlySpan<byte> GetTileData(int tileNum) => this.tileData.Span[..this.tileDataLength]; |
|
||||
|
|
||||
private static ReadOnlyMemory<byte> Encode<TSample, TOperator>( |
|
||||
Av1SymbolEncoder writer, |
|
||||
Av1EncoderFrame<TSample> source, |
|
||||
Av1EncoderFrame<TSample> reconstruction, |
|
||||
Av1PictureControlSet picture, |
|
||||
Av1EncoderCoefficientBuffer coefficientBuffer, |
|
||||
Av1EncoderSuperblockWorkspace superblockWorkspace, |
|
||||
Av1EncoderBlockWorkspace blockWorkspace, |
|
||||
int effort, |
|
||||
out int tileDataLength) |
|
||||
where TSample : unmanaged |
|
||||
where TOperator : struct, Av1IntraSuperblockEncoder.IBlockEncodingOperator<TSample> |
|
||||
{ |
|
||||
ObuFrameHeader frameHeader = picture.Parent.FrameHeader; |
|
||||
ObuSequenceHeader sequenceHeader = picture.Sequence.SequenceHeader; |
|
||||
const ushort TileIndex = 0; |
|
||||
Av1TileInfo tile = new(0, 0, frameHeader); |
|
||||
Av1Superblock superblock = new() |
|
||||
{ |
|
||||
Workspace = superblockWorkspace, |
|
||||
TileInfo = tile |
|
||||
}; |
|
||||
|
|
||||
Point firstModeInfoPosition = new(tile.ModeInfoColumnStart, tile.ModeInfoRowStart); |
|
||||
Av1TileWriter.Av1EntropyCodingContext entropyContext = new() |
|
||||
{ |
|
||||
MacroBlock = new Av1MacroBlockD { Tile = tile }, |
|
||||
MacroBlockModeInfo = picture.GetMacroBlockModeInfo(firstModeInfoPosition) |
|
||||
}; |
|
||||
|
|
||||
int superblockModeInfoSize = sequenceHeader.SuperblockModeInfoSize; |
|
||||
int superblockShift = sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; |
|
||||
if (frameHeader.AllowIntraBlockCopy) |
|
||||
{ |
|
||||
// Hash the visible source once before reconstruction begins so candidate discovery never depends
|
|
||||
// on coding order and the workspace can be reused as compact bucket links afterward.
|
|
||||
picture.IntraBlockCopySearch.Initialize<TSample, TOperator>( |
|
||||
source.View.GetPlane(Av1Plane.Y)); |
|
||||
} |
|
||||
|
|
||||
for (int modeInfoRow = tile.ModeInfoRowStart; |
|
||||
modeInfoRow < tile.ModeInfoRowEnd; |
|
||||
modeInfoRow += superblockModeInfoSize) |
|
||||
{ |
|
||||
for (int modeInfoColumn = tile.ModeInfoColumnStart; |
|
||||
modeInfoColumn < tile.ModeInfoColumnEnd; |
|
||||
modeInfoColumn += superblockModeInfoSize) |
|
||||
{ |
|
||||
int superblockRow = modeInfoRow >> superblockShift; |
|
||||
int superblockColumn = modeInfoColumn >> superblockShift; |
|
||||
superblock.Index = (superblockRow * coefficientBuffer.SuperblockColumnCount) + superblockColumn; |
|
||||
entropyContext.SuperblockOrigin = new Point( |
|
||||
modeInfoColumn << Av1Constants.ModeInfoSizeLog2, |
|
||||
modeInfoRow << Av1Constants.ModeInfoSizeLog2); |
|
||||
|
|
||||
Av1IntraSuperblockEncoder.Prepare( |
|
||||
picture, |
|
||||
superblock, |
|
||||
entropyContext.SuperblockOrigin); |
|
||||
|
|
||||
Av1IntraSuperblockEncoder.ModeDecision<TSample, TOperator> blockEncoder = new( |
|
||||
source, |
|
||||
reconstruction, |
|
||||
picture, |
|
||||
superblock, |
|
||||
coefficientBuffer, |
|
||||
blockWorkspace, |
|
||||
effort); |
|
||||
|
|
||||
Av1TileWriter.WriteSuperblock( |
|
||||
picture, |
|
||||
entropyContext, |
|
||||
writer, |
|
||||
superblock, |
|
||||
coefficientBuffer, |
|
||||
TileIndex, |
|
||||
ref blockEncoder); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return writer.Exit(out tileDataLength); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,383 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
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.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Encodes one range-coded AV1 tile payload.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct Av1TileEncoder : IAv1TileWriter |
||||
|
{ |
||||
|
private readonly ReadOnlyMemory<byte> tileData; |
||||
|
private readonly Av1PictureControlSet picture; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1TileEncoder"/> struct for eight-bit samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The symbol encoder that retains tile output through the enclosing frame write.</param>
|
||||
|
/// <param name="source">The coded source frame.</param>
|
||||
|
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
||||
|
/// <param name="picture">The frame coding and mode-information state.</param>
|
||||
|
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
||||
|
/// <param name="superblockWorkspace">The reusable partition and final-block decision workspace.</param>
|
||||
|
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
||||
|
/// <param name="effort">The mode-search effort in the inclusive range zero through ten.</param>
|
||||
|
public Av1TileEncoder( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1EncoderFrame<byte> source, |
||||
|
Av1EncoderFrame<byte> reconstruction, |
||||
|
Av1PictureControlSet picture, |
||||
|
Av1EncoderCoefficientBuffer coefficientBuffer, |
||||
|
Av1EncoderSuperblockWorkspace superblockWorkspace, |
||||
|
Av1EncoderBlockWorkspace blockWorkspace, |
||||
|
int effort) |
||||
|
{ |
||||
|
this.picture = picture; |
||||
|
this.tileData = Encode<byte, Av1IntraSuperblockEncoder.ByteOperator>( |
||||
|
writer, |
||||
|
source, |
||||
|
reconstruction, |
||||
|
reconstruction, |
||||
|
picture, |
||||
|
coefficientBuffer, |
||||
|
new Av1EncoderTileWorkspace(picture.Parent.FrameHeader, superblockWorkspace), |
||||
|
blockWorkspace, |
||||
|
effort); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1TileEncoder"/> struct for an eight-bit inter frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The symbol encoder that retains tile output through the enclosing frame write.</param>
|
||||
|
/// <param name="source">The coded source frame.</param>
|
||||
|
/// <param name="reference">The reconstructed reference frame.</param>
|
||||
|
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
||||
|
/// <param name="picture">The frame coding and mode-information state.</param>
|
||||
|
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
||||
|
/// <param name="superblockWorkspace">The reusable partition and final-block decision workspace.</param>
|
||||
|
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
||||
|
/// <param name="effort">The mode-search effort in the inclusive range zero through ten.</param>
|
||||
|
public Av1TileEncoder( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1EncoderFrame<byte> source, |
||||
|
Av1EncoderFrame<byte> reference, |
||||
|
Av1EncoderFrame<byte> reconstruction, |
||||
|
Av1PictureControlSet picture, |
||||
|
Av1EncoderCoefficientBuffer coefficientBuffer, |
||||
|
Av1EncoderSuperblockWorkspace superblockWorkspace, |
||||
|
Av1EncoderBlockWorkspace blockWorkspace, |
||||
|
int effort) |
||||
|
{ |
||||
|
this.picture = picture; |
||||
|
this.tileData = Encode<byte, Av1IntraSuperblockEncoder.ByteOperator>( |
||||
|
writer, |
||||
|
source, |
||||
|
reference, |
||||
|
reconstruction, |
||||
|
picture, |
||||
|
coefficientBuffer, |
||||
|
new Av1EncoderTileWorkspace(picture.Parent.FrameHeader, superblockWorkspace), |
||||
|
blockWorkspace, |
||||
|
effort); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1TileEncoder"/> struct for an eight-bit inter frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The symbol encoder that retains tile output through the enclosing frame write.</param>
|
||||
|
/// <param name="source">The coded source frame.</param>
|
||||
|
/// <param name="reference">The reconstructed reference frame.</param>
|
||||
|
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
||||
|
/// <param name="picture">The frame coding and mode-information state.</param>
|
||||
|
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
||||
|
/// <param name="tileWorkspace">The retained tile, superblock, and entropy cursor graph.</param>
|
||||
|
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
||||
|
/// <param name="effort">The mode-search effort in the inclusive range zero through ten.</param>
|
||||
|
public Av1TileEncoder( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1EncoderFrame<byte> source, |
||||
|
Av1EncoderFrame<byte> reference, |
||||
|
Av1EncoderFrame<byte> reconstruction, |
||||
|
Av1PictureControlSet picture, |
||||
|
Av1EncoderCoefficientBuffer coefficientBuffer, |
||||
|
Av1EncoderTileWorkspace tileWorkspace, |
||||
|
Av1EncoderBlockWorkspace blockWorkspace, |
||||
|
int effort) |
||||
|
{ |
||||
|
this.picture = picture; |
||||
|
this.tileData = Encode<byte, Av1IntraSuperblockEncoder.ByteOperator>( |
||||
|
writer, |
||||
|
source, |
||||
|
reference, |
||||
|
reconstruction, |
||||
|
picture, |
||||
|
coefficientBuffer, |
||||
|
tileWorkspace, |
||||
|
blockWorkspace, |
||||
|
effort); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1TileEncoder"/> struct for high-bit-depth samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The symbol encoder that retains tile output through the enclosing frame write.</param>
|
||||
|
/// <param name="source">The coded source frame.</param>
|
||||
|
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
||||
|
/// <param name="picture">The frame coding and mode-information state.</param>
|
||||
|
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
||||
|
/// <param name="superblockWorkspace">The reusable partition and final-block decision workspace.</param>
|
||||
|
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
||||
|
/// <param name="effort">The mode-search effort in the inclusive range zero through ten.</param>
|
||||
|
public Av1TileEncoder( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1EncoderFrame<ushort> source, |
||||
|
Av1EncoderFrame<ushort> reconstruction, |
||||
|
Av1PictureControlSet picture, |
||||
|
Av1EncoderCoefficientBuffer coefficientBuffer, |
||||
|
Av1EncoderSuperblockWorkspace superblockWorkspace, |
||||
|
Av1EncoderBlockWorkspace blockWorkspace, |
||||
|
int effort) |
||||
|
{ |
||||
|
this.picture = picture; |
||||
|
this.tileData = Encode<ushort, Av1IntraSuperblockEncoder.UInt16Operator>( |
||||
|
writer, |
||||
|
source, |
||||
|
reconstruction, |
||||
|
reconstruction, |
||||
|
picture, |
||||
|
coefficientBuffer, |
||||
|
new Av1EncoderTileWorkspace(picture.Parent.FrameHeader, superblockWorkspace), |
||||
|
blockWorkspace, |
||||
|
effort); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1TileEncoder"/> struct for a high-bit-depth inter frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The symbol encoder that retains tile output through the enclosing frame write.</param>
|
||||
|
/// <param name="source">The coded source frame.</param>
|
||||
|
/// <param name="reference">The reconstructed reference frame.</param>
|
||||
|
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
||||
|
/// <param name="picture">The frame coding and mode-information state.</param>
|
||||
|
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
||||
|
/// <param name="superblockWorkspace">The reusable partition and final-block decision workspace.</param>
|
||||
|
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
||||
|
/// <param name="effort">The mode-search effort in the inclusive range zero through ten.</param>
|
||||
|
public Av1TileEncoder( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1EncoderFrame<ushort> source, |
||||
|
Av1EncoderFrame<ushort> reference, |
||||
|
Av1EncoderFrame<ushort> reconstruction, |
||||
|
Av1PictureControlSet picture, |
||||
|
Av1EncoderCoefficientBuffer coefficientBuffer, |
||||
|
Av1EncoderSuperblockWorkspace superblockWorkspace, |
||||
|
Av1EncoderBlockWorkspace blockWorkspace, |
||||
|
int effort) |
||||
|
{ |
||||
|
this.picture = picture; |
||||
|
this.tileData = Encode<ushort, Av1IntraSuperblockEncoder.UInt16Operator>( |
||||
|
writer, |
||||
|
source, |
||||
|
reference, |
||||
|
reconstruction, |
||||
|
picture, |
||||
|
coefficientBuffer, |
||||
|
new Av1EncoderTileWorkspace(picture.Parent.FrameHeader, superblockWorkspace), |
||||
|
blockWorkspace, |
||||
|
effort); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1TileEncoder"/> struct for a high-bit-depth inter frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="writer">The symbol encoder that retains tile output through the enclosing frame write.</param>
|
||||
|
/// <param name="source">The coded source frame.</param>
|
||||
|
/// <param name="reference">The reconstructed reference frame.</param>
|
||||
|
/// <param name="reconstruction">The reconstructed frame updated during encoding.</param>
|
||||
|
/// <param name="picture">The frame coding and mode-information state.</param>
|
||||
|
/// <param name="coefficientBuffer">The frame-owned quantized coefficient and transform state.</param>
|
||||
|
/// <param name="tileWorkspace">The retained tile, superblock, and entropy cursor graph.</param>
|
||||
|
/// <param name="blockWorkspace">The reusable block arithmetic workspace.</param>
|
||||
|
/// <param name="effort">The mode-search effort in the inclusive range zero through ten.</param>
|
||||
|
public Av1TileEncoder( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1EncoderFrame<ushort> source, |
||||
|
Av1EncoderFrame<ushort> reference, |
||||
|
Av1EncoderFrame<ushort> reconstruction, |
||||
|
Av1PictureControlSet picture, |
||||
|
Av1EncoderCoefficientBuffer coefficientBuffer, |
||||
|
Av1EncoderTileWorkspace tileWorkspace, |
||||
|
Av1EncoderBlockWorkspace blockWorkspace, |
||||
|
int effort) |
||||
|
{ |
||||
|
this.picture = picture; |
||||
|
this.tileData = Encode<ushort, Av1IntraSuperblockEncoder.UInt16Operator>( |
||||
|
writer, |
||||
|
source, |
||||
|
reference, |
||||
|
reconstruction, |
||||
|
picture, |
||||
|
coefficientBuffer, |
||||
|
tileWorkspace, |
||||
|
blockWorkspace, |
||||
|
effort); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ReadOnlySpan<byte> GetTileData(int tileNum) |
||||
|
{ |
||||
|
int offset = this.picture.TileDataOffsets.Span[tileNum]; |
||||
|
int length = this.picture.TileDataLengths.Span[tileNum]; |
||||
|
return this.tileData.Span.Slice(offset, length); |
||||
|
} |
||||
|
|
||||
|
private static ReadOnlyMemory<byte> Encode<TSample, TOperator>( |
||||
|
Av1SymbolEncoder writer, |
||||
|
Av1EncoderFrame<TSample> source, |
||||
|
Av1EncoderFrame<TSample> reference, |
||||
|
Av1EncoderFrame<TSample> reconstruction, |
||||
|
Av1PictureControlSet picture, |
||||
|
Av1EncoderCoefficientBuffer coefficientBuffer, |
||||
|
Av1EncoderTileWorkspace tileWorkspace, |
||||
|
Av1EncoderBlockWorkspace blockWorkspace, |
||||
|
int effort) |
||||
|
where TSample : unmanaged |
||||
|
where TOperator : struct, Av1IntraSuperblockEncoder.IBlockEncodingOperator<TSample> |
||||
|
{ |
||||
|
ObuFrameHeader frameHeader = picture.Parent.FrameHeader; |
||||
|
ObuSequenceHeader sequenceHeader = picture.Sequence.SequenceHeader; |
||||
|
Av1TileInfo tile = tileWorkspace.Tile; |
||||
|
Av1Superblock superblock = tileWorkspace.Superblock; |
||||
|
Av1TileWriter.Av1EntropyCodingContext entropyContext = tileWorkspace.EntropyContext; |
||||
|
|
||||
|
int superblockModeInfoSize = sequenceHeader.SuperblockModeInfoSize; |
||||
|
int superblockShift = sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; |
||||
|
ObuTileGroupHeader tileLayout = frameHeader.TilesInfo; |
||||
|
Span<int> tileDataOffsets = picture.TileDataOffsets.Span; |
||||
|
Span<int> tileDataLengths = picture.TileDataLengths.Span; |
||||
|
if (frameHeader.AllowIntraBlockCopy) |
||||
|
{ |
||||
|
// Hash the visible source once before reconstruction begins so candidate discovery never depends
|
||||
|
// on coding order and the workspace can be reused as compact bucket links afterward.
|
||||
|
picture.IntraBlockCopySearch.Initialize<TSample, TOperator>( |
||||
|
source.View.GetPlane(Av1Plane.Y)); |
||||
|
} |
||||
|
|
||||
|
int tileIndex = 0; |
||||
|
int tileDataEnd = 0; |
||||
|
for (int tileRow = 0; tileRow < tileLayout.TileRowCount; tileRow++) |
||||
|
{ |
||||
|
tile.SetTileRow(tileLayout, frameHeader.ModeInfoRowCount, tileRow); |
||||
|
for (int tileColumn = 0; tileColumn < tileLayout.TileColumnCount; tileColumn++) |
||||
|
{ |
||||
|
tile.SetTileColumn(tileLayout, frameHeader.ModeInfoColumnCount, tileColumn); |
||||
|
if (tileIndex > 0) |
||||
|
{ |
||||
|
// Every tile begins from the same frame probabilities, while its bytes follow the preceding
|
||||
|
// tile in the retained output allocation.
|
||||
|
writer.Reset(tileDataEnd); |
||||
|
} |
||||
|
|
||||
|
Point firstModeInfoPosition = new(tile.ModeInfoColumnStart, tile.ModeInfoRowStart); |
||||
|
entropyContext.MacroBlockModeInfo = picture.GetMacroBlockModeInfo(firstModeInfoPosition); |
||||
|
for (int modeInfoRow = tile.ModeInfoRowStart; |
||||
|
modeInfoRow < tile.ModeInfoRowEnd; |
||||
|
modeInfoRow += superblockModeInfoSize) |
||||
|
{ |
||||
|
for (int modeInfoColumn = tile.ModeInfoColumnStart; |
||||
|
modeInfoColumn < tile.ModeInfoColumnEnd; |
||||
|
modeInfoColumn += superblockModeInfoSize) |
||||
|
{ |
||||
|
int superblockRow = modeInfoRow >> superblockShift; |
||||
|
int superblockColumn = modeInfoColumn >> superblockShift; |
||||
|
superblock.Index = (superblockRow * coefficientBuffer.SuperblockColumnCount) + superblockColumn; |
||||
|
entropyContext.SuperblockOrigin = new Point( |
||||
|
modeInfoColumn << Av1Constants.ModeInfoSizeLog2, |
||||
|
modeInfoRow << Av1Constants.ModeInfoSizeLog2); |
||||
|
|
||||
|
Av1IntraSuperblockEncoder.Prepare( |
||||
|
picture, |
||||
|
superblock, |
||||
|
entropyContext.SuperblockOrigin); |
||||
|
|
||||
|
Av1IntraSuperblockEncoder.ModeDecision<TSample, TOperator> blockEncoder = new( |
||||
|
source, |
||||
|
reference, |
||||
|
reconstruction, |
||||
|
picture, |
||||
|
superblock, |
||||
|
coefficientBuffer, |
||||
|
blockWorkspace, |
||||
|
effort); |
||||
|
|
||||
|
Av1TileWriter.WriteSuperblock( |
||||
|
picture, |
||||
|
entropyContext, |
||||
|
writer, |
||||
|
superblock, |
||||
|
coefficientBuffer, |
||||
|
(ushort)tileIndex, |
||||
|
ref blockEncoder); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_ = writer.Exit(out int tileDataLength); |
||||
|
tileDataOffsets[tileIndex] = tileDataEnd; |
||||
|
tileDataLengths[tileIndex] = tileDataLength; |
||||
|
tileDataEnd += tileDataLength; |
||||
|
tileIndex++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return writer.GetOutput(tileDataEnd); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Retains the mutable tile, superblock, and entropy cursor graph reused by serial frame encoding.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct Av1EncoderTileWorkspace |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1EncoderTileWorkspace"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="frameHeader">The fixed-geometry frame header defining tile boundaries.</param>
|
||||
|
/// <param name="superblockWorkspace">The retained superblock decision storage.</param>
|
||||
|
public Av1EncoderTileWorkspace( |
||||
|
ObuFrameHeader frameHeader, |
||||
|
Av1EncoderSuperblockWorkspace superblockWorkspace) |
||||
|
{ |
||||
|
this.Tile = new Av1TileInfo(0, 0, frameHeader); |
||||
|
this.Superblock = new Av1Superblock |
||||
|
{ |
||||
|
Workspace = superblockWorkspace, |
||||
|
TileInfo = this.Tile |
||||
|
}; |
||||
|
|
||||
|
this.EntropyContext = new Av1TileWriter.Av1EntropyCodingContext |
||||
|
{ |
||||
|
MacroBlock = new Av1MacroBlockD { Tile = this.Tile }, |
||||
|
MacroBlockModeInfo = default |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the mutable tile boundaries selected during raster traversal.
|
||||
|
/// </summary>
|
||||
|
public Av1TileInfo Tile { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the mutable superblock cursor connected to the retained decision workspace.
|
||||
|
/// </summary>
|
||||
|
public Av1Superblock Superblock { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the mutable entropy cursor shared by successive superblocks.
|
||||
|
/// </summary>
|
||||
|
public Av1TileWriter.Av1EntropyCodingContext EntropyContext { get; } |
||||
|
} |
||||
Loading…
Reference in new issue