From 8a4ec3e07ee617ce0d8799463f978fe5b8a89a6c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 2 Sep 2026 13:12:18 +1000 Subject: [PATCH] Fix AV1 encoder partition and transform traversal --- HEIF_IMPLEMENTATION_PLAN.md | 5 +- .../Formats/Heif/Av1/Av1Constants.cs | 10 - .../Heif/Av1/ModeDecision/Av1BlockGeometry.cs | 163 --- .../ModeDecision/Av1BlockGeometryFactory.cs | 1050 --------------- .../Heif/Av1/ModeDecision/Av1GeometryIndex.cs | 50 - .../Av1/Tiling/Av1EncoderBlockModeInfo.cs | 5 +- .../Heif/Av1/Tiling/Av1EncoderBlockStruct.cs | 24 +- .../Av1/Tiling/Av1EncoderCoefficientBuffer.cs | 61 +- .../Tiling/Av1EncoderTransformBlockState.cs | 42 + .../Av1/Tiling/Av1EntropyCodingContext.cs | 5 + .../Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs | 10 - .../Heif/Av1/Tiling/Av1PictureControlSet.cs | 4 +- .../Av1/Tiling/Av1PictureParentControlSet.cs | 15 - .../Formats/Heif/Av1/Tiling/Av1Superblock.cs | 2 - .../Heif/Av1/Tiling/Av1SuperblockGeometry.cs | 15 - .../Formats/Heif/Av1/Tiling/Av1TileWriter.cs | 1153 ++++++++++------- .../Heif/Av1/Tiling/Av1TransformUnit.cs | 54 - .../Heif/Av1/Av1CoefficientsEntropyTests.cs | 428 +++++- 18 files changed, 1235 insertions(+), 1861 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometry.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1GeometryIndex.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderTransformBlockState.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 29faf78e9e..a4e570b0da 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -822,8 +822,9 @@ Encoder verification contract: - [ ] Implement real rate-distortion selection and make quality and effort change work, size, and output quality. - [~] The tile writer now publishes one packed coefficient context per covered 4x4 edge unit and derives luma/chroma skip plus DC-sign contexts from the complete transform edges using current-libaom units. Complete tile traversal, initialized picture state, and verified CDF update behavior remain. - [~] Encoder mode information now uses a frame-owned reference grid over its contiguous allocation, matching current libaom's `mi_grid_base` and `mi_alloc` ownership without per-block tail copies. Signed relative neighbor lookup, 4x4-unit addressing, and mutable selected skip syntax have focused contracts; complete mode decision still remains. -- [~] Per-block transform, palette-size, and prediction syntax now uses fixed inline storage matching libaom's embedded block state. All 16 transform entries are immediately usable, directional deltas retain their signed range, and traversal performs no managed allocation; picture-level block storage and complete decision state remain. -- [~] Finalized transform coefficients now use raster-ordered, per-superblock plane segments matching current libaom's frame coefficient-pool geometry. ImageSharp may segment the frame owner between superblock rows, avoiding decoder borders and giant contiguous rentals while preserving disjoint luma/chroma ranges; the forward transform and mode-decision stages still need to populate this owner. +- [~] Final block decisions now use contiguous value storage with palette and prediction syntax inline. Macroblock edge and neighbor state is reused by the entropy-coding operation instead of allocating one managed object per final block; directional deltas retain their signed range, while complete mode decision still remains. +- [~] Finalized transform coefficients and packed EOB/type state now use raster-ordered, per-superblock plane segments matching current libaom's coefficient-pool geometry. One ImageSharp allocator owner replaces libaom's separate coefficient, EOB, and entropy-context allocations while preserving the full 1024 luma and 256-per-chroma 4x4 state capacity of a 128x128 4:2:0 superblock; the forward transform and mode-decision stages still need to populate this owner. +- [~] Tile partition writing now follows current libaom's recursive `write_modes_sb` preorder traversal and `update_ext_partition_context` edge updates directly. The obsolete SVT-derived global geometry catalog and its unimplemented lookup are removed; transform geometry is derived in libaom's bounded 64x64 residual order from the selected block transform size, frame-edge and segmentation syntax use mode-information units, and 128x128 CDEF units use libaom's 0-to-3 indexing and first-block strength ownership. Partition analysis and selected-transform syntax still need to populate and encode these retained decisions. - [ ] Implement legal deblocking, CDEF, restoration, super-resolution, and film-grain signaling decisions. - [~] The coefficient symbol encoder now reuses tile-lifetime level and context workspaces instead of allocating per transform. Every remaining encoder fragment must be audited before it becomes active. - [~] The planar conversion, forward transform, and forward quantizer use descending SIMD dispatch: Vector512, Vector256, Vector128, then scalar. Apply the same rule to every later hot-path family. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 72822fbe0d..15609fc2db 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -286,21 +286,11 @@ internal static class Av1Constants /// public const int MaxVarTransform = 2; - /// - /// The maximum number of transform blocks at one depth. - /// - public const int MaxTransformBlockCount = 16; - /// /// Number of items in the enumeration. /// public const int PlaneTypeCount = 2; - /// - /// The maximum number of transform units stored for one encoded block. - /// - public const int MaxTransformUnitCount = 16; - /// /// Gets the number of payload bits used by each segmentation feature. /// diff --git a/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometry.cs b/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometry.cs deleted file mode 100644 index 89ffd922bc..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometry.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision; - -/// -/// Describes the spatial, chroma, and transform layout of one AV1 block considered by mode-decision scanning. -/// -internal class Av1BlockGeometry -{ - /// - /// The luma block size from which the cached luma dimensions are derived. - /// - private Av1BlockSize blockSize; - - /// - /// The chroma block size from which the cached chroma dimensions are derived. - /// - private Av1BlockSize blockSizeUv; - - /// - /// Initializes a new instance of the class with storage for every supported transform depth. - /// - public Av1BlockGeometry() - { - this.RedunancyList = []; - this.TransformOrigin = new Point[Av1Constants.MaxVarTransform + 1][]; - for (int i = 0; i < this.TransformOrigin.Length; i++) - { - this.TransformOrigin[i] = new Point[Av1Constants.MaxTransformBlockCount]; - } - } - - /// - /// Gets or sets the luma block size and updates and to match. - /// - public Av1BlockSize BlockSize - { - get => this.blockSize; - set - { - this.blockSize = value; - this.BlockWidth = value.GetWidth(); - this.BlockHeight = value.GetHeight(); - } - } - - /// - /// Gets or sets the chroma block size and updates and to match. - /// - public Av1BlockSize BlockSizeUv - { - get => this.blockSizeUv; - set - { - this.blockSizeUv = value; - this.BlockWidthUv = value.GetWidth(); - this.BlockHeightUv = value.GetHeight(); - } - } - - /// - /// Gets or sets the block origin in pixels relative to the top-left corner of its superblock. - /// - public Point Origin { get; set; } - - /// - /// Gets or sets a value indicating whether this luma block owns chroma samples in the mode-decision layout. - /// - public bool HasUv { get; set; } - - /// - /// Gets the luma block width in pixels. - /// - public int BlockWidth { get; private set; } - - /// - /// Gets the luma block height in pixels. - /// - public int BlockHeight { get; private set; } - - /// - /// Gets the number of luma transform blocks at each transform depth. - /// - public int[] TransformBlockCount { get; } = new int[Av1Constants.MaxVarTransform + 1]; - - /// - /// Gets the luma transform size selected at each transform depth. - /// - public Av1TransformSize[] TransformSize { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; - - /// - /// Gets the chroma transform size selected at each transform depth. - /// - public Av1TransformSize[] TransformSizeUv { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; - - /// - /// Gets the pixel origins of the transform blocks at each transform depth. - /// - public Point[][] TransformOrigin { get; private set; } - - /// - /// Gets or sets the block index in mode-decision scan order. - /// - public int ModeDecisionIndex { get; set; } - - /// - /// Gets or sets the scan offset from this square block to the next block at the same depth. - /// - public int NextDepthOffset { get; set; } - - /// - /// Gets or sets the scan offset from this square block to its first child at the next depth. - /// - public int Depth1Offset { get; set; } - - /// - /// Gets a value indicating whether this block is redundant to another. - /// - public bool IsRedundant => this.RedunancyList.Count > 0; - - /// - /// Gets or sets the mode-decision indices of blocks with the same size and origin as this block. - /// - public List RedunancyList { get; set; } - - /// - /// Gets or sets the zero-based component index of this block within a non-square partition. - /// - public int NonSquareIndex { get; set; } - - /// - /// Gets or sets the number of component blocks produced by this partition shape. - /// - public int TotalNonSuareCount { get; set; } - - /// - /// Gets the chroma block width in pixels. - /// - public int BlockWidthUv { get; private set; } - - /// - /// Gets the chroma block height in pixels. - /// - public int BlockHeightUv { get; private set; } - - /// - /// Gets or sets the quadtree depth of this block within its superblock. - /// - public int Depth { get; set; } - - /// - /// Gets or sets the width and height, in pixels, of the square sequence region that produced this block. - /// - public int SequenceSize { get; set; } - - /// - /// Gets or sets a value indicating whether this block belongs to the last quadrant of its parent. - /// - public bool IsLastQuadrant { get; set; } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs b/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs deleted file mode 100644 index 107fb02f6c..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs +++ /dev/null @@ -1,1050 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision; - -/// -/// Builds the AV1 block and transform geometries traversed by the mode-decision scan. -/// -internal class Av1BlockGeometryFactory -{ - /// - /// The number of scan entries required by the largest supported 128-pixel superblock geometry. - /// - private const int MaxBlocksAllocated = 4421; - - /// - /// Marks a geometry-depth combination that has no valid scan offset. - /// - private const int NotUsedValue = 0; - - /// - /// Maps each partition shape, axis, and component to its origin offset measured in quarter-block units. - /// - private static readonly int[][][] NonSkipQuarterOffMult = - [ - - // 9 means not used. - // | x | | y | - /*P=0*/ [[0, 9, 9, 9], [0, 9, 9, 9]], - /*P=1*/ [[0, 0, 9, 9], [0, 2, 9, 9]], - /*P=2*/ [[0, 2, 9, 9], [0, 0, 9, 9]], - - /*P=7*/ [[0, 0, 0, 0], [0, 1, 2, 3]], - /*P=8*/ [[0, 1, 2, 3], [0, 0, 0, 0]], - - /*P=3*/ [[0, 2, 0, 9], [0, 0, 2, 9]], - /*P=4*/ [[0, 0, 2, 9], [0, 2, 2, 9]], - /*P=5*/ [[0, 0, 2, 9], [0, 2, 0, 9]], - /*P=6*/ [[0, 2, 2, 9], [0, 0, 2, 9]] - ]; - - /// - /// Maps each partition shape, axis, and component to its dimension measured in quarter-block units. - /// - private static readonly uint[][][] NonSkipSizeMult = - [ - - // 9 means not used. - // | h | | v | - /*P=0*/ [[4, 9, 9, 9], [4, 9, 9, 9]], - /*P=1*/ [[4, 4, 9, 9], [2, 2, 9, 9]], - /*P=2*/ [[2, 2, 9, 9], [4, 4, 9, 9]], - - /*P=7*/ [[4, 4, 4, 4], [1, 1, 1, 1]], - /*P=8*/ [[1, 1, 1, 1], [4, 4, 4, 4]], - - /*P=3*/ [[2, 2, 4, 9], [2, 2, 2, 9]], - /*P=4*/ [[4, 2, 2, 9], [2, 2, 2, 9]], - /*P=5*/ [[2, 2, 2, 9], [2, 2, 4, 9]], - /*P=6*/ [[2, 2, 2, 9], [4, 2, 2, 9]] - ]; - - /// - /// Maps geometry and quadtree depth to the scan offset of the next quadrant at that depth. - /// - private static readonly int[][] NonSkipDepthOffset = - [ - [85, 21, 5, 1, NotUsedValue, NotUsedValue], - [105, 25, 5, 1, NotUsedValue, NotUsedValue], - [169, 41, 9, 1, NotUsedValue, NotUsedValue], - [425, 105, 25, 5, NotUsedValue, NotUsedValue], - [681, 169, 41, 9, 1, NotUsedValue], - [849, 209, 49, 9, 1, NotUsedValue], - [1101, 269, 61, 9, 1, NotUsedValue], - [4421, 1101, 269, 61, 9, 1], - [2377, 593, 145, 33, 5, NotUsedValue] - ]; - - /// - /// Maps geometry and quadtree depth to the scan offset of the square block's first child. - /// - private static readonly int[][] Depth1DepthOffset = - [ - [1, 1, 1, 1, 1, NotUsedValue], - [5, 5, 1, 1, 1, NotUsedValue], - [5, 5, 5, 1, 1, NotUsedValue], - [5, 5, 5, 5, 1, NotUsedValue], - [5, 5, 5, 5, 1, NotUsedValue], - [13, 13, 13, 5, 1, NotUsedValue], - [25, 25, 25, 5, 1, NotUsedValue], - [17, 25, 25, 25, 5, 1], - [5, 13, 13, 13, 5, NotUsedValue] - ]; - - /// - /// The geometry whose lookup-table row is active while a scan is constructed. - /// - private static Av1GeometryIndex geometryIndex; - - /// - /// The active geometry's superblock width and height in pixels. - /// - private static int maxSuperblock; - - /// - /// The number of quadtree depths generated for the active geometry. - /// - private static int maxDepth; - - /// - /// The number of partition shapes considered by the active geometry before size-specific restrictions. - /// - private static int maxPart; - - // private static int maxActiveBlockCount; - - /// - /// Stores block geometries by mode-decision scan index. - /// - private readonly Av1BlockGeometry[] blockGeometryModeDecisionScan; - - /// - /// Initializes a new instance of the class. - /// - /// The predefined geometry used to size and populate the mode-decision scan. - public Av1BlockGeometryFactory(Av1GeometryIndex geom) - { - this.blockGeometryModeDecisionScan = new Av1BlockGeometry[MaxBlocksAllocated]; - int max_block_count; - geometryIndex = geom; - byte min_nsq_bsize; - - // These preset limits and the enum order form the row index contract for the offset tables above. - // Changing one without the other would make parent and sibling scan offsets refer to a different geometry. - if (geom == Av1GeometryIndex.Geometry0) - { - maxSuperblock = 64; - maxDepth = 4; - maxPart = 1; - max_block_count = 85; - min_nsq_bsize = 16; - } - else if (geom == Av1GeometryIndex.Geometry1) - { - maxSuperblock = 64; - maxDepth = 4; - maxPart = 3; - max_block_count = 105; - min_nsq_bsize = 16; - } - else if (geom == Av1GeometryIndex.Geometry2) - { - maxSuperblock = 64; - maxDepth = 4; - maxPart = 3; - max_block_count = 169; - min_nsq_bsize = 8; - } - else if (geom == Av1GeometryIndex.Geometry3) - { - maxSuperblock = 64; - maxDepth = 4; - maxPart = 3; - max_block_count = 425; - min_nsq_bsize = 0; - } - else if (geom == Av1GeometryIndex.Geometry4) - { - maxSuperblock = 64; - maxDepth = 5; - maxPart = 3; - max_block_count = 681; - min_nsq_bsize = 0; - } - else if (geom == Av1GeometryIndex.Geometry5) - { - maxSuperblock = 64; - maxDepth = 5; - maxPart = 5; - max_block_count = 849; - min_nsq_bsize = 0; - } - else if (geom == Av1GeometryIndex.Geometry6) - { - maxSuperblock = 64; - maxDepth = 5; - maxPart = 9; - max_block_count = 1101; - min_nsq_bsize = 0; - } - else if (geom == Av1GeometryIndex.Geometry7) - { - maxSuperblock = 128; - maxDepth = 6; - maxPart = 9; - max_block_count = 4421; - min_nsq_bsize = 0; - } - else - { - maxSuperblock = 128; - maxDepth = 5; - maxPart = 5; - max_block_count = 2377; - min_nsq_bsize = 0; - } - - // (0)compute total number of blocks using the information provided - // maxActiveBlockCount = CountTotalNumberOfActiveBlocks(min_nsq_bsize); - - // (2) Construct md scan blk_geom_mds: use info from dps - int idx_mds = 0; - this.ScanAllBlocks(ref idx_mds, maxSuperblock, 0, 0, false, 0, min_nsq_bsize); - LogRedundancySimilarity(max_block_count); - } - - /// - /// Counts the block entries produced by every enabled partition at every depth of the active geometry. - /// - /// The smallest square size, in pixels, at which non-square partitions remain enabled. - /// The number of active mode-decision scan entries. - private static int CountTotalNumberOfActiveBlocks(int min_nsq_bsize) - { - int depth_scan_idx = 0; - - for (int depthIterator = 0; depthIterator < maxDepth; depthIterator++) - { - int totalSquareCount = 1 << depthIterator; - - // Each quadtree depth halves the square sequence dimension. The final branch covers the deepest - // 128-pixel-superblock geometry, whose sixth level contains 4-pixel squares. - int sequenceSize = depthIterator == 0 ? maxSuperblock - : depthIterator == 1 ? maxSuperblock / 2 - : depthIterator == 2 ? maxSuperblock / 4 - : depthIterator == 3 ? maxSuperblock / 8 - : depthIterator == 4 ? maxSuperblock / 16 : maxSuperblock / 32; - - // AV1 restricts the partition shapes allowed at the largest and smallest block sizes. Apply those - // caps before walking the shape table so a row is never interpreted for an illegal block size. - int max_part_updated = sequenceSize == 128 ? Math.Min(maxPart, maxPart < 9 && maxPart > 3 ? 3 : 7) - : sequenceSize == 8 ? Math.Min(maxPart, 3) - : sequenceSize == 4 ? 1 : maxPart; - if (sequenceSize <= min_nsq_bsize) - { - max_part_updated = 1; - } - - for (int squareIteratorY = 0; squareIteratorY < totalSquareCount; squareIteratorY++) - { - for (int squareIteratorX = 0; squareIteratorX < totalSquareCount; squareIteratorX++) - { - for (int partitionIterator = 0; partitionIterator < max_part_updated; partitionIterator++) - { - int tot_num_ns_per_part = GetNonSquareCountPerPart(partitionIterator, sequenceSize); - depth_scan_idx += tot_num_ns_per_part; - } - } - } - } - - return depth_scan_idx; - } - - /// - /// Gets the number of component blocks emitted by one partition shape. - /// - /// The zero-based partition-shape index in scan order. - /// The width and height, in pixels, of the square being partitioned. - /// The number of component blocks in the partition. - private static int GetNonSquareCountPerPart(int partitionIterator, int sequenceSize) - { - int tot_num_ns_per_part = partitionIterator < 1 ? 1 : partitionIterator < 3 ? 2 : partitionIterator < 5 && sequenceSize < 128 ? 4 : 3; - return tot_num_ns_per_part; - } - - /// - /// Records scan entries that represent the same block size at the same pixel origin. - /// - /// The number of populated scan entries to compare. - private static void LogRedundancySimilarity(int max_block_count) - { - for (int blockIterator = 0; blockIterator < max_block_count; blockIterator++) - { - Av1BlockGeometry cur_geom = GetBlockGeometryByModeDecisionScanIndex(blockIterator); - cur_geom.RedunancyList.Clear(); - - for (int searchIterator = 0; searchIterator < max_block_count; searchIterator++) - { - Av1BlockGeometry search_geom = GetBlockGeometryByModeDecisionScanIndex(searchIterator); - - if (cur_geom.BlockSize == search_geom.BlockSize && - cur_geom.Origin == search_geom.Origin && - searchIterator != blockIterator) - { - if (cur_geom.NonSquareIndex == 0 && search_geom.NonSquareIndex == 0 && cur_geom.RedunancyList.Count < 3) - { - cur_geom.RedunancyList.Add(search_geom.ModeDecisionIndex); - } - } - } - } - } - - /// - /// Gets the block geometry at a mode-decision scan index. - /// - /// The zero-based mode-decision scan index. - /// The geometry stored at . - /// Always thrown because the geometry lookup has not been implemented. - public static Av1BlockGeometry GetBlockGeometryByModeDecisionScanIndex(int modeDecisionScanIndex) => throw new NotImplementedException(); - - /// - /// Appends every enabled partition and transform layout for a square region to scan order. - /// - /// The next scan index; advanced once for every emitted block geometry. - /// The width and height, in pixels, of the square region being partitioned. - /// The region's horizontal origin in pixels relative to the superblock. - /// The region's vertical origin in pixels relative to the superblock. - /// Whether the region is the final quadrant of its parent. - /// The zero-based quadrant index within the parent. - /// The smallest square size, in pixels, at which non-square partitions remain enabled. - private void ScanAllBlocks(ref int index, int sequenceSize, int x, int y, bool isLastQuadrant, byte quadIterator, byte minNonSquareBlockSize) - { - // The input block is the parent square block of size sq_size located at pos (x,y) - Guard.MustBeLessThanOrEqualTo(quadIterator, (byte)3, nameof(quadIterator)); - - int halfsize = sequenceSize / 2; - int quartsize = sequenceSize / 4; - - // AV1 removes partition shapes that cannot be represented at 128-, 8-, and 4-pixel square sizes. - // The scan tables are ordered by the remaining shape set, so the cap must be applied before indexing them. - int max_part_updated = sequenceSize == 128 ? Math.Min(maxPart, maxPart is < 9 and > 3 ? 3 : 7) - : sequenceSize == 8 ? Math.Min(maxPart, 3) - : sequenceSize == 4 ? 1 : maxPart; - if (sequenceSize <= minNonSquareBlockSize) - { - max_part_updated = 1; - } - - int sqi_mds = index; - - for (int partitionIterator = 0; partitionIterator < max_part_updated; partitionIterator++) - { - int tot_num_ns_per_part = GetNonSquareCountPerPart(partitionIterator, sequenceSize); - - for (int nonSquareIterator = 0; nonSquareIterator < tot_num_ns_per_part; nonSquareIterator++) - { - // Geometry presets use power-of-two superblocks, so the current square dimension uniquely identifies - // its quadtree depth without carrying recursion state in every scan entry. - this.blockGeometryModeDecisionScan[index].Depth = sequenceSize == maxSuperblock / 1 ? 0 - : sequenceSize == maxSuperblock / 2 ? 1 - : sequenceSize == maxSuperblock / 4 ? 2 - : sequenceSize == maxSuperblock / 8 ? 3 - : sequenceSize == maxSuperblock / 16 ? 4 : 5; - - this.blockGeometryModeDecisionScan[index].SequenceSize = sequenceSize; - this.blockGeometryModeDecisionScan[index].IsLastQuadrant = isLastQuadrant; - - // part_it >= 3 for 128x128 blocks corresponds to HA/HB/VA/VB shapes since H4/V4 are not allowed - // for 128x128 blocks. Therefore, need to offset part_it by 2 to not index H4/V4 shapes. - int part_it_idx = partitionIterator >= 3 && sequenceSize == 128 ? partitionIterator + 2 : partitionIterator; - this.blockGeometryModeDecisionScan[index].Origin = new Point( - x + (quartsize * NonSkipQuarterOffMult[part_it_idx][0][nonSquareIterator]), - y + (quartsize * NonSkipQuarterOffMult[part_it_idx][1][nonSquareIterator])); - - // These properties aren't used. - // this.blockGeometryModeDecisionScan[index].Shape = (Part)part_it_idx; - // this.blockGeometryModeDecisionScan[index].QuadIndex = quadIterator; - // this.blockGeometryModeDecisionScan[index].d1i = depth1Iterator++; - // this.blockGeometryModeDecisionScan[index].sqi_mds = sqi_mds; - this.blockGeometryModeDecisionScan[index].Depth1Offset = - Depth1DepthOffset[(int)geometryIndex][this.blockGeometryModeDecisionScan[index].Depth]; - this.blockGeometryModeDecisionScan[index].NextDepthOffset = - NonSkipDepthOffset[(int)geometryIndex][this.blockGeometryModeDecisionScan[index].Depth]; - this.blockGeometryModeDecisionScan[index].TotalNonSuareCount = tot_num_ns_per_part; - this.blockGeometryModeDecisionScan[index].NonSquareIndex = nonSquareIterator; - uint blockWidth = (uint)quartsize * NonSkipSizeMult[part_it_idx][0][nonSquareIterator]; - uint blockHeight = (uint)quartsize * NonSkipSizeMult[part_it_idx][1][nonSquareIterator]; - - // Av1BlockSize indexes dimensions by log2(size) - 2 because 4x4 is the smallest coded block. - this.blockGeometryModeDecisionScan[index].BlockSize = - Av1BlockSizeExtensions.FromWidthAndHeight(Av1Math.Log2_32(blockWidth) - 2u, Av1Math.Log2_32(blockHeight) - 2u); - this.blockGeometryModeDecisionScan[index].BlockSizeUv = this.blockGeometryModeDecisionScan[index].BlockSize.GetSubsampled(true, true); - - // this.blockGeometryModeDecisionScan[index].BlockWidthUv = Math.Max(4, this.blockGeometryModeDecisionScan[index].BlockWidth >> 1); - // this.blockGeometryModeDecisionScan[index].BlockHeightUv = Math.Max(4, this.blockGeometryModeDecisionScan[index].BlockHeight >> 1); - this.blockGeometryModeDecisionScan[index].HasUv = true; - - // Chroma cannot be subdivided below its minimum block dimensions. When several luma blocks map to - // the same chroma block, only the final contributing luma component owns that shared U/V geometry. - if (this.blockGeometryModeDecisionScan[index].BlockWidth == 4 && this.blockGeometryModeDecisionScan[index].BlockHeight == 4) - { - this.blockGeometryModeDecisionScan[index].HasUv = isLastQuadrant; - } - else if ((this.blockGeometryModeDecisionScan[index].BlockWidth >> 1) < this.blockGeometryModeDecisionScan[index].BlockWidthUv || - (this.blockGeometryModeDecisionScan[index].BlockHeight >> 1) < this.blockGeometryModeDecisionScan[index].BlockHeightUv) - { - int num_blk_same_uv = 1; - if (this.blockGeometryModeDecisionScan[index].BlockWidth >> 1 < 4) - { - num_blk_same_uv *= 2; - } - - if (this.blockGeometryModeDecisionScan[index].BlockHeight >> 1 < 4) - { - num_blk_same_uv *= 2; - } - - // if (this.blockGeometryModeDecisionScan[index].nsi % 2 == 0) - // if (this.blockGeometryModeDecisionScan[index].nsi != (this.blockGeometryModeDecisionScan[index].totns-1) ) - if (this.blockGeometryModeDecisionScan[index].NonSquareIndex != (num_blk_same_uv - 1) && - this.blockGeometryModeDecisionScan[index].NonSquareIndex != ((2 * num_blk_same_uv) - 1)) - { - this.blockGeometryModeDecisionScan[index].HasUv = false; - } - } - - // Transform depth zero keeps the largest legal transform. Blocks larger than AV1's 64x64 transform - // limit are represented by two or four transform blocks whose origins cover the coded block. - int tx_depth = 0; - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128 - ? 4 - : this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block128x64 or Av1BlockSize.Block64x128 - ? 2 - : 1; - for (int transformBlockIterator = 0; transformBlockIterator < this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth]; transformBlockIterator++) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = - GetTransformSize(this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - GetTransformSize(this.blockGeometryModeDecisionScan[index].BlockSize, 1); - if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128) - { - int offsetx = (transformBlockIterator is 0 or 2) ? 0 : 64; - int offsety = (transformBlockIterator is 0 or 1) ? 0 : 64; - Size offset = new(offsetx, offsety); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x64) - { - int offsetx = (transformBlockIterator == 0) ? 0 : 64; - int offsety = 0; - Size offset = new(offsetx, offsety); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x128) - { - int offsetx = 0; - int offsety = (transformBlockIterator == 0) ? 0 : 64; - Size offset = new(offsetx, offsety); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else - { - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin; - } - } - - // Transform depth one subdivides eligible luma blocks once while chroma retains its depth-zero size. - // The block-count cases below mirror the legal rectangular AV1 transform partitions. - tx_depth = 1; - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128 - ? 4 - : this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block128x64 or Av1BlockSize.Block64x128 - ? 2 - : 1; - - if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x64 or - Av1BlockSize.Block32x32 or - Av1BlockSize.Block16x16 or - Av1BlockSize.Block8x8) - { - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 4; - } - - if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x32 or - Av1BlockSize.Block32x64 or - Av1BlockSize.Block32x16 or - Av1BlockSize.Block16x32 or - Av1BlockSize.Block16x8 or - Av1BlockSize.Block8x16) - { - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 2; - } - - if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x16 or - Av1BlockSize.Block16x64 or - Av1BlockSize.Block32x8 or - Av1BlockSize.Block8x32 or - Av1BlockSize.Block16x4 or - Av1BlockSize.Block4x16) - { - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 2; - } - - for (int transformBlockIterator = 0; transformBlockIterator < this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth]; transformBlockIterator++) - { - if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x64) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block32x32, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 32, 0, 32]; - int[] offsety = [0, 0, 32, 32]; - - // 0 1 - // 2 3 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x32) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block32x32, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 32]; - int[] offsety = [0, 0]; - - // 0 1 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x64) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block32x32, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 0]; - int[] offsety = [0, 32]; - - // 0 1 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x32) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 16, 0, 16]; - int[] offsety = [0, 0, 16, 16]; - - // 0 1 - // 2 3 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 16]; - int[] offsety = [0, 0]; - - // 0 1 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x32) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 0]; - int[] offsety = [0, 16]; - - // 0 1 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 8, 0, 8]; - int[] offsety = [0, 0, 8, 8]; - - // 0 1 - // 2 3 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x8) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 8]; - int[] offsety = [0, 0]; - - // 0 1 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 0]; - int[] offsety = [0, 8]; - - // 0 1 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x8) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int[] offsetx = [0, 4, 0, 4]; - int[] offsety = [0, 0, 4, 4]; - - // 0 1 - // 2 3 - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block32x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx = [0, 32]; - int[] offsety = [0, 0]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x64) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x32, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx = [0, 0]; - int[] offsety = [0, 32]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x8) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx = [0, 16]; - int[] offsety = [0, 0]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x32) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - // 0 1 2 3 - int[] offsetx = [0, 0]; - int[] offsety = [0, 16]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x4) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x4, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx = [0, 8]; - int[] offsety = [0, 0]; - - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block4x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx = [0, 0]; - int[] offsety = [0, 8]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else - { - if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize( - this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int offsetx = (transformBlockIterator is 0 or 2) ? 0 : 64; - int offsety = (transformBlockIterator is 0 or 1) ? 0 : 64; - Size offset = new(offsetx, offsety); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x64) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize( - this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int offsetx = (transformBlockIterator is 0) ? 0 : 64; - int offsety = 0; - Size offset = new(offsetx, offsety); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x128) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize( - this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int offsetx = 0; - int offsety = (transformBlockIterator is 0) ? 0 : 64; - Size offset = new(offsetx, offsety); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize( - this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin; - } - } - - /*this.blockGeometryModeDecisionScan[index].tx_width[tx_depth] = - tx_size_wide[this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth]]; - this.blockGeometryModeDecisionScan[index].tx_height[tx_depth] = - tx_size_high[this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth]]; - this.blockGeometryModeDecisionScan[index].tx_width_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_width_uv[0]; - this.blockGeometryModeDecisionScan[index].tx_height_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_height_uv[0];*/ - } - - // Transform depth two performs a second subdivision. The origin tables enumerate the child - // transforms in raster order so coefficient reconstruction visits the same spatial layout. - tx_depth = 2; - - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128 - ? 4 - : this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block128x64 or - Av1BlockSize.Block64x128 - ? 2 - : 1; - - if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x64 or - Av1BlockSize.Block32x32 or - Av1BlockSize.Block16x16) - { - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 16; - } - - if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x32 or - Av1BlockSize.Block32x64 or - Av1BlockSize.Block32x16 or - Av1BlockSize.Block16x32 or - Av1BlockSize.Block16x8 or - Av1BlockSize.Block8x16) - { - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 8; - } - - if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x16 or - Av1BlockSize.Block16x64 or - Av1BlockSize.Block32x8 or - Av1BlockSize.Block8x32 or - Av1BlockSize.Block16x4 or - Av1BlockSize.Block4x16) - { - this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 4; - } - - for (int transformBlockIterator = 0; transformBlockIterator < this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth]; transformBlockIterator++) - { - if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x64) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 16, 32, 48, 0, 16, 32, 48, 0, 16, 32, 48, 0, 16, 32, 48]; - int[] offsety_intra = [0, 0, 0, 0, 16, 16, 16, 16, 32, 32, 32, 32, 48, 48, 48, 48]; - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x32) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 16, 32, 48, 0, 16, 32, 48]; - int[] offsety_intra = [0, 0, 0, 0, 16, 16, 16, 16]; - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x64) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 16, 0, 16, 0, 16, 0, 16]; - int[] offsety_intra = [0, 0, 16, 16, 32, 32, 48, 48]; - - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x32) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 8, 16, 24, 0, 8, 16, 24, 0, 8, 16, 24, 0, 8, 16, 24]; - int[] offsety_intra = [0, 0, 0, 0, 8, 8, 8, 8, 16, 16, 16, 16, 24, 24, 24, 24]; - - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 8, 16, 24, 0, 8, 16, 24]; - int[] offsety_intra = [0, 0, 0, 0, 8, 8, 8, 8]; - - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x32) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 8, 0, 8, 0, 8, 0, 8]; - int[] offsety_intra = [0, 0, 8, 8, 16, 16, 24, 24]; - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x8) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 4, 8, 12, 0, 4, 8, 12]; - int[] offsety_intra = [0, 0, 0, 0, 4, 4, 4, 4]; - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 4, 0, 4, 0, 4, 0, 4]; - int[] offsety_intra = [0, 0, 4, 4, 8, 8, 12, 12]; - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - int[] offsetx_intra = [0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12]; - int[] offsety_intra = [0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12]; - Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - // 0 1 2 3 - int[] offsetx = [0, 16, 32, 48]; - int[] offsety = [0, 0, 0, 0]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x64) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - // 0 1 2 3 - int[] offsetx = [0, 0, 0, 0]; - int[] offsety = [0, 16, 32, 48]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x8) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - // 0 1 2 3 - int[] offsetx = [0, 8, 16, 24]; - int[] offsety = [0, 0, 0, 0]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x32) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - // 0 1 2 3 - int[] offsetx = [0, 0, 0, 0]; - int[] offsety = [0, 8, 16, 24]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x4) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - // 0 1 2 3 - int[] offsetx = [0, 4, 8, 12]; - int[] offsety = [0, 0, 0, 0]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block4x16) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - - // 0 1 2 3 - int[] offsetx = [0, 0, 0, 0]; - int[] offsety = [0, 4, 8, 12]; - Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else - { - if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize( - this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int offsetx = (transformBlockIterator is 0 or 2) ? 0 : 64; - int offsety = (transformBlockIterator is 0 or 1) ? 0 : 64; - Size offset = new(offsetx, offsety); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x64) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize( - this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int offsetx = (transformBlockIterator is 0) ? 0 : 64; - int offsety = 0; - Size offset = new(offsetx, offsety); - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin + offset; - } - else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x128) - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize( - this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - int offsetx = 0; - int offsety = (transformBlockIterator is 0) ? 0 : 64; - Size offset = new(offsetx, offsety); - } - else - { - this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize( - this.blockGeometryModeDecisionScan[index].BlockSize, 0); - this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = - this.blockGeometryModeDecisionScan[index].TransformSizeUv[0]; - this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] = - this.blockGeometryModeDecisionScan[index].Origin; - } - } - - /*this.blockGeometryModeDecisionScan[index].tx_width[tx_depth] = - tx_size_wide[this.blockGeometryModeDecisionScan[index].txsize[tx_depth]]; - this.blockGeometryModeDecisionScan[index].tx_height[tx_depth] = - tx_size_high[this.blockGeometryModeDecisionScan[index].txsize[tx_depth]]; - this.blockGeometryModeDecisionScan[index].tx_width_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_width_uv[0]; - this.blockGeometryModeDecisionScan[index].tx_height_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_height_uv[0];*/ - } - - this.blockGeometryModeDecisionScan[index].ModeDecisionIndex = index; - index += 1; - } - } - } - - /// - /// Gets the largest legal transform size for a luma or subsampled chroma block. - /// - /// The coded block size whose transform limit is requested. - /// The plane index, where zero selects luma and a positive value selects chroma. - /// The maximum transform size for the selected plane. - private static Av1TransformSize GetTransformSize(Av1BlockSize blockSize, int plane) - { - // Luma uses the coded block's normative transform ceiling directly. - if (plane == 0) - { - return blockSize.GetMaximumTransformSize(); - } - - // This geometry models 4:2:0 chroma, so both chroma axes are subsampled before selecting their limit. - bool subsampling_x = plane > 0; - bool subsampling_y = plane > 0; - return blockSize.GetMaxUvTransformSize(subsampling_x, subsampling_y); - } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1GeometryIndex.cs b/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1GeometryIndex.cs deleted file mode 100644 index cc78310178..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1GeometryIndex.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision; - -/// -/// Identifies a predefined AV1 mode-decision geometry with a fixed superblock size, search depth, and partition set. -/// -internal enum Av1GeometryIndex -{ - /// - /// The 64-pixel, four-depth geometry limited to square partitions. - /// - Geometry0, - - /// - /// The 64-pixel, four-depth geometry with horizontal and vertical binary partitions down to 16 pixels. - /// - Geometry1, - - /// - /// The 64-pixel, four-depth geometry with horizontal and vertical binary partitions down to 8 pixels. - /// - Geometry2, - - /// - /// The 64-pixel, four-depth geometry with binary partitions at every supported size. - /// - Geometry3, - - /// - /// The 64-pixel, five-depth geometry with binary partitions at every supported size. - /// - Geometry4, - - /// - /// The 64-pixel, five-depth geometry that also enables four-way horizontal and vertical partitions. - /// - Geometry5, - - /// - /// The 64-pixel, five-depth geometry that enables all supported partition shapes. - /// - Geometry6, - - /// - /// The 128-pixel, six-depth geometry that enables all supported partition shapes. - /// - Geometry7, -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs index d70470fd0b..98f981d070 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; @@ -41,9 +42,9 @@ internal class Av1EncoderBlockModeInfo public int SegmentId { get; set; } /// - /// Gets or sets the transform-tree depth selected for the block. + /// Gets or sets the luma transform size selected for the block. /// - public int TransformDepth { get; set; } + public Av1TransformSize TransformSize { get; set; } /// /// Gets or sets the luma prediction mode written for the block. diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs index facacd1f9c..d43486f145 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; @@ -8,13 +9,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; /// /// Stores encoder block geometry and its selected coding-mode information. /// -internal class Av1EncoderBlockStruct +internal struct Av1EncoderBlockStruct { - /// - /// Stores transform-unit state inline with the block. - /// - private InlineArray16 transformBlocks; - /// /// Stores the luma and shared chroma palette sizes inline with the block. /// @@ -26,19 +22,9 @@ internal class Av1EncoderBlockStruct private Av1EncoderPredictionUnit predictionUnit; /// - /// Gets the transform-unit state in transform traversal order. - /// - public Span TransformBlocks => this.transformBlocks; - - /// - /// Gets or sets the macroblock edge and neighbor state used while writing the block. - /// - public required Av1MacroBlockD MacroBlock { get; set; } - - /// - /// Gets or sets the index used to resolve the block geometry from mode-decision scan order. + /// Gets or sets a value indicating whether this luma block owns the corresponding chroma syntax. /// - public int ModeDecisionScanIndex { get; set; } + public bool HasChroma { get; set; } /// /// Gets or sets the quantizer index used for the block. @@ -58,11 +44,13 @@ internal class Av1EncoderBlockStruct /// /// Gets the writable palette sizes for luma and for the shared chroma mode. /// + [UnscopedRef] public Span PaletteSize => this.paletteSize; /// /// Gets the encoder prediction-unit state for the block. /// + [UnscopedRef] public ref Av1EncoderPredictionUnit PredictionUnit => ref this.predictionUnit; /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCoefficientBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCoefficientBuffer.cs index a50f4fe7ef..a7765c75c5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCoefficientBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderCoefficientBuffer.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Memory; @@ -12,9 +13,14 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal sealed class Av1EncoderCoefficientBuffer : IDisposable { /// - /// Stores one complete superblock's luma and chroma coefficients in each row. + /// The number of coefficients represented by one entry in libaom's EOB arrays. /// - private readonly Buffer2D coefficients; + public const int TransformBlockUnitCoefficientCount = 1 << (Av1Constants.ModeInfoSizeLog2 * 2); + + /// + /// Stores one complete superblock's coefficients and packed transform-block state in each row. + /// + private readonly Buffer2D storage; /// /// Initializes a new instance of the class. @@ -40,11 +46,15 @@ internal sealed class Av1EncoderCoefficientBuffer : IDisposable int chromaSubsampling = (colorConfig.SubSamplingX ? 1 : 0) + (colorConfig.SubSamplingY ? 1 : 0); this.ChromaCoefficientCount = colorConfig.IsMonochrome ? 0 : this.LumaCoefficientCount >> chromaSubsampling; this.CoefficientsPerSuperblock = this.LumaCoefficientCount + (2 * this.ChromaCoefficientCount); + this.LumaTransformBlockCount = this.LumaCoefficientCount / TransformBlockUnitCoefficientCount; + this.ChromaTransformBlockCount = this.ChromaCoefficientCount / TransformBlockUnitCoefficientCount; + this.TransformBlocksPerSuperblock = this.LumaTransformBlockCount + (2 * this.ChromaTransformBlockCount); + int storageElementsPerSuperblock = this.CoefficientsPerSuperblock + this.TransformBlocksPerSuperblock; // libaom stores finalized coefficients by raster-ordered superblock. A two-dimensional owner preserves that - // layout while allowing ImageSharp's allocator to segment the frame instead of demanding one giant rental. - this.coefficients = configuration.MemoryAllocator.Allocate2D( - this.CoefficientsPerSuperblock, + // layout, and packing the EOB/type state into the same row removes its two additional frame-sized allocations. + this.storage = configuration.MemoryAllocator.Allocate2D( + storageElementsPerSuperblock, this.SuperblockCount); } @@ -78,6 +88,21 @@ internal sealed class Av1EncoderCoefficientBuffer : IDisposable /// public int CoefficientsPerSuperblock { get; } + /// + /// Gets the number of luma transform-block positions reserved for each superblock. + /// + public int LumaTransformBlockCount { get; } + + /// + /// Gets the number of transform-block positions reserved for each chroma plane in each superblock. + /// + public int ChromaTransformBlockCount { get; } + + /// + /// Gets the number of transform-block positions reserved for each complete superblock. + /// + public int TransformBlocksPerSuperblock { get; } + /// /// Gets the total number of coefficient positions retained for the frame. /// @@ -91,7 +116,7 @@ internal sealed class Av1EncoderCoefficientBuffer : IDisposable /// The complete coefficient span reserved for that plane and superblock. public Span GetPlaneSpan(int superblockIndex, Av1Plane plane) { - Span superblock = this.coefficients.DangerousGetRowSpan(superblockIndex); + Span superblock = this.storage.DangerousGetRowSpan(superblockIndex); return plane switch { Av1Plane.Y => superblock[..this.LumaCoefficientCount], @@ -102,8 +127,30 @@ internal sealed class Av1EncoderCoefficientBuffer : IDisposable }; } + /// + /// Gets one component plane's transform-block state for a raster-ordered superblock. + /// + /// The raster-ordered superblock index. + /// The requested component plane. + /// One state entry for every 4x4 coefficient unit in the plane. + public Span GetTransformBlockSpan(int superblockIndex, Av1Plane plane) + { + Span superblock = this.storage.DangerousGetRowSpan(superblockIndex); + Span transformBlocks = + MemoryMarshal.Cast(superblock[this.CoefficientsPerSuperblock..]); + + return plane switch + { + Av1Plane.Y => transformBlocks[..this.LumaTransformBlockCount], + Av1Plane.U => transformBlocks.Slice(this.LumaTransformBlockCount, this.ChromaTransformBlockCount), + _ => transformBlocks.Slice( + this.LumaTransformBlockCount + this.ChromaTransformBlockCount, + this.ChromaTransformBlockCount) + }; + } + /// /// Releases the frame coefficient storage. /// - public void Dispose() => this.coefficients.Dispose(); + public void Dispose() => this.storage.Dispose(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderTransformBlockState.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderTransformBlockState.cs new file mode 100644 index 0000000000..bab0da76be --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderTransformBlockState.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +/// +/// Stores the entropy syntax retained for one AV1 transform block. +/// +[StructLayout(LayoutKind.Sequential, Size = sizeof(int))] +internal struct Av1EncoderTransformBlockState +{ + /// + /// Stores the position after the final nonzero coefficient. + /// + private ushort endOfBlock; + + /// + /// Stores the selected transform type. + /// + private Av1TransformType transformType; + + /// + /// Gets or sets the position after the final nonzero coefficient. + /// + public ushort EndOfBlock + { + readonly get => this.endOfBlock; + set => this.endOfBlock = value; + } + + /// + /// Gets or sets the selected transform type. + /// + public Av1TransformType TransformType + { + readonly get => this.transformType; + set => this.transformType = value; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs index ae5dd0a745..d61947a7c5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs @@ -13,6 +13,11 @@ internal partial class Av1TileWriter /// internal class Av1EntropyCodingContext { + /// + /// Gets the reusable macroblock edge and neighbor state for the current entropy-coding operation. + /// + public required Av1MacroBlockD MacroBlock { get; init; } + /// /// Gets or sets the macroblock mode information currently being encoded. /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs index c4517af43b..4f17311c28 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs @@ -68,16 +68,6 @@ internal class Av1MacroBlockD /// public int ToRightEdge { get; set; } - /// - /// Gets or sets the block dimensions in samples for rectangular-partition context selection. - /// - public Size N8Size { get; set; } - - /// - /// Gets or sets a value indicating whether this block is the second half of a rectangular partition. - /// - public bool IsSecondRectangle { get; set; } - /// /// Selects the current entry in the frame-owned mode-information reference grid. /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs index b32eeab6d3..aad7c366f8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs @@ -113,8 +113,8 @@ internal class Av1PictureControlSet int mi_col = origin.X >> Av1Constants.ModeInfoSizeLog2; int mi_row = origin.Y >> Av1Constants.ModeInfoSizeLog2; int mi_offset = (mi_row * cm.ModeInfoColumnCount) + mi_col; - int bw = blockSize.GetWidth(); - int bh = blockSize.GetHeight(); + int bw = blockSize.Get4x4WideCount(); + int bh = blockSize.Get4x4HighCount(); int xmis = Math.Min(cm.ModeInfoColumnCount - mi_col, bw); int ymis = Math.Min(cm.ModeInfoRowCount - mi_row, bh); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs index a262e15e59..dcc774f1e3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs @@ -29,19 +29,4 @@ internal class Av1PictureParentControlSet /// Gets or sets the encoder palette-search level. /// public int PaletteLevel { get; set; } - - /// - /// Gets or sets the frame width aligned for superblock traversal. - /// - public int AlignedWidth { get; set; } - - /// - /// Gets or sets the frame height aligned for superblock traversal. - /// - public int AlignedHeight { get; set; } - - /// - /// Gets or sets the geometry state for each superblock in the picture. - /// - public required Av1SuperblockGeometry[] SuperblockGeometry { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs index 2d33bb2961..59dd140d48 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using static SixLabors.ImageSharp.Formats.Heif.Av1.Tiling.Av1TileWriter; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs deleted file mode 100644 index e6475e50a2..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockGeometry.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; - -/// -/// Describes a superblock node's location, dimensions, and children in the encoder partition tree. -/// -internal class Av1SuperblockGeometry -{ - /// - /// Gets or sets a value indicating whether the superblock lies completely within the coded frame. - /// - public bool IsComplete { get; set; } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs index c6998e57d7..941c5c4c77 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; -using SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; @@ -69,198 +68,465 @@ internal partial class Av1TileWriter Av1EncoderCoefficientBuffer coefficientBuffer, ushort tileIndex) { - Av1SequenceControlSet scs = pcs.Sequence; - Av1NeighborArrayUnit partitionContextNeighbors = pcs.PartitionContexts[tileIndex]; - - // The geometry scan includes both partition nodes and final coding blocks. These two indices - // advance independently because a split node consumes geometry without consuming FinalBlocks. - int blockIndex = 0; - uint finalBlockIndex = 0; - ec_ctx.CodedAreaSuperblock = 0; ec_ctx.CodedAreaSuperblockUv = 0; - Av1SuperblockGeometry sb_geom = pcs.Parent.SuperblockGeometry[superblock.Index]; - bool check_blk_out_of_bound = !sb_geom.IsComplete; - do + ec_ctx.MacroBlock.Tile = superblock.TileInfo; + int partitionIndex = 0; + int finalBlockIndex = 0; + + // Current libaom writes the selected partition tree recursively from the superblock origin. Keeping the + // decisions in preorder removes the global geometry catalog and keeps traversal state on this stack. + WritePartitionTree( + pcs, + ec_ctx, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + pcs.Sequence.SequenceHeader.SuperblockSize, + ec_ctx.SuperblockOrigin, + ref partitionIndex, + ref finalBlockIndex); + } + + /// + /// Writes one selected partition node and recursively visits its split children. + /// + private static void WritePartitionTree( + Av1PictureControlSet pcs, + Av1EntropyCodingContext entropyCodingContext, + ref Av1SymbolEncoder writer, + Av1Superblock superblock, + Av1EncoderCoefficientBuffer coefficientBuffer, + ushort tileIndex, + Av1BlockSize blockSize, + Point blockOrigin, + ref int partitionIndex, + ref int finalBlockIndex) + { + Av1EncoderCommon common = pcs.Parent.Common; + int modeInfoRow = blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2; + int modeInfoColumn = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2; + if (modeInfoRow >= common.ModeInfoRowCount || modeInfoColumn >= common.ModeInfoColumnCount) { - bool code_blk_cond = true; - Av1EncoderBlockStruct blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - Av1BlockGeometry blk_geom = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blockIndex); + return; + } - Av1BlockSize bsize = blk_geom.BlockSize; - Point blockOrigin = blk_geom.Origin; - Guard.IsTrue(bsize < Av1BlockSize.AllSizes, nameof(bsize), "Block size must be a valid value."); + Av1PartitionType partition = superblock.CodingUnitPartitionTypes[partitionIndex++]; + Av1BlockSize subSize = partition.GetBlockSubSize(blockSize); + int halfBlockSize = blockSize.GetWidth() >> 1; + int quarterBlockSize = blockSize.GetWidth() >> 2; - if (check_blk_out_of_bound) - { - // Edge superblocks retain their complete geometry tree, but only nodes whose center or - // origin reaches the visible frame can contribute coding syntax. - code_blk_cond = (((blockOrigin.X + (blk_geom.BlockWidth / 2)) < pcs.Parent.AlignedWidth) || - ((blockOrigin.Y + (blk_geom.BlockHeight / 2)) < pcs.Parent.AlignedHeight)) && - (blockOrigin.X < pcs.Parent.AlignedWidth && blockOrigin.Y < pcs.Parent.AlignedHeight); - } + EncodePartition( + pcs, + ref writer, + blockSize, + partition, + blockOrigin, + pcs.PartitionContexts[tileIndex]); - if (code_blk_cond) - { - int hbs = bsize.Get4x4WideCount() >> 1; - int quarter_step = bsize.Get4x4WideCount() >> 2; - Av1EncoderCommon cm = pcs.Parent.Common; - int mi_row = blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2; - int mi_col = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2; + switch (partition) + { + case Av1PartitionType.None: + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin, + ref finalBlockIndex); - if (bsize >= Av1BlockSize.Block8x8) + break; + case Av1PartitionType.Horizontal: + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin, + ref finalBlockIndex); + + if (modeInfoRow + (blockSize.Get4x4HighCount() >> 1) < common.ModeInfoRowCount) { - // Blocks below 8x8 cannot be partition points in the AV1 syntax. - EncodePartition( + WriteFinalBlock( pcs, + entropyCodingContext, ref writer, - bsize, - superblock.CodingUnitPartitionTypes[blockIndex], - blockOrigin, - partitionContextNeighbors); + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(0, halfBlockSize), + ref finalBlockIndex); } - Guard.IsTrue(Av1Math.Implies(bsize == Av1BlockSize.Block4x4, superblock.CodingUnitPartitionTypes[blockIndex] == Av1PartitionType.None), nameof(bsize), string.Empty); - switch (superblock.CodingUnitPartitionTypes[blockIndex]) - { - case Av1PartitionType.None: - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); - break; - - case Av1PartitionType.Horizontal: - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); - - if (mi_row + hbs < cm.ModeInfoRowCount) - { - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); - } - - break; - - case Av1PartitionType.Vertical: - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); - if (mi_col + hbs < cm.ModeInfoColumnCount) - { - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); - } - - break; - case Av1PartitionType.Split: - break; - case Av1PartitionType.HorizontalA: - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + break; + case Av1PartitionType.Vertical: + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin, + ref finalBlockIndex); - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + if (modeInfoColumn + (blockSize.Get4x4WideCount() >> 1) < common.ModeInfoColumnCount) + { + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(halfBlockSize, 0), + ref finalBlockIndex); + } - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + break; + case Av1PartitionType.Split: + WritePartitionTree( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + subSize, + blockOrigin, + ref partitionIndex, + ref finalBlockIndex); + WritePartitionTree( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + subSize, + blockOrigin + new Size(halfBlockSize, 0), + ref partitionIndex, + ref finalBlockIndex); + WritePartitionTree( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + subSize, + blockOrigin + new Size(0, halfBlockSize), + ref partitionIndex, + ref finalBlockIndex); + WritePartitionTree( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + subSize, + blockOrigin + new Size(halfBlockSize, halfBlockSize), + ref partitionIndex, + ref finalBlockIndex); + + break; + case Av1PartitionType.HorizontalA: + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin, + ref finalBlockIndex); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(halfBlockSize, 0), + ref finalBlockIndex); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(0, halfBlockSize), + ref finalBlockIndex); - break; - case Av1PartitionType.HorizontalB: - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + break; + case Av1PartitionType.HorizontalB: + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin, + ref finalBlockIndex); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(0, halfBlockSize), + ref finalBlockIndex); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(halfBlockSize, halfBlockSize), + ref finalBlockIndex); - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + break; + case Av1PartitionType.VerticalA: + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin, + ref finalBlockIndex); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(0, halfBlockSize), + ref finalBlockIndex); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(halfBlockSize, 0), + ref finalBlockIndex); - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + break; + case Av1PartitionType.VerticalB: + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin, + ref finalBlockIndex); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(halfBlockSize, 0), + ref finalBlockIndex); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + blockOrigin + new Size(halfBlockSize, halfBlockSize), + ref finalBlockIndex); + break; + case Av1PartitionType.Horizontal4: + for (int childIndex = 0; childIndex < 4; childIndex++) + { + Point childOrigin = blockOrigin + new Size(0, childIndex * quarterBlockSize); + if (childIndex > 0 && + (childOrigin.Y >> Av1Constants.ModeInfoSizeLog2) >= common.ModeInfoRowCount) + { break; - case Av1PartitionType.VerticalA: - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); - - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + } - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + childOrigin, + ref finalBlockIndex); + } + break; + case Av1PartitionType.Vertical4: + for (int childIndex = 0; childIndex < 4; childIndex++) + { + Point childOrigin = blockOrigin + new Size(childIndex * quarterBlockSize, 0); + if (childIndex > 0 && + (childOrigin.X >> Av1Constants.ModeInfoSizeLog2) >= common.ModeInfoColumnCount) + { break; - case Av1PartitionType.VerticalB: - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + } - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + WriteFinalBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + coefficientBuffer, + tileIndex, + childOrigin, + ref finalBlockIndex); + } - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); + break; + } - break; - case Av1PartitionType.Horizontal4: - for (int i = 0; i < 4; ++i) - { - int this_mi_row = mi_row + (i * quarter_step); - if (i > 0 && this_mi_row >= cm.ModeInfoRowCount) - { - // Only the last block is able to be outside the picture boundary. If one of the first - // 3 blocks is outside the boundary, H4 is not a valid partition (see AV1 spec 5.11.4) - Guard.IsTrue(i == 3, nameof(i), "Only the last block can be partial"); - break; - } - - if (i > 0) - { - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - } - - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); - } + UpdatePartitionContexts( + pcs.PartitionContexts[tileIndex], + blockOrigin, + subSize, + blockSize, + partition); + } - break; - case Av1PartitionType.Vertical4: - for (int i = 0; i < 4; ++i) - { - int this_mi_col = mi_col + (i * quarter_step); - if (i > 0 && this_mi_col >= cm.ModeInfoColumnCount) - { - // Only the last block is able to be outside the picture boundary. If one of the first - // 3 blocks is outside the boundary, H4 is not a valid partition (see AV1 spec 5.11.4) - Guard.IsTrue(i == 3, nameof(i), "Only the last block can be partial"); - break; - } - - if (i > 0) - { - finalBlockIndex++; - blk_ptr = superblock.FinalBlocks[finalBlockIndex]; - } - - WriteModesBlock(pcs, ec_ctx, ref writer, superblock, blk_ptr, tileIndex, coefficientBuffer); - } + /// + /// Writes the next final block selected by partition traversal. + /// + private static void WriteFinalBlock( + Av1PictureControlSet pcs, + Av1EntropyCodingContext entropyCodingContext, + ref Av1SymbolEncoder writer, + Av1Superblock superblock, + Av1EncoderCoefficientBuffer coefficientBuffer, + ushort tileIndex, + Point blockOrigin, + ref int finalBlockIndex) + { + ref Av1EncoderBlockStruct block = ref superblock.FinalBlocks[finalBlockIndex++]; + WriteModesBlock( + pcs, + entropyCodingContext, + ref writer, + superblock, + ref block, + tileIndex, + blockOrigin, + coefficientBuffer); + } - break; - } + /// + /// Publishes the partition contexts produced by one completed partition node. + /// + internal static void UpdatePartitionContexts( + Av1NeighborArrayUnit neighbors, + Point blockOrigin, + Av1BlockSize subSize, + Av1BlockSize blockSize, + Av1PartitionType partition) + { + if (blockSize < Av1BlockSize.Block8x8) + { + return; + } - if (superblock.CodingUnitPartitionTypes[blockIndex] != Av1PartitionType.Split) - { - finalBlockIndex++; - blockIndex += blk_geom.NextDepthOffset; - } - else + int halfBlockSize = blockSize.GetWidth() >> 1; + Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize); + switch (partition) + { + case Av1PartitionType.Split: + if (blockSize != Av1BlockSize.Block8x8) { - blockIndex += blk_geom.Depth1Offset; + return; } - } - else - { - blockIndex += blk_geom.Depth1Offset; - } + + UpdatePartitionContext(neighbors, blockOrigin, subSize, blockSize); + break; + case Av1PartitionType.None: + case Av1PartitionType.Horizontal: + case Av1PartitionType.Vertical: + case Av1PartitionType.Horizontal4: + case Av1PartitionType.Vertical4: + UpdatePartitionContext(neighbors, blockOrigin, subSize, blockSize); + break; + case Av1PartitionType.HorizontalA: + UpdatePartitionContext(neighbors, blockOrigin, splitSize, subSize); + UpdatePartitionContext( + neighbors, + blockOrigin + new Size(0, halfBlockSize), + subSize, + subSize); + + break; + case Av1PartitionType.HorizontalB: + UpdatePartitionContext(neighbors, blockOrigin, subSize, subSize); + UpdatePartitionContext( + neighbors, + blockOrigin + new Size(0, halfBlockSize), + splitSize, + subSize); + + break; + case Av1PartitionType.VerticalA: + UpdatePartitionContext(neighbors, blockOrigin, splitSize, subSize); + UpdatePartitionContext( + neighbors, + blockOrigin + new Size(halfBlockSize, 0), + subSize, + subSize); + + break; + case Av1PartitionType.VerticalB: + UpdatePartitionContext(neighbors, blockOrigin, subSize, subSize); + UpdatePartitionContext( + neighbors, + blockOrigin + new Size(halfBlockSize, 0), + splitSize, + subSize); + + break; } - while (blockIndex < scs.MaxBlockCount); + } + + /// + /// Writes one partition-context value across the complete parent edges. + /// + private static void UpdatePartitionContext( + Av1NeighborArrayUnit neighbors, + Point blockOrigin, + Av1BlockSize contextBlockSize, + Av1BlockSize coveredBlockSize) + { + Av1PartitionContext context = PartitionContextLookup[(int)contextBlockSize]; + Av1NeighborArrayUnit.UnitMask edgeMask = + Av1NeighborArrayUnit.UnitMask.Left | + Av1NeighborArrayUnit.UnitMask.Top; + + neighbors.UnitModeWrite( + context, + blockOrigin, + new Size(coveredBlockSize.GetWidth(), coveredBlockSize.GetHeight()), + edgeMask); } /// @@ -287,9 +553,10 @@ internal partial class Av1TileWriter return; } - int hbs = (blockSize.Get4x4WideCount() << 2) >> 1; - bool has_rows = (blockOrigin.Y + hbs) < pcs.Parent.AlignedHeight; - bool has_cols = (blockOrigin.X + hbs) < pcs.Parent.AlignedWidth; + int halfBlockModeInfoCount = blockSize.Get4x4WideCount() >> 1; + Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; + bool has_rows = modeInfoPosition.Y + halfBlockModeInfoCount < pcs.Parent.Common.ModeInfoRowCount; + bool has_cols = modeInfoPosition.X + halfBlockModeInfoCount < pcs.Parent.Common.ModeInfoColumnCount; int partition_context_left_neighbor_index = partition_context_na.GetLeftIndex(blockOrigin); int partition_context_top_neighbor_index = partition_context_na.GetTopIndex(blockOrigin); @@ -345,14 +612,16 @@ internal partial class Av1TileWriter /// The containing superblock. /// The final encoder decisions for the block. /// The zero-based tile index. + /// The absolute luma-sample origin of the block. /// The transformed coefficients retained by raster-ordered superblock. private static void WriteModesBlock( Av1PictureControlSet pcs, Av1EntropyCodingContext entropyCodingContext, ref Av1SymbolEncoder writer, Av1Superblock tb_ptr, - Av1EncoderBlockStruct blk_ptr, + ref Av1EncoderBlockStruct blk_ptr, ushort tile_idx, + Point blockOrigin, Av1EncoderCoefficientBuffer coefficientBuffer) { Av1SequenceControlSet scs = pcs.Sequence; @@ -360,49 +629,22 @@ internal partial class Av1TileWriter Av1NeighborArrayUnit luma_dc_sign_level_coeff_na = pcs.LuminanceDcSignLevelCoefficientNeighbors[tile_idx]; Av1NeighborArrayUnit cr_dc_sign_level_coeff_na = pcs.CrDcSignLevelCoefficientNeighbors[tile_idx]; Av1NeighborArrayUnit cb_dc_sign_level_coeff_na = pcs.CbDcSignLevelCoefficientNeighbors[tile_idx]; - Av1NeighborArrayUnit txfm_context_array = pcs.TransformFunctionContexts[tile_idx]; - Av1BlockGeometry blockGeometry = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blk_ptr.ModeDecisionScanIndex); - Point blockOrigin = Point.Add(entropyCodingContext.SuperblockOrigin, (Size)blockGeometry.Origin); - Av1BlockSize blockSize = blockGeometry.BlockSize; int mi_row = blockOrigin.Y >> Av1Constants.ModeInfoSizeLog2; int mi_col = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2; int mi_stride = pcs.Parent.Common.ModeInfoStride; - int offset = (mi_row * mi_stride) + mi_col; Point modeInfoPosition = new(mi_col, mi_row); Av1MacroBlockModeInfo macroBlockModeInfo = pcs.GetMacroBlockModeInfo(modeInfoPosition); + Av1BlockSize blockSize = macroBlockModeInfo.Block.BlockSize; bool skipWritingCoefficients = macroBlockModeInfo.Block.Skip; entropyCodingContext.MacroBlockModeInfo = macroBlockModeInfo; - - bool skip_mode = macroBlockModeInfo.Block.SkipMode; + Av1MacroBlockD macroBlock = entropyCodingContext.MacroBlock; Guard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize)); - blk_ptr.MacroBlock.SetModeInfoGrid(pcs.ModeInfoGrid, offset); - blk_ptr.MacroBlock.Tile = new Av1TileInfo(tb_ptr.TileInfo); - blk_ptr.MacroBlock.IsUpAvailable = modeInfoPosition.Y > tb_ptr.TileInfo.ModeInfoRowStart; - blk_ptr.MacroBlock.IsLeftAvailable = modeInfoPosition.X > tb_ptr.TileInfo.ModeInfoColumnStart; - - if (blk_ptr.MacroBlock.IsUpAvailable) - { - blk_ptr.MacroBlock.AboveMacroBlock = blk_ptr.MacroBlock.GetRelativeModeInfo(-mi_stride).MacroBlockModeInfo; - } - else - { - blk_ptr.MacroBlock.AboveMacroBlock = null; - } - - if (blk_ptr.MacroBlock.IsLeftAvailable) - { - blk_ptr.MacroBlock.LeftMacroBlock = blk_ptr.MacroBlock.GetRelativeModeInfo(-1).MacroBlockModeInfo; - } - else - { - blk_ptr.MacroBlock.LeftMacroBlock = null; - } SetModeInfoRowAndColumn( pcs, - blk_ptr.MacroBlock, - blk_ptr.MacroBlock.Tile, + macroBlock, + macroBlock.Tile, modeInfoPosition, blockSize, mi_stride, @@ -413,23 +655,23 @@ internal partial class Av1TileWriter { if (pcs.Parent.FrameHeader.SegmentationParameters.Enabled && pcs.Parent.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) { - WriteSegmentId(pcs, ref writer, blockGeometry.BlockSize, blockOrigin, blk_ptr, skipWritingCoefficients); + WriteSegmentId(pcs, ref writer, blockSize, blockOrigin, macroBlock, ref blk_ptr, skipWritingCoefficients); } - EncodeSkipCoefficients(ref writer, blk_ptr, skipWritingCoefficients); + EncodeSkipCoefficients(ref writer, macroBlock, skipWritingCoefficients); if (pcs.Parent.FrameHeader.SegmentationParameters.Enabled && !pcs.Parent.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) { - WriteSegmentId(pcs, ref writer, blockGeometry.BlockSize, blockOrigin, blk_ptr, skipWritingCoefficients); + WriteSegmentId(pcs, ref writer, blockSize, blockOrigin, macroBlock, ref blk_ptr, skipWritingCoefficients); } WriteCdef( scs, pcs, - ref writer, + writer, tile_idx, skipWritingCoefficients, - blockOrigin << Av1Constants.ModeInfoSizeLog2); + modeInfoPosition); if (pcs.Parent.FrameHeader.DeltaQParameters.IsPresent) { @@ -451,37 +693,37 @@ internal partial class Av1TileWriter Av1ChromaPredictionMode intra_chroma_mode = macroBlockModeInfo.Block.UvMode; if (IsIntraBlockCopyAllowed(pcs.Parent.FrameHeader/*, pcs.Parent.SliceType*/)) { - WriteIntraBlockCopyInfo(ref writer, macroBlockModeInfo, blk_ptr); + WriteIntraBlockCopyInfo(ref writer, macroBlockModeInfo); } if (!macroBlockModeInfo.Block.UseIntraBlockCopy) { - EncodeIntraLumaMode(ref writer, macroBlockModeInfo, blk_ptr, blockSize, intra_luma_mode); + EncodeIntraLumaMode(ref writer, macroBlockModeInfo, macroBlock, ref blk_ptr, blockSize, intra_luma_mode); } if (!macroBlockModeInfo.Block.UseIntraBlockCopy) { - if (blockGeometry.HasUv) + if (blk_ptr.HasChroma) { EncodeIntraChromaMode( ref writer, macroBlockModeInfo, - blk_ptr, + ref blk_ptr, blockSize, intra_luma_mode, intra_chroma_mode, - blockGeometry.BlockWidth <= 32 && blockGeometry.BlockHeight <= 32); + blockSize.GetWidth() <= 32 && blockSize.GetHeight() <= 32); } } - if (!macroBlockModeInfo.Block.UseIntraBlockCopy && IsPaletteAllowed(frm_hdr.AllowScreenContentTools, blockGeometry.BlockSize)) + if (!macroBlockModeInfo.Block.UseIntraBlockCopy && IsPaletteAllowed(frm_hdr.AllowScreenContentTools, blockSize)) { WritePaletteModeInfo( scs, ref writer, macroBlockModeInfo, - blk_ptr, - blockGeometry.BlockSize, + ref blk_ptr, + blockSize, blockOrigin >> Av1Constants.ModeInfoSizeLog2); } @@ -506,27 +748,13 @@ internal partial class Av1TileWriter } } - if (frm_hdr.TransformMode == Av1TransformMode.Select) - { - // TODO: Implement when Selecting transform block size is supported. - // CodeTransformSize( - // pcs, - // ref writer, - // blockOrigin, - // blk_ptr, - // blockGeometry, - // txfm_context_array, - // skipWritingCoefficients); - } - if (!skipWritingCoefficients) { EncodeCoefficients1d( pcs, entropyCodingContext, ref writer, - entropyCodingContext.MacroBlockModeInfo, - blk_ptr, + ref blk_ptr, blockOrigin, intra_luma_mode, blockSize, @@ -539,17 +767,7 @@ internal partial class Av1TileWriter } // Neighbor state must be updated after all symbols for the block have used the preceding contexts. - UpdateNeighbors(pcs, entropyCodingContext, blockOrigin, blk_ptr, tile_idx, blockSize); - - if (IsPaletteAllowed(pcs.Parent.PaletteLevel, blockGeometry.BlockSize)) - { - /* - // free ENCDEC palette info buffer - assert(blk_ptr.palette_info.color_idx_map != null && "free palette:Null"); - EB_FREE(blk_ptr.palette_info.color_idx_map); - blk_ptr.palette_info.color_idx_map = null; - EB_FREE(blk_ptr.palette_info);*/ - } + UpdateNeighbors(pcs, entropyCodingContext, blockOrigin, ref blk_ptr, tile_idx, blockSize); } /// @@ -565,7 +783,7 @@ internal partial class Av1TileWriter private static void EncodeIntraChromaMode( ref Av1SymbolEncoder writer, Av1MacroBlockModeInfo macroBlockModeInfo, - Av1EncoderBlockStruct blk_ptr, + ref Av1EncoderBlockStruct blk_ptr, Av1BlockSize blockSize, Av1PredictionMode lumaMode, Av1ChromaPredictionMode chromaMode, @@ -618,17 +836,19 @@ internal partial class Av1TileWriter /// /// The tile symbol encoder. /// The selected block modes. + /// The reusable macroblock edge and neighbor state. /// The encoder prediction-unit state. /// The block size. /// The selected luma prediction mode. private static void EncodeIntraLumaMode( ref Av1SymbolEncoder writer, Av1MacroBlockModeInfo macroBlockModeInfo, - Av1EncoderBlockStruct blk_ptr, + Av1MacroBlockD macroBlock, + ref Av1EncoderBlockStruct blk_ptr, Av1BlockSize blockSize, Av1PredictionMode lumaMode) { - GetYModeContext(blk_ptr.MacroBlock, out byte topContext, out byte leftContext); + GetYModeContext(macroBlock, out byte topContext, out byte leftContext); writer.WriteLumaMode(lumaMode, topContext, leftContext); if (blockSize >= Av1BlockSize.Block8x8 && macroBlockModeInfo.Block.Mode.IsDirectional()) @@ -651,7 +871,7 @@ internal partial class Av1TileWriter Av1SequenceControlSet scs, ref Av1SymbolEncoder writer, Av1MacroBlockModeInfo macroBlockModeInfo, - Av1EncoderBlockStruct blk_ptr, + ref Av1EncoderBlockStruct blk_ptr, Av1BlockSize blockSize, Point point) { @@ -694,12 +914,10 @@ internal partial class Av1TileWriter /// /// The tile symbol encoder. /// The selected block modes. - /// The encoder block state. /// The displacement-vector syntax is not implemented when intra block copy is selected. private static void WriteIntraBlockCopyInfo( ref Av1SymbolEncoder writer, - Av1MacroBlockModeInfo macroBlockModeInfo, - Av1EncoderBlockStruct block) + Av1MacroBlockModeInfo macroBlockModeInfo) { bool use_intrabc = macroBlockModeInfo.Block.UseIntraBlockCopy; writer.WriteUseIntraBlockCopy(use_intrabc); @@ -718,7 +936,7 @@ internal partial class Av1TileWriter => frameHeader.AllowScreenContentTools && frameHeader.AllowIntraBlockCopy; /// - /// Updates partition and coefficient neighbor arrays after writing a block. + /// Updates coefficient neighbor arrays after writing a block. /// /// The picture coding state. /// The entropy-coding position state for the superblock. @@ -730,29 +948,18 @@ internal partial class Av1TileWriter Av1PictureControlSet pcs, Av1EntropyCodingContext entropyCodingContext, Point blockOrigin, - Av1EncoderBlockStruct blk_ptr, + ref Av1EncoderBlockStruct blk_ptr, ushort tile_idx, Av1BlockSize blockSize) { - Av1NeighborArrayUnit partition_context_na = pcs.PartitionContexts[tile_idx]; Av1NeighborArrayUnit luma_dc_sign_level_coeff_na = pcs.LuminanceDcSignLevelCoefficientNeighbors[tile_idx]; Av1NeighborArrayUnit cr_dc_sign_level_coeff_na = pcs.CrDcSignLevelCoefficientNeighbors[tile_idx]; Av1NeighborArrayUnit cb_dc_sign_level_coeff_na = pcs.CbDcSignLevelCoefficientNeighbors[tile_idx]; - Av1BlockGeometry blk_geom = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blk_ptr.ModeDecisionScanIndex); Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; Av1MacroBlockModeInfo mbmi = pcs.GetMacroBlockModeInfo(modeInfoPosition); bool skip_coeff = mbmi.Block.Skip; - // Store the block-size split mask across the edges that future partition symbols can observe. - Av1PartitionContext partition = new( - PartitionContextLookup[(int)blockSize].Above, - PartitionContextLookup[(int)blockSize].Left); - Size size = new(blk_geom.BlockWidth, blk_geom.BlockHeight); - partition_context_na.UnitModeWrite( - partition, - blockOrigin, - size, - Av1NeighborArrayUnit.UnitMask.Left | Av1NeighborArrayUnit.UnitMask.Top); + Size size = new(blockSize.GetWidth(), blockSize.GetHeight()); if (skip_coeff) { // A skipped block has an all-zero residual, so publish a zero sign/level context over its edges @@ -763,10 +970,14 @@ internal partial class Av1TileWriter size, Av1NeighborArrayUnit.UnitMask.Left | Av1NeighborArrayUnit.UnitMask.Top); - if (blk_geom.HasUv) + ObuColorConfig colorConfig = pcs.Sequence.SequenceHeader.ColorConfig; + if (blk_ptr.HasChroma && !colorConfig.IsMonochrome) { - Point chromaOrigin = RoundUv(blockOrigin) >> 1; - Size chromaSize = new(blk_geom.BlockWidthUv, blk_geom.BlockHeightUv); + int subsamplingX = colorConfig.SubSamplingX ? 1 : 0; + int subsamplingY = colorConfig.SubSamplingY ? 1 : 0; + Point chromaOrigin = GetChromaBlockOrigin(blockOrigin, subsamplingX, subsamplingY); + Av1BlockSize chromaBlockSize = blockSize.GetSubsampled(colorConfig.SubSamplingX, colorConfig.SubSamplingY); + Size chromaSize = new(chromaBlockSize.GetWidth(), chromaBlockSize.GetHeight()); cb_dc_sign_level_coeff_na.UnitModeWrite( 0, @@ -778,10 +989,10 @@ internal partial class Av1TileWriter chromaOrigin, chromaSize, Av1NeighborArrayUnit.UnitMask.Left | Av1NeighborArrayUnit.UnitMask.Top); - entropyCodingContext.CodedAreaSuperblockUv += blk_geom.BlockWidthUv * blk_geom.BlockHeightUv; + entropyCodingContext.CodedAreaSuperblockUv += chromaSize.Width * chromaSize.Height; } - entropyCodingContext.CodedAreaSuperblock += blk_geom.BlockWidth * blk_geom.BlockHeight; + entropyCodingContext.CodedAreaSuperblock += size.Width * size.Height; } } @@ -821,32 +1032,21 @@ internal partial class Av1TileWriter /// The zero-based tile index. /// A value indicating whether the current block omits residual coefficients. /// The block position in 4x4 mode-information units. - private static void WriteCdef( + internal static void WriteCdef( Av1SequenceControlSet scs, Av1PictureControlSet pcs, - ref Av1SymbolEncoder writer, + Av1SymbolEncoder writer, int tileIndex, bool skip, Point modeInfoPosition) { - Av1EncoderCommon cm = pcs.Parent.Common; ObuFrameHeader frameHeader = pcs.Parent.FrameHeader; if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy) { - // Lossless and intra-block-copy frames disable CDEF, so normalize the header to its single zero-strength form. - frameHeader.CdefParameters.BitCount = 0; - frameHeader.CdefParameters.YStrength[0] = 0; - frameHeader.CdefParameters.UvStrength[0] = 0; - - // pcs.Parent.nb_cdef_strengths = 1; return; } - // int m = ~((1 << (6 - Av1Constants.ModeInfoSizeLog2)) - 1); - // cm->mi_grid_visible[(mi_row & m) * cm->mi_stride + (mi_col & m)]; - Av1ModeInfo mi = pcs.GetFromModeInfoGrid(modeInfoPosition); - // Each superblock begins with all contained 64x64 filter units unassigned. if ((modeInfoPosition.Y & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0 && (modeInfoPosition.X & (scs.SequenceHeader.SuperblockModeInfoSize - 1)) == 0) @@ -858,13 +1058,23 @@ internal partial class Av1TileWriter } // The strength is coded once, at the first non-skipped block in each 64x64 CDEF filter unit. - int mask = 1 << (6 - Av1Constants.ModeInfoSizeLog2); - int index = scs.SequenceHeader.Use128x128Superblock ? Math.Max(1, modeInfoPosition.X & mask) + (2 * Math.Max(1, modeInfoPosition.Y & mask)) : 0; + int cdefSize = 1 << (6 - Av1Constants.ModeInfoSizeLog2); + int unitColumn = (modeInfoPosition.X & cdefSize) != 0 ? 1 : 0; + int unitRow = (modeInfoPosition.Y & cdefSize) != 0 ? 1 : 0; + int index = scs.SequenceHeader.Use128x128Superblock ? unitColumn + (2 * unitRow) : 0; if (pcs.CdefPreset[tileIndex][index] == -1 && !skip) { - writer.WriteCdefStrength(mi.MacroBlockModeInfo.CdefStrength, frameHeader.CdefParameters.BitCount); - pcs.CdefPreset[tileIndex][index] = mi.MacroBlockModeInfo.CdefStrength; + int firstBlockMask = ~(cdefSize - 1); + Point firstBlockPosition = new( + modeInfoPosition.X & firstBlockMask, + modeInfoPosition.Y & firstBlockMask); + Av1ModeInfo firstBlock = pcs.GetFromModeInfoGrid(firstBlockPosition); + + // CDEF strength belongs to the first mode-info block in the 64x64 filter unit even when skipped + // blocks delay transmission until a later coding block. + writer.WriteCdefStrength(firstBlock.MacroBlockModeInfo.CdefStrength, frameHeader.CdefParameters.BitCount); + pcs.CdefPreset[tileIndex][index] = firstBlock.MacroBlockModeInfo.CdefStrength; } } @@ -879,7 +1089,7 @@ internal partial class Av1TileWriter /// The row stride of the mode-information grid. /// The coded frame height in mode-information rows. /// The coded frame width in mode-information columns. - private static void SetModeInfoRowAndColumn( + internal static void SetModeInfoRowAndColumn( Av1PictureControlSet pcs, Av1MacroBlockD macroBlock, Av1TileInfo tile, @@ -890,9 +1100,11 @@ internal partial class Av1TileWriter int modeInfoColumnCount) { macroBlock.ToTopEdge = -((modeInfoPosition.Y << Av1Constants.ModeInfoSizeLog2) << 3); - macroBlock.ToBottomEdge = ((modeInfoRowCount - blockSize.GetHeight() - modeInfoPosition.Y) << Av1Constants.ModeInfoSizeLog2) << 3; + int blockModeInfoHeight = blockSize.Get4x4HighCount(); + int blockModeInfoWidth = blockSize.Get4x4WideCount(); + macroBlock.ToBottomEdge = ((modeInfoRowCount - blockModeInfoHeight - modeInfoPosition.Y) << Av1Constants.ModeInfoSizeLog2) << 3; macroBlock.ToLeftEdge = -((modeInfoPosition.X << Av1Constants.ModeInfoSizeLog2) << 3); - macroBlock.ToRightEdge = ((modeInfoColumnCount - blockSize.GetWidth() - modeInfoPosition.X) << Av1Constants.ModeInfoSizeLog2) << 3; + macroBlock.ToRightEdge = ((modeInfoColumnCount - blockModeInfoWidth - modeInfoPosition.X) << Av1Constants.ModeInfoSizeLog2) << 3; macroBlock.ModeInfoStride = modeInfoStride; @@ -919,26 +1131,6 @@ internal partial class Av1TileWriter { macroBlock.LeftMacroBlock = null; } - - macroBlock.N8Size = new Size(blockSize.GetWidth(), blockSize.GetHeight()); - macroBlock.IsSecondRectangle = false; - if (macroBlock.N8Size.Width < macroBlock.N8Size.Height) - { - // Only the last sub-block of a rectangular partition selects the secondary transform context. - // Vertical-four therefore maps to (0, 0, 0, 1), while two-way partitions map to (0, 1). - if (((modeInfoPosition.X + macroBlock.N8Size.Width) & (macroBlock.N8Size.Height - 1)) == 0) - { - macroBlock.IsSecondRectangle = true; - } - } - - if (macroBlock.N8Size.Width > macroBlock.N8Size.Height) - { - if ((modeInfoPosition.Y & (macroBlock.N8Size.Width - 1)) > 0) - { - macroBlock.IsSecondRectangle = true; - } - } } /// @@ -947,7 +1139,6 @@ internal partial class Av1TileWriter /// The picture coding state. /// The entropy-coding position state for the superblock. /// The tile symbol encoder. - /// The selected macroblock modes. /// The encoder block state. /// The block origin in samples. /// The luma prediction direction. @@ -961,8 +1152,7 @@ internal partial class Av1TileWriter Av1PictureControlSet pcs, Av1EntropyCodingContext ec_ctx, ref Av1SymbolEncoder writer, - Av1MacroBlockModeInfo mbmi, - Av1EncoderBlockStruct blk_ptr, + ref Av1EncoderBlockStruct blk_ptr, Point blockOrigin, Av1PredictionMode intraLumaDir, Av1BlockSize planeBlockSize, @@ -976,8 +1166,7 @@ internal partial class Av1TileWriter pcs, ec_ctx, ref writer, - mbmi, - blk_ptr, + ref blk_ptr, blockOrigin, intraLumaDir, planeBlockSize, @@ -989,8 +1178,7 @@ internal partial class Av1TileWriter pcs, ec_ctx, ref writer, - mbmi, - blk_ptr, + ref blk_ptr, blockOrigin, intraLumaDir, planeBlockSize, @@ -1006,7 +1194,6 @@ internal partial class Av1TileWriter /// The picture coding state. /// The entropy-coding position state for the superblock. /// The tile symbol encoder. - /// The selected macroblock modes. /// The encoder block state. /// The block origin in samples. /// The luma prediction direction. @@ -1018,8 +1205,7 @@ internal partial class Av1TileWriter Av1PictureControlSet pcs, Av1EntropyCodingContext entropyCodingContext, ref Av1SymbolEncoder writer, - Av1MacroBlockModeInfo mbmi, - Av1EncoderBlockStruct blk_ptr, + ref Av1EncoderBlockStruct blk_ptr, Point blockOrigin, Av1PredictionMode intraLumaDir, Av1BlockSize plane_bsize, @@ -1027,61 +1213,91 @@ internal partial class Av1TileWriter int superblockIndex, Av1NeighborArrayUnit luma_dc_sign_level_coeff_na) { - // This writer currently emits intra frames, so coefficient contexts use only intra prediction state. - Av1BlockGeometry blockGeometry = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blk_ptr.ModeDecisionScanIndex); - int tx_depth = mbmi.Block.TransformDepth; - int txb_count = blockGeometry.TransformBlockCount[mbmi.Block.TransformDepth]; ObuFrameHeader frameHeader = pcs.Parent.FrameHeader; Span lumaCoefficients = coefficientBuffer.GetPlaneSpan(superblockIndex, Av1Plane.Y); - - for (int tx_index = 0; tx_index < txb_count; tx_index++) + Span lumaTransformBlocks = + coefficientBuffer.GetTransformBlockSpan(superblockIndex, Av1Plane.Y); + Av1TransformSize transformSize = entropyCodingContext.MacroBlockModeInfo.Block.TransformSize; + int transformBlockWidth = transformSize.Get4x4WideCount(); + int transformBlockHeight = transformSize.Get4x4HighCount(); + Av1MacroBlockD macroBlock = entropyCodingContext.MacroBlock; + int maximumBlocksWide = plane_bsize.GetWidth(); + int maximumBlocksHigh = plane_bsize.GetHeight(); + if (macroBlock.ToRightEdge < 0) { - int txb_itr = tx_index; - - Av1TransformSize tx_size = blockGeometry.TransformSize[tx_depth]; - - int coeff1d_offset = entropyCodingContext.CodedAreaSuperblock; - Span coeff_buffer = lumaCoefficients[coeff1d_offset..]; + maximumBlocksWide += macroBlock.ToRightEdge >> 3; + } - Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][txb_itr]; - Av1TransformBlockContext blockContext = GetTransformBlockContexts( - Av1ComponentType.Luminance, - luma_dc_sign_level_coeff_na, - blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin, - plane_bsize, - tx_size); + if (macroBlock.ToBottomEdge < 0) + { + maximumBlocksHigh += macroBlock.ToBottomEdge >> 3; + } - Av1TransformType tx_type = blk_ptr.TransformBlocks[txb_itr].TransformType[(int)Av1ComponentType.Luminance]; - int eob = blk_ptr.TransformBlocks[txb_itr].NzCoefficientCount[0]; - if (eob == 0) + maximumBlocksWide >>= Av1Constants.ModeInfoSizeLog2; + maximumBlocksHigh >>= Av1Constants.ModeInfoSizeLog2; + int maximumUnitBlocksWide = Math.Min( + Av1BlockSize.Block64x64.Get4x4WideCount(), + maximumBlocksWide); + int maximumUnitBlocksHigh = Math.Min( + Av1BlockSize.Block64x64.Get4x4HighCount(), + maximumBlocksHigh); + + // AV1 visits residuals in bounded 64x64 regions so transform order remains stable for 128x128 blocks. + for (int regionRow = 0; regionRow < maximumBlocksHigh; regionRow += maximumUnitBlocksHigh) + { + int unitHeight = Math.Min(maximumUnitBlocksHigh + regionRow, maximumBlocksHigh); + for (int regionColumn = 0; regionColumn < maximumBlocksWide; regionColumn += maximumUnitBlocksWide) { - // AV1 requires the canonical DCT_DCT transform type when a transform block has no coefficients. - tx_type = blk_ptr.TransformBlocks[txb_itr].TransformType[(int)Av1PlaneType.Y] = Av1TransformType.DctDct; - Guard.IsTrue(tx_type == Av1TransformType.DctDct, nameof(tx_type), string.Empty); - } - - int culLevelY = writer.WriteCoefficients( - tx_size, - tx_type, - intraLumaDir, - coeff_buffer, - Av1ComponentType.Luminance, - blockContext, - (ushort)eob, - frameHeader.UseReducedTransformSet, - blk_ptr.FilterIntraMode); - - // Only the packed low byte is the AV1 entropy context. Converting the value explicitly keeps - // the update independent of machine endianness and publishes one value per covered edge unit. - int transformWidth = blockGeometry.TransformSize[tx_depth].GetWidth(); - int transformHeight = blockGeometry.TransformSize[tx_depth].GetHeight(); - luma_dc_sign_level_coeff_na.UnitModeWrite( - (byte)culLevelY, - blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin, - new Size(transformWidth, transformHeight), - Av1NeighborArrayUnit.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); + int unitWidth = Math.Min(maximumUnitBlocksWide + regionColumn, maximumBlocksWide); + for (int blockRow = regionRow; blockRow < unitHeight; blockRow += transformBlockHeight) + { + for (int blockColumn = regionColumn; blockColumn < unitWidth; blockColumn += transformBlockWidth) + { + int transformStateIndex = entropyCodingContext.CodedAreaSuperblock / + Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; + ref Av1EncoderTransformBlockState transformBlock = ref lumaTransformBlocks[transformStateIndex]; + Point transformOrigin = blockOrigin + new Size( + blockColumn << Av1Constants.ModeInfoSizeLog2, + blockRow << Av1Constants.ModeInfoSizeLog2); + Span coefficients = lumaCoefficients[entropyCodingContext.CodedAreaSuperblock..]; + Av1TransformBlockContext blockContext = GetTransformBlockContexts( + Av1ComponentType.Luminance, + luma_dc_sign_level_coeff_na, + transformOrigin, + plane_bsize, + transformSize); + + Av1TransformType transformType = transformBlock.TransformType; + ushort endOfBlock = transformBlock.EndOfBlock; + if (endOfBlock == 0) + { + // Empty transform blocks use the canonical transform type even when mode decision retained another candidate. + transformType = transformBlock.TransformType = Av1TransformType.DctDct; + } - entropyCodingContext.CodedAreaSuperblock += transformWidth * transformHeight; + int culLevelY = writer.WriteCoefficients( + transformSize, + transformType, + intraLumaDir, + coefficients, + Av1ComponentType.Luminance, + blockContext, + endOfBlock, + frameHeader.UseReducedTransformSet, + blk_ptr.FilterIntraMode); + + int transformWidth = transformSize.GetWidth(); + int transformHeight = transformSize.GetHeight(); + luma_dc_sign_level_coeff_na.UnitModeWrite( + (byte)culLevelY, + transformOrigin, + new Size(transformWidth, transformHeight), + Av1NeighborArrayUnit.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); + + entropyCodingContext.CodedAreaSuperblock += transformWidth * transformHeight; + } + } + } } } @@ -1091,7 +1307,6 @@ internal partial class Av1TileWriter /// The picture coding state. /// The entropy-coding position state for the superblock. /// The tile symbol encoder. - /// The selected macroblock modes. /// The encoder block state. /// The luma block origin in samples. /// The luma prediction direction used by coefficient contexts. @@ -1104,8 +1319,7 @@ internal partial class Av1TileWriter Av1PictureControlSet pcs, Av1EntropyCodingContext entropyCodingContext, ref Av1SymbolEncoder writer, - Av1MacroBlockModeInfo mbmi, - Av1EncoderBlockStruct blk_ptr, + ref Av1EncoderBlockStruct blk_ptr, Point blockOrigin, Av1PredictionMode intraLumaDir, Av1BlockSize plane_bsize, @@ -1114,93 +1328,140 @@ internal partial class Av1TileWriter Av1NeighborArrayUnit cr_dc_sign_level_coeff_na, Av1NeighborArrayUnit cb_dc_sign_level_coeff_na) { - Av1BlockGeometry blockGeometry = Av1BlockGeometryFactory.GetBlockGeometryByModeDecisionScanIndex(blk_ptr.ModeDecisionScanIndex); - - if (!blockGeometry.HasUv) + ObuColorConfig colorConfig = pcs.Sequence.SequenceHeader.ColorConfig; + if (!blk_ptr.HasChroma || colorConfig.IsMonochrome) { return; } - int tx_depth = mbmi.Block.TransformDepth; ObuFrameHeader frameHeader = pcs.Parent.FrameHeader; - Av1TransformSize chromaTransformSize = blockGeometry.TransformSizeUv[tx_depth]; - int transformWidth = chromaTransformSize.GetWidth(); - int transformHeight = chromaTransformSize.GetHeight(); - int transformBlockCount = (blockGeometry.BlockWidthUv * blockGeometry.BlockHeightUv) / - (transformWidth * transformHeight); Span blueCoefficients = coefficientBuffer.GetPlaneSpan(superblockIndex, Av1Plane.U); Span redCoefficients = coefficientBuffer.GetPlaneSpan(superblockIndex, Av1Plane.V); + Span blueTransformBlocks = + coefficientBuffer.GetTransformBlockSpan(superblockIndex, Av1Plane.U); + Span redTransformBlocks = + coefficientBuffer.GetTransformBlockSpan(superblockIndex, Av1Plane.V); + + int subsamplingX = colorConfig.SubSamplingX ? 1 : 0; + int subsamplingY = colorConfig.SubSamplingY ? 1 : 0; + Av1BlockSize chromaBlockSize = plane_bsize.GetSubsampled(colorConfig.SubSamplingX, colorConfig.SubSamplingY); + Point chromaBlockOrigin = GetChromaBlockOrigin(blockOrigin, subsamplingX, subsamplingY); + Av1TransformSize chromaTransformSize = frameHeader.LosslessArray[entropyCodingContext.MacroBlockModeInfo.Block.SegmentId] + ? Av1TransformSize.Size4x4 + : plane_bsize.GetMaxUvTransformSize(colorConfig.SubSamplingX, colorConfig.SubSamplingY); + int transformBlockWidth = chromaTransformSize.Get4x4WideCount(); + int transformBlockHeight = chromaTransformSize.Get4x4HighCount(); + int transformWidth = chromaTransformSize.GetWidth(); + int transformHeight = chromaTransformSize.GetHeight(); + Av1MacroBlockD macroBlock = entropyCodingContext.MacroBlock; + int maximumBlocksWide = chromaBlockSize.GetWidth(); + int maximumBlocksHigh = chromaBlockSize.GetHeight(); + if (macroBlock.ToRightEdge < 0) + { + maximumBlocksWide += macroBlock.ToRightEdge >> (3 + subsamplingX); + } - for (int transformBlockIndex = 0; transformBlockIndex < transformBlockCount; ++transformBlockIndex) + if (macroBlock.ToBottomEdge < 0) { - Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][transformBlockIndex]; - Point chromaOrigin = RoundUv(blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin) >> 1; - - // Both chroma planes share transform geometry but retain independent coefficient contexts. - Span coefficients = blueCoefficients[entropyCodingContext.CodedAreaSuperblockUv..]; - Av1TransformBlockContext blockContext = GetTransformBlockContexts( - Av1ComponentType.Chroma, - cb_dc_sign_level_coeff_na, - chromaOrigin, - blockGeometry.BlockSizeUv, - chromaTransformSize); - Av1TransformType chromaTransformType = blk_ptr.TransformBlocks[transformBlockIndex].TransformType[(int)Av1ComponentType.Chroma]; - int endOfBlockCb = blk_ptr.TransformBlocks[transformBlockIndex].NzCoefficientCount[1]; - int culLevelCb = writer.WriteCoefficients( - chromaTransformSize, - chromaTransformType, - intraLumaDir, - coefficients, - Av1ComponentType.Chroma, - blockContext, - (ushort)endOfBlockCb, - frameHeader.UseReducedTransformSet, - blk_ptr.FilterIntraMode); - - coefficients = redCoefficients[entropyCodingContext.CodedAreaSuperblockUv..]; - int endOfBlockCr = blk_ptr.TransformBlocks[transformBlockIndex].NzCoefficientCount[2]; - - blockContext = GetTransformBlockContexts( - Av1ComponentType.Chroma, - cr_dc_sign_level_coeff_na, - chromaOrigin, - blockGeometry.BlockSizeUv, - chromaTransformSize); - - int culLevelCr = writer.WriteCoefficients( - chromaTransformSize, - chromaTransformType, - intraLumaDir, - coefficients, - Av1ComponentType.Chroma, - blockContext, - (ushort)endOfBlockCr, - frameHeader.UseReducedTransformSet, - blk_ptr.FilterIntraMode); - - // Each plane publishes its packed context across the complete chroma transform edges. - cb_dc_sign_level_coeff_na.UnitModeWrite( - (byte)culLevelCb, - chromaOrigin, - new Size(transformWidth, transformHeight), - Av1NeighborArrayUnit.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); - - cr_dc_sign_level_coeff_na.UnitModeWrite( - (byte)culLevelCr, - chromaOrigin, - new Size(transformWidth, transformHeight), - Av1NeighborArrayUnit.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); - - entropyCodingContext.CodedAreaSuperblockUv += transformWidth * transformHeight; + maximumBlocksHigh += macroBlock.ToBottomEdge >> (3 + subsamplingY); + } + + maximumBlocksWide >>= Av1Constants.ModeInfoSizeLog2; + maximumBlocksHigh >>= Av1Constants.ModeInfoSizeLog2; + Av1BlockSize maximumUnitBlockSize = + Av1BlockSize.Block64x64.GetSubsampled(colorConfig.SubSamplingX, colorConfig.SubSamplingY); + int maximumUnitBlocksWide = Math.Min(maximumUnitBlockSize.Get4x4WideCount(), maximumBlocksWide); + int maximumUnitBlocksHigh = Math.Min(maximumUnitBlockSize.Get4x4HighCount(), maximumBlocksHigh); + + // Chroma follows the same bounded-region order after scaling both the block and frame edges to its plane. + for (int regionRow = 0; regionRow < maximumBlocksHigh; regionRow += maximumUnitBlocksHigh) + { + int unitHeight = Math.Min(maximumUnitBlocksHigh + regionRow, maximumBlocksHigh); + for (int regionColumn = 0; regionColumn < maximumBlocksWide; regionColumn += maximumUnitBlocksWide) + { + int unitWidth = Math.Min(maximumUnitBlocksWide + regionColumn, maximumBlocksWide); + for (int blockRow = regionRow; blockRow < unitHeight; blockRow += transformBlockHeight) + { + for (int blockColumn = regionColumn; blockColumn < unitWidth; blockColumn += transformBlockWidth) + { + int transformStateIndex = entropyCodingContext.CodedAreaSuperblockUv / + Av1EncoderCoefficientBuffer.TransformBlockUnitCoefficientCount; + ref Av1EncoderTransformBlockState blueTransformBlock = ref blueTransformBlocks[transformStateIndex]; + ref Av1EncoderTransformBlockState redTransformBlock = ref redTransformBlocks[transformStateIndex]; + Point chromaOrigin = chromaBlockOrigin + new Size( + blockColumn << Av1Constants.ModeInfoSizeLog2, + blockRow << Av1Constants.ModeInfoSizeLog2); + + // U and V share transform geometry and type while retaining independent coefficient and EOB state. + Span coefficients = blueCoefficients[entropyCodingContext.CodedAreaSuperblockUv..]; + Av1TransformBlockContext blockContext = GetTransformBlockContexts( + Av1ComponentType.Chroma, + cb_dc_sign_level_coeff_na, + chromaOrigin, + chromaBlockSize, + chromaTransformSize); + + Av1TransformType chromaTransformType = blueTransformBlock.TransformType; + int culLevelCb = writer.WriteCoefficients( + chromaTransformSize, + chromaTransformType, + intraLumaDir, + coefficients, + Av1ComponentType.Chroma, + blockContext, + blueTransformBlock.EndOfBlock, + frameHeader.UseReducedTransformSet, + blk_ptr.FilterIntraMode); + + coefficients = redCoefficients[entropyCodingContext.CodedAreaSuperblockUv..]; + blockContext = GetTransformBlockContexts( + Av1ComponentType.Chroma, + cr_dc_sign_level_coeff_na, + chromaOrigin, + chromaBlockSize, + chromaTransformSize); + + int culLevelCr = writer.WriteCoefficients( + chromaTransformSize, + chromaTransformType, + intraLumaDir, + coefficients, + Av1ComponentType.Chroma, + blockContext, + redTransformBlock.EndOfBlock, + frameHeader.UseReducedTransformSet, + blk_ptr.FilterIntraMode); + + cb_dc_sign_level_coeff_na.UnitModeWrite( + (byte)culLevelCb, + chromaOrigin, + new Size(transformWidth, transformHeight), + Av1NeighborArrayUnit.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); + + cr_dc_sign_level_coeff_na.UnitModeWrite( + (byte)culLevelCr, + chromaOrigin, + new Size(transformWidth, transformHeight), + Av1NeighborArrayUnit.UnitMask.Top | Av1NeighborArrayUnit.UnitMask.Left); + + entropyCodingContext.CodedAreaSuperblockUv += transformWidth * transformHeight; + } + } + } } } /// - /// Rounds a luma sample position down to the 8-sample alignment used before chroma subsampling. + /// Converts a luma origin to the shared 4x4 chroma-block origin for the active subsampling. /// - /// The luma sample position. - /// The aligned luma position. - private static Point RoundUv(Point point) => (point >> 3) << 3; + /// The luma sample position. + /// The horizontal chroma subsampling shift. + /// The vertical chroma subsampling shift. + /// The aligned origin in chroma samples. + private static Point GetChromaBlockOrigin(Point lumaOrigin, int subsamplingX, int subsamplingY) + => new( + (lumaOrigin.X >> (Av1Constants.ModeInfoSizeLog2 + subsamplingX)) << Av1Constants.ModeInfoSizeLog2, + (lumaOrigin.Y >> (Av1Constants.ModeInfoSizeLog2 + subsamplingY)) << Av1Constants.ModeInfoSizeLog2); /// /// Derives coefficient skip and DC-sign contexts from the transform block's above and left neighbors. @@ -1296,9 +1557,17 @@ internal partial class Av1TileWriter /// The tile symbol encoder. /// The block size. /// The block origin in samples. + /// The reusable macroblock edge and neighbor state. /// The encoder block state. /// A value indicating whether residual coefficients are omitted. - private static void WriteSegmentId(Av1PictureControlSet pcs, ref Av1SymbolEncoder writer, Av1BlockSize blockSize, Point blockOrigin, Av1EncoderBlockStruct block, bool skip) + private static void WriteSegmentId( + Av1PictureControlSet pcs, + ref Av1SymbolEncoder writer, + Av1BlockSize blockSize, + Point blockOrigin, + Av1MacroBlockD macroBlock, + ref Av1EncoderBlockStruct block, + bool skip) { ObuSegmentationParameters segmentation_params = pcs.Parent.FrameHeader.SegmentationParameters; if (!segmentation_params.Enabled) @@ -1306,7 +1575,7 @@ internal partial class Av1TileWriter return; } - int spatial_pred = GetSpatialSegmentationPrediction(pcs, block.MacroBlock, blockOrigin, out int cdf_num); + int spatial_pred = GetSpatialSegmentationPrediction(pcs, macroBlock, blockOrigin, out int cdf_num); if (skip) { // With segment-id-before-skip syntax, a skipped block inherits the spatial predictor without coding a residual ID. @@ -1398,12 +1667,12 @@ internal partial class Av1TileWriter /// Writes the block skip flag using the sum of available above and left skip states as its context. /// /// The tile symbol encoder. - /// The encoder block state. + /// The reusable macroblock edge and neighbor state. /// The skip value to write. - public static void EncodeSkipCoefficients(ref Av1SymbolEncoder writer, Av1EncoderBlockStruct block, bool skip) + public static void EncodeSkipCoefficients(ref Av1SymbolEncoder writer, Av1MacroBlockD macroBlock, bool skip) { - Av1MacroBlockModeInfo? above_mi = block.MacroBlock.AboveMacroBlock; - Av1MacroBlockModeInfo? left_mi = block.MacroBlock.LeftMacroBlock; + Av1MacroBlockModeInfo? above_mi = macroBlock.AboveMacroBlock; + Av1MacroBlockModeInfo? left_mi = macroBlock.LeftMacroBlock; int above_skip = (above_mi != null && above_mi.Block.Skip) ? 1 : 0; int left_skip = (left_mi != null && left_mi.Block.Skip) ? 1 : 0; writer.WriteSkip(skip, above_skip + left_skip); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs deleted file mode 100644 index c8322a1b82..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; - -/// -/// Stores the transform syntax and coefficient range for one AV1 transform unit. -/// -internal struct Av1TransformUnit -{ - /// - /// Stores the luma, blue-difference, and red-difference end-of-block positions. - /// - private InlineArray3 nzCoefficientCount; - - /// - /// Stores the luma and shared chroma transform types. - /// - private InlineArray2 transformType; - - /// - /// Gets the nonzero-coefficient count for each color plane. - /// - [UnscopedRef] - public Span NzCoefficientCount => this.nzCoefficientCount; - - /// - /// Gets the transform type selected for each color plane. - /// - [UnscopedRef] - public Span TransformType => this.transformType; - - /// - /// Stores the three per-plane coefficient counts inline. - /// - [InlineArray(3)] - private struct InlineArray3 - { - private T element; - } - - /// - /// Stores the luma and shared chroma transform types inline. - /// - [InlineArray(Av1Constants.PlaneTypeCount)] - private struct InlineArray2 - { - private T element; - } -} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index 358cfb63cd..ee2e035547 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -102,8 +102,7 @@ public class Av1CoefficientsEntropyTests TilesInfo = new ObuTileGroupHeader() }, FrameHeader = new ObuFrameHeader(), - PreviousQIndex = [], - SuperblockGeometry = [] + PreviousQIndex = [] }, SegmentationNeighborMap = [], ModeInfoGrid = new Av1ModeInfo[16], @@ -166,39 +165,37 @@ public class Av1CoefficientsEntropyTests } [Fact] - public void EncoderBlockInlineStateSupportsEveryTransformWithoutTraversalAllocations() + public void EncoderBlocksKeepInlineModeStateWithoutPerBlockAllocations() { - Av1EncoderBlockStruct block = new() { MacroBlock = CreateMacroBlock() }; + Av1EncoderBlockStruct[] blocks = new Av1EncoderBlockStruct[2]; long before = GC.GetAllocatedBytesForCurrentThread(); - Span transforms = block.TransformBlocks; - transforms[^1].NzCoefficientCount[2] = 17; - transforms[^1].TransformType[(int)Av1PlaneType.Uv] = Av1TransformType.VerticalAdst; + ref Av1EncoderBlockStruct block = ref blocks[1]; block.PaletteSize[0] = 3; block.PaletteSize[1] = 5; block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] = -2; block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] = 3; long allocated = GC.GetAllocatedBytesForCurrentThread() - before; - Assert.Equal(Av1Constants.MaxTransformUnitCount, transforms.Length); - Assert.Equal(17, block.TransformBlocks[^1].NzCoefficientCount[2]); - Assert.Equal(Av1TransformType.VerticalAdst, block.TransformBlocks[^1].TransformType[(int)Av1PlaneType.Uv]); - Assert.Equal(3, block.PaletteSize[0]); - Assert.Equal(5, block.PaletteSize[1]); - Assert.Equal(-2, block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y]); - Assert.Equal(3, block.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv]); + Assert.Equal(3, blocks[1].PaletteSize[0]); + Assert.Equal(5, blocks[1].PaletteSize[1]); + Assert.Equal(-2, blocks[1].PredictionUnit.AngleDelta[(int)Av1PlaneType.Y]); + Assert.Equal(3, blocks[1].PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv]); Assert.Equal(0, allocated); } [Theory] - [InlineData(false, 6, 4096, 1024, 6144, 36864L)] - [InlineData(true, 2, 16384, 4096, 24576, 49152L)] + [InlineData(false, 6, 4096, 1024, 6144, 256, 64, 384, 36864L)] + [InlineData(true, 2, 16384, 4096, 24576, 1024, 256, 1536, 49152L)] public void EncoderCoefficientBufferMatchesLibaom420SuperblockLayout( bool use128x128Superblock, int expectedSuperblockCount, int expectedLumaCount, int expectedChromaCount, int expectedCoefficientsPerSuperblock, + int expectedLumaTransformBlockCount, + int expectedChromaTransformBlockCount, + int expectedTransformBlocksPerSuperblock, long expectedTotalCoefficientCount) { ObuSequenceHeader sequenceHeader = new() { Use128x128Superblock = use128x128Superblock }; @@ -216,10 +213,16 @@ public class Av1CoefficientsEntropyTests Assert.Equal(expectedLumaCount, coefficients.LumaCoefficientCount); Assert.Equal(expectedChromaCount, coefficients.ChromaCoefficientCount); Assert.Equal(expectedCoefficientsPerSuperblock, coefficients.CoefficientsPerSuperblock); + Assert.Equal(expectedLumaTransformBlockCount, coefficients.LumaTransformBlockCount); + Assert.Equal(expectedChromaTransformBlockCount, coefficients.ChromaTransformBlockCount); + Assert.Equal(expectedTransformBlocksPerSuperblock, coefficients.TransformBlocksPerSuperblock); Assert.Equal(expectedTotalCoefficientCount, coefficients.TotalCoefficientCount); Assert.Equal(expectedLumaCount, coefficients.GetPlaneSpan(0, Av1Plane.Y).Length); Assert.Equal(expectedChromaCount, coefficients.GetPlaneSpan(0, Av1Plane.U).Length); Assert.Equal(expectedChromaCount, coefficients.GetPlaneSpan(0, Av1Plane.V).Length); + Assert.Equal(expectedLumaTransformBlockCount, coefficients.GetTransformBlockSpan(0, Av1Plane.Y).Length); + Assert.Equal(expectedChromaTransformBlockCount, coefficients.GetTransformBlockSpan(0, Av1Plane.U).Length); + Assert.Equal(expectedChromaTransformBlockCount, coefficients.GetTransformBlockSpan(0, Av1Plane.V).Length); } [Fact] @@ -240,11 +243,342 @@ public class Av1CoefficientsEntropyTests coefficients.GetPlaneSpan(0, Av1Plane.U)[0] = 22; coefficients.GetPlaneSpan(0, Av1Plane.V)[0] = 33; coefficients.GetPlaneSpan(1, Av1Plane.Y)[0] = 44; + coefficients.GetTransformBlockSpan(0, Av1Plane.Y)[0].EndOfBlock = 55; + coefficients.GetTransformBlockSpan(0, Av1Plane.U)[0].EndOfBlock = 66; + coefficients.GetTransformBlockSpan(0, Av1Plane.V)[0].EndOfBlock = 77; + coefficients.GetTransformBlockSpan(1, Av1Plane.Y)[0].EndOfBlock = 88; Assert.Equal(11, coefficients.GetPlaneSpan(0, Av1Plane.Y)[0]); Assert.Equal(22, coefficients.GetPlaneSpan(0, Av1Plane.U)[0]); Assert.Equal(33, coefficients.GetPlaneSpan(0, Av1Plane.V)[0]); Assert.Equal(44, coefficients.GetPlaneSpan(1, Av1Plane.Y)[0]); + Assert.Equal(55, coefficients.GetTransformBlockSpan(0, Av1Plane.Y)[0].EndOfBlock); + Assert.Equal(66, coefficients.GetTransformBlockSpan(0, Av1Plane.U)[0].EndOfBlock); + Assert.Equal(77, coefficients.GetTransformBlockSpan(0, Av1Plane.V)[0].EndOfBlock); + Assert.Equal(88, coefficients.GetTransformBlockSpan(1, Av1Plane.Y)[0].EndOfBlock); + } + + [Fact] + public void EncoderLumaTraversalRepresentsAllTransformsIn128x128Block() + { + Av1PictureControlSet picture = CreateEncoderPicture(32, 32, use128x128Superblock: true); + Av1MacroBlockModeInfo modeInfo = picture.ModeInfoAllocation[0].MacroBlockModeInfo; + modeInfo.Block.BlockSize = Av1BlockSize.Block128x128; + modeInfo.Block.TransformSize = Av1TransformSize.Size16x16; + modeInfo.Block.SegmentId = 0; + Av1TileInfo tile = new(0, 0, picture.Parent.FrameHeader); + Av1TileWriter.Av1EntropyCodingContext context = new() + { + MacroBlock = new Av1MacroBlockD { Tile = tile }, + MacroBlockModeInfo = modeInfo, + SuperblockOrigin = Point.Empty + }; + + using Av1NeighborArrayUnit luma = new( + Configuration.Default, + leftSize: 128, + topSize: 128, + topLeftSize: 256) + { + GranularityNormalLog2 = Av1Constants.ModeInfoSizeLog2, + GranularityTopLeftLog2 = Av1Constants.ModeInfoSizeLog2 + }; + + using Av1EncoderCoefficientBuffer coefficients = new( + Configuration.Default, + picture.Sequence.SequenceHeader, + width: 128, + height: 128); + + Span transformBlocks = + coefficients.GetTransformBlockSpan(0, Av1Plane.Y); + transformBlocks.Fill(new Av1EncoderTransformBlockState { TransformType = Av1TransformType.Identity }); + + Av1EncoderBlockStruct block = default; + Av1SymbolEncoder writer = new(Configuration.Default, 4096, BaseQIndex); + Av1TileWriter.EncodeTransformCoefficientsY( + picture, + context, + ref writer, + ref block, + Point.Empty, + Av1PredictionMode.DC, + Av1BlockSize.Block128x128, + coefficients, + superblockIndex: 0, + luma); + + writer.Dispose(); + + int visitedTransformCount = 0; + for (int index = 0; index < transformBlocks.Length; index++) + { + if ((index % 16) == 0) + { + Assert.Equal(Av1TransformType.DctDct, transformBlocks[index].TransformType); + visitedTransformCount++; + } + else + { + Assert.Equal(Av1TransformType.Identity, transformBlocks[index].TransformType); + } + } + + Assert.Equal(64, visitedTransformCount); + Assert.Equal(16384, context.CodedAreaSuperblock); + } + + [Fact] + public void SegmentationUpdateUsesModeInfoUnits() + { + Av1PictureControlSet picture = CreateEncoderPicture(8, 8); + picture.SegmentationNeighborMap = new byte[64]; + + picture.UpdateSegmentation(Av1BlockSize.Block16x8, new Point(8, 12), segmentId: 5); + + for (int row = 0; row < 8; row++) + { + for (int column = 0; column < 8; column++) + { + byte expected = row is 3 or 4 && column >= 2 && column < 6 ? (byte)5 : (byte)0; + Assert.Equal(expected, picture.SegmentationNeighborMap[(row * 8) + column]); + } + } + } + + [Theory] + [InlineData((int)Av1PartitionType.None, 24, 24)] + [InlineData((int)Av1PartitionType.Horizontal, 24, 28)] + [InlineData((int)Av1PartitionType.Vertical, 28, 24)] + [InlineData((int)Av1PartitionType.Split, 0, 0)] + [InlineData((int)Av1PartitionType.HorizontalA, 24, 28)] + [InlineData((int)Av1PartitionType.HorizontalB, 28, 28)] + [InlineData((int)Av1PartitionType.VerticalA, 28, 24)] + [InlineData((int)Av1PartitionType.VerticalB, 28, 28)] + [InlineData((int)Av1PartitionType.Horizontal4, 24, 30)] + [InlineData((int)Av1PartitionType.Vertical4, 30, 24)] + public void PartitionContextUpdatesMatchLibaomExtendedPartitionRules( + int partitionValue, + byte expectedAbove, + byte expectedLeft) + { + using Av1NeighborArrayUnit neighbors = new( + Configuration.Default, + leftSize: 16, + topSize: 16, + topLeftSize: 32) + { + GranularityNormalLog2 = 2, + GranularityTopLeftLog2 = 2 + }; + + Av1PartitionType partition = (Av1PartitionType)partitionValue; + Av1BlockSize blockSize = Av1BlockSize.Block32x32; + Av1BlockSize subSize = partition.GetBlockSubSize(blockSize); + + Av1TileWriter.UpdatePartitionContexts( + neighbors, + new Point(8, 12), + subSize, + blockSize, + partition); + + for (int index = 0; index < 16; index++) + { + byte above = index is >= 2 and < 10 ? expectedAbove : (byte)0; + byte left = index is >= 3 and < 11 ? expectedLeft : (byte)0; + Assert.Equal(above, neighbors.Top[index].Above); + Assert.Equal(left, neighbors.Left[index].Left); + } + } + + [Fact] + public void EightByEightSplitPublishesFourByFourPartitionContexts() + { + using Av1NeighborArrayUnit neighbors = new( + Configuration.Default, + leftSize: 4, + topSize: 4, + topLeftSize: 8) + { + GranularityNormalLog2 = 2, + GranularityTopLeftLog2 = 2 + }; + + Av1TileWriter.UpdatePartitionContexts( + neighbors, + new Point(4, 4), + Av1BlockSize.Block4x4, + Av1BlockSize.Block8x8, + Av1PartitionType.Split); + + Assert.Equal(31, neighbors.Top[1].Above); + Assert.Equal(31, neighbors.Top[2].Above); + Assert.Equal(31, neighbors.Left[1].Left); + Assert.Equal(31, neighbors.Left[2].Left); + } + + [Fact] + public void EncoderModeInfoEdgesUseFourByFourUnits() + { + Av1PictureControlSet picture = CreateEncoderPicture(6, 5); + Av1TileInfo tile = new(0, 0, picture.Parent.FrameHeader); + Av1MacroBlockD macroBlock = new() { Tile = tile }; + Point position = new(2, 3); + + Av1TileWriter.SetModeInfoRowAndColumn( + picture, + macroBlock, + tile, + position, + Av1BlockSize.Block16x8, + picture.ModeInfoStride, + picture.Parent.Common.ModeInfoRowCount, + picture.Parent.Common.ModeInfoColumnCount); + + Assert.Equal(-96, macroBlock.ToTopEdge); + Assert.Equal(0, macroBlock.ToBottomEdge); + Assert.Equal(-64, macroBlock.ToLeftEdge); + Assert.Equal(0, macroBlock.ToRightEdge); + Assert.Same(picture.ModeInfoGrid[14].MacroBlockModeInfo, macroBlock.AboveMacroBlock); + Assert.Same(picture.ModeInfoGrid[19].MacroBlockModeInfo, macroBlock.LeftMacroBlock); + } + + [Fact] + public void CdefUsesLibaomUnitIndexAndFirstBlockStrength() + { + Av1PictureControlSet picture = CreateEncoderPicture(32, 32, use128x128Superblock: true); + picture.Parent.FrameHeader.CdefParameters.BitCount = 2; + picture.ModeInfoGrid[16].MacroBlockModeInfo.CdefStrength = 3; + picture.ModeInfoGrid[20].MacroBlockModeInfo.CdefStrength = 1; + using Av1SymbolEncoder writer = new(Configuration.Default, 16, BaseQIndex); + + Av1TileWriter.WriteCdef( + picture.Sequence, + picture, + writer, + tileIndex: 0, + skip: false, + modeInfoPosition: new Point(20, 4)); + + Assert.Equal(new[] { -1, 3, -1, -1 }, picture.CdefPreset[0]); + } + + [Fact] + public void SuperblockWriterTraversesSplitTreeFromAbsoluteOrigin() + { + Av1PictureControlSet picture = CreateEncoderPicture(32, 16); + picture.Sequence.SequenceHeader.ColorConfig.IsMonochrome = true; + picture.Parent.FrameHeader.CodedLossless = true; + using Av1NeighborArrayUnit partitions = new( + Configuration.Default, + leftSize: 16, + topSize: 32, + topLeftSize: 48) + { + GranularityNormalLog2 = 2, + GranularityTopLeftLog2 = 2 + }; + + using Av1NeighborArrayUnit luma = new( + Configuration.Default, + leftSize: 16, + topSize: 32, + topLeftSize: 48) + { + GranularityNormalLog2 = 2, + GranularityTopLeftLog2 = 2 + }; + + using Av1NeighborArrayUnit red = new( + Configuration.Default, + leftSize: 16, + topSize: 32, + topLeftSize: 48) + { + GranularityNormalLog2 = 2, + GranularityTopLeftLog2 = 2 + }; + + using Av1NeighborArrayUnit blue = new( + Configuration.Default, + leftSize: 16, + topSize: 32, + topLeftSize: 48) + { + GranularityNormalLog2 = 2, + GranularityTopLeftLog2 = 2 + }; + + picture.PartitionContexts = [partitions]; + picture.LuminanceDcSignLevelCoefficientNeighbors = [luma]; + picture.CrDcSignLevelCoefficientNeighbors = [red]; + picture.CbDcSignLevelCoefficientNeighbors = [blue]; + Av1TileInfo tile = new(0, 0, picture.Parent.FrameHeader); + Point[] blockPositions = [new(16, 0), new(24, 0), new(16, 8), new(24, 8)]; + Av1EncoderBlockStruct[] blocks = new Av1EncoderBlockStruct[blockPositions.Length]; + for (int index = 0; index < blockPositions.Length; index++) + { + Point position = blockPositions[index]; + Av1EncoderBlockModeInfo blockMode = picture.ModeInfoAllocation[ + (position.Y * picture.ModeInfoStride) + position.X].MacroBlockModeInfo.Block; + + blockMode.BlockSize = Av1BlockSize.Block32x32; + blockMode.Skip = true; + blockMode.Mode = Av1PredictionMode.DC; + blockMode.UvMode = Av1ChromaPredictionMode.DC; + blocks[index] = new Av1EncoderBlockStruct { HasChroma = false }; + } + + Av1Superblock superblock = new() + { + FinalBlocks = blocks, + TileInfo = tile, + CodingUnitPartitionTypes = + [ + Av1PartitionType.Split, + Av1PartitionType.None, + Av1PartitionType.None, + Av1PartitionType.None, + Av1PartitionType.None + ], + Index = 1 + }; + Av1TileWriter.Av1EntropyCodingContext context = new() + { + MacroBlock = new Av1MacroBlockD { Tile = tile }, + MacroBlockModeInfo = picture.ModeInfoAllocation[16].MacroBlockModeInfo, + SuperblockOrigin = new Point(64, 0) + }; + using Av1EncoderCoefficientBuffer coefficients = new( + Configuration.Default, + picture.Sequence.SequenceHeader, + width: 128, + height: 64); + + Av1SymbolEncoder writer = new(Configuration.Default, 512, BaseQIndex); + + Av1TileWriter.WriteSuperblock( + picture, + context, + ref writer, + superblock, + coefficients, + tileIndex: 0); + + writer.Dispose(); + + Assert.Equal(4096, context.CodedAreaSuperblock); + Assert.Equal(0, context.CodedAreaSuperblockUv); + for (int index = 0; index < partitions.Top.Length; index++) + { + Assert.Equal(index < 16 ? 0 : 24, partitions.Top[index].Above); + } + + for (int index = 0; index < partitions.Left.Length; index++) + { + Assert.Equal(24, partitions.Left[index].Left); + } } [Fact] @@ -262,7 +596,7 @@ public class Av1CoefficientsEntropyTests Av1TransformInfo transformInfo = new(transformSize, 0, 0); int[] aboveContexts = new int[1]; int[] leftContexts = new int[1]; - Av1TransformBlockContext transformBlockContext = new(); + Av1TransformBlockContext transformBlockContext = default; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Span coefficientsBuffer = [1, 2, 3, 4, 5]; @@ -332,7 +666,7 @@ public class Av1CoefficientsEntropyTests Av1TransformInfo transformInfo = new(transformSize, 0, 0); int[] aboveContexts = new int[1]; int[] leftContexts = new int[1]; - Av1TransformBlockContext transformBlockContext = new(); + Av1TransformBlockContext transformBlockContext = default; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Span coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; @@ -407,7 +741,7 @@ public class Av1CoefficientsEntropyTests Av1TransformInfo transformInfo = new(transformSize, 0, 0); int[] aboveContexts = new int[transformSize.Get4x4WideCount()]; int[] leftContexts = new int[transformSize.Get4x4HighCount()]; - Av1TransformBlockContext transformBlockContext = new(); + Av1TransformBlockContext transformBlockContext = default; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Span coefficientsBuffer = Enumerable.Range(0, blockSize.GetHeight() * blockSize.GetWidth()).ToArray(); @@ -461,6 +795,62 @@ public class Av1CoefficientsEntropyTests } }; + private static Av1PictureControlSet CreateEncoderPicture( + int modeInfoColumnCount, + int modeInfoRowCount, + bool use128x128Superblock = false) + { + ObuTileGroupHeader tiles = new() + { + TileColumnCount = 1, + TileRowCount = 1 + }; + + tiles.TileColumnStartModeInfo[1] = modeInfoColumnCount; + tiles.TileRowStartModeInfo[1] = modeInfoRowCount; + ObuSequenceHeader sequenceHeader = new() { Use128x128Superblock = use128x128Superblock }; + ObuFrameHeader frameHeader = new() + { + ModeInfoColumnCount = modeInfoColumnCount, + ModeInfoRowCount = modeInfoRowCount, + TilesInfo = tiles + }; + + Av1ModeInfo[] modeInfoGrid = new Av1ModeInfo[modeInfoColumnCount * modeInfoRowCount]; + for (int index = 0; index < modeInfoGrid.Length; index++) + { + modeInfoGrid[index] = CreateModeInfo(Av1PredictionMode.DC); + } + + return new Av1PictureControlSet + { + PartitionContexts = [], + LuminanceDcSignLevelCoefficientNeighbors = [], + CrDcSignLevelCoefficientNeighbors = [], + CbDcSignLevelCoefficientNeighbors = [], + TransformFunctionContexts = [], + Sequence = new Av1SequenceControlSet { SequenceHeader = sequenceHeader }, + Parent = new Av1PictureParentControlSet + { + Common = new Av1EncoderCommon + { + ModeInfoColumnCount = modeInfoColumnCount, + ModeInfoRowCount = modeInfoRowCount, + ModeInfoStride = modeInfoColumnCount, + FrameSize = new ObuFrameSize(), + TilesInfo = tiles + }, + FrameHeader = frameHeader, + PreviousQIndex = [] + }, + SegmentationNeighborMap = [], + ModeInfoGrid = modeInfoGrid, + ModeInfoAllocation = modeInfoGrid, + ModeInfoStride = modeInfoColumnCount, + CdefPreset = [[-1, -1, -1, -1]] + }; + } + private static Av1MacroBlockD CreateMacroBlock() { ObuTileGroupHeader tiles = new()