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