From 8abff74c56df8ad4dade5153f752097e1e67e036 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 2 Sep 2026 15:05:25 +1000 Subject: [PATCH] Pack AV1 encoder mode information --- HEIF_IMPLEMENTATION_PLAN.md | 2 +- .../Av1/Tiling/Av1EncoderBlockModeInfo.cs | 70 ++++++++-- .../Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs | 37 +++--- .../Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs | 14 +- .../Formats/Heif/Av1/Tiling/Av1ModeInfo.cs | 15 --- .../Heif/Av1/Tiling/Av1PictureControlSet.cs | 51 +++++--- .../Formats/Heif/Av1/Tiling/Av1TileWriter.cs | 41 ++---- .../Heif/Av1/Av1CoefficientsEntropyTests.cs | 123 ++++++++++++------ 8 files changed, 220 insertions(+), 133 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 654dcce5dc..931ff88356 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -828,7 +828,7 @@ Encoder verification contract: - [~] Current-libaom `av1_quantize_fp_no_qmatrix` arithmetic is implemented as a closed generic forward-quantizer family with Vector512, Vector256, Vector128, and scalar paths, raster-order output, coded 64-point coefficient limits, and scan-order EOB selection. Transform search, coefficient optimization, and lossless behavior remain. - [ ] 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. +- [~] Encoder mode information now uses a frame-owned integer alias grid over a packed 8-byte value allocation, matching current libaom's `mi_grid_base` and `mi_alloc` relationship without a managed object or reference per 4x4 entry. Every coded 4x4 cell covered by square, rectangular, or clipped edge blocks maps to its owning allocation entry before context-dependent symbols are written. At 4K, mode values occupy about 4.0 MiB and the alias grid about 2.0 MiB. Packed syntax, relative neighbor lookup, full block mapping, writer traversal, entropy, and OBU coverage pass 1,947 of 1,947 direct net11 VSTest cases in Release; complete mode decision still remains. - [~] 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 transform-block boundary can populate the owner's quantized coefficient and state slices directly; mode-decision traversal still needs to select and invoke it. - [~] 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, fixed intra transform-size symbols use the reference depth and neighbor contexts, 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 and mode analysis still need to populate these retained decisions; variable inter-transform syntax remains part of later inter-frame support. diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs index 98f981d070..beb3df8ca7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs @@ -9,50 +9,100 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; /// /// Stores encoder-selected prediction, transform, skip, and palette state for one block. /// -internal class Av1EncoderBlockModeInfo +internal struct Av1EncoderBlockModeInfo { + private const byte SkipMask = 1 << 0; + private const byte SkipModeMask = 1 << 1; + private const byte IntraBlockCopyMask = 1 << 2; + + // Every stored syntax value has an AV1-defined range below 256. Byte fields and one shared flag byte + // keep the frame-wide mode allocation compact without losing any representable encoder state. + private byte blockSize; + private byte partitionType; + private byte flags; + private byte segmentId; + private byte transformSize; + private byte mode; + private byte uvMode; + /// /// Gets or sets the encoded block size. /// - public Av1BlockSize BlockSize { get; set; } + public Av1BlockSize BlockSize + { + readonly get => (Av1BlockSize)this.blockSize; + set => this.blockSize = (byte)value; + } /// /// Gets or sets the partition type that produced the block. /// - public Av1PartitionType PartitionType { get; set; } + public Av1PartitionType PartitionType + { + readonly get => (Av1PartitionType)this.partitionType; + set => this.partitionType = (byte)value; + } /// /// Gets or sets a value indicating whether residual coefficients are omitted for the block. /// - public bool Skip { get; set; } + public bool Skip + { + readonly get => (this.flags & SkipMask) != 0; + set => this.flags = value ? (byte)(this.flags | SkipMask) : (byte)(this.flags & ~SkipMask); + } /// /// Gets or sets a value indicating whether compound skip mode is selected. /// - public bool SkipMode { get; set; } + public bool SkipMode + { + readonly get => (this.flags & SkipModeMask) != 0; + set => this.flags = value ? (byte)(this.flags | SkipModeMask) : (byte)(this.flags & ~SkipModeMask); + } /// /// Gets or sets a value indicating whether intra block copy is selected. /// - public bool UseIntraBlockCopy { get; set; } + public bool UseIntraBlockCopy + { + readonly get => (this.flags & IntraBlockCopyMask) != 0; + set => this.flags = value ? (byte)(this.flags | IntraBlockCopyMask) : (byte)(this.flags & ~IntraBlockCopyMask); + } /// /// Gets or sets the segmentation identifier assigned to the block. /// - public int SegmentId { get; set; } + public int SegmentId + { + readonly get => this.segmentId; + set => this.segmentId = (byte)value; + } /// /// Gets or sets the luma transform size selected for the block. /// - public Av1TransformSize TransformSize { get; set; } + public Av1TransformSize TransformSize + { + readonly get => (Av1TransformSize)this.transformSize; + set => this.transformSize = (byte)value; + } /// /// Gets or sets the luma prediction mode written for the block. /// - public Av1PredictionMode Mode { get; set; } + public Av1PredictionMode Mode + { + readonly get => (Av1PredictionMode)this.mode; + set => this.mode = (byte)value; + } /// /// Gets or sets the chroma prediction mode written for the block. /// - public Av1ChromaPredictionMode UvMode { get; set; } + public Av1ChromaPredictionMode UvMode + { + readonly get => (Av1ChromaPredictionMode)this.uvMode; + set => this.uvMode = (byte)value; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs index 4f17311c28..33598f85ec 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs @@ -4,14 +4,19 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; /// -/// Holds decoder-side macroblock edges, plane state, and neighboring mode information. +/// Holds encoder-side macroblock edges and neighboring mode information. /// internal class Av1MacroBlockD { /// - /// Stores the frame's mode-information reference grid. + /// Stores the frame's mode-information allocation-index grid. /// - private Av1ModeInfo[] modeInfoGrid = []; + private Memory modeInfoGrid; + + /// + /// Stores the frame's contiguous mode-information values. + /// + private Memory modeInfoAllocation; /// /// Stores the current block's linear position in . @@ -33,16 +38,6 @@ internal class Av1MacroBlockD /// public bool IsLeftAvailable { get; set; } - /// - /// Gets or sets the above macroblock mode information, when available. - /// - public Av1MacroBlockModeInfo? AboveMacroBlock { get; set; } - - /// - /// Gets or sets the left macroblock mode information, when available. - /// - public Av1MacroBlockModeInfo? LeftMacroBlock { get; set; } - /// /// Gets or sets the row stride of the frame mode-information map. /// @@ -69,13 +64,15 @@ internal class Av1MacroBlockD public int ToRightEdge { get; set; } /// - /// Selects the current entry in the frame-owned mode-information reference grid. + /// Selects the current entry in the frame-owned mode-information grid. /// - /// The frame-owned mode-information reference grid. + /// The frame-owned mode-information allocation-index grid. + /// The frame-owned contiguous mode-information values. /// The current block's linear grid index. - public void SetModeInfoGrid(Av1ModeInfo[] grid, int index) + public void SetModeInfoGrid(Memory grid, Memory allocation, int index) { this.modeInfoGrid = grid; + this.modeInfoAllocation = allocation; this.modeInfoIndex = index; } @@ -83,6 +80,10 @@ internal class Av1MacroBlockD /// Gets a mode-information entry relative to the current block. /// /// The signed linear offset from the current block. - /// The mapped neighboring or current entry. - public Av1ModeInfo GetRelativeModeInfo(int offset) => this.modeInfoGrid[this.modeInfoIndex + offset]; + /// A reference to the mapped neighboring or current entry. + public ref Av1MacroBlockModeInfo GetRelativeModeInfo(int offset) + { + int allocationIndex = this.modeInfoGrid.Span[this.modeInfoIndex + offset]; + return ref this.modeInfoAllocation.Span[allocationIndex]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs index f8e9c13989..d39355b94b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs @@ -6,15 +6,21 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; /// /// Stores the encoder's selected modes and references for an AV1 macroblock. /// -internal class Av1MacroBlockModeInfo +internal struct Av1MacroBlockModeInfo { + private byte cdefStrength; + /// - /// Gets or sets the prediction, transform, and segmentation decisions for the block. + /// Stores the prediction, transform, and segmentation decisions for the block. /// - public required Av1EncoderBlockModeInfo Block { get; set; } + public Av1EncoderBlockModeInfo Block; /// /// Gets or sets the constrained directional enhancement filter strength for the block. /// - public int CdefStrength { get; set; } + public int CdefStrength + { + readonly get => this.cdefStrength; + set => this.cdefStrength = (byte)value; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.cs deleted file mode 100644 index 3e6c325c09..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ModeInfo.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; - -/// -/// Stores the decoded prediction, segmentation, skip, and transform state for an AV1 mode-information block. -/// -internal class Av1ModeInfo -{ - /// - /// Gets or sets the macroblock mode information associated with this map entry. - /// - public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; set; } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs index aad7c366f8..dbc2010690 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs @@ -49,14 +49,14 @@ internal class Av1PictureControlSet public required byte[] SegmentationNeighborMap { get; set; } /// - /// Gets or sets the frame grid that maps each 4x4 position to its mode-information entry. + /// Gets or sets the frame grid that maps each 4x4 position to its mode-information allocation index. /// - public required Av1ModeInfo[] ModeInfoGrid { get; set; } + public required Memory ModeInfoGrid { get; set; } /// /// Gets or sets the contiguous mode-information storage addressed by . /// - public required Av1ModeInfo[] ModeInfoAllocation { get; set; } + public required Memory ModeInfoAllocation { get; set; } /// /// Gets or sets the row stride of in 4x4 mode-information units. @@ -77,27 +77,48 @@ internal class Av1PictureControlSet /// Gets the mode-information entry mapped to a frame position. /// /// The frame position in 4x4 mode-information units. - /// The mapped mode-information entry. - public Av1ModeInfo GetFromModeInfoGrid(Point position) - => this.ModeInfoGrid[(position.Y * this.ModeInfoStride) + position.X]; + /// A reference to the mapped mode-information entry. + public ref Av1MacroBlockModeInfo GetFromModeInfoGrid(Point position) + { + int gridOffset = (position.Y * this.ModeInfoStride) + position.X; + int allocationOffset = this.ModeInfoGrid.Span[gridOffset]; + return ref this.ModeInfoAllocation.Span[allocationOffset]; + } /// - /// Gets the macroblock mode information at a block origin and refreshes its grid mapping. + /// Gets the macroblock mode information allocated at a block origin. /// /// The block position in 4x4 mode-information units. - /// The macroblock mode information at the origin. - public Av1MacroBlockModeInfo GetMacroBlockModeInfo(Point modeInfoPosition) + /// A reference to the macroblock mode information at the origin. + public ref Av1MacroBlockModeInfo GetMacroBlockModeInfo(Point modeInfoPosition) { int modeInfoStride = this.ModeInfoStride; - int offset = (modeInfoPosition.Y * modeInfoStride) + modeInfoPosition.X; + int disallow4x4 = this.Disallow4x4AllFrames ? 1 : 0; + int allocationOffset = ((modeInfoPosition.Y >> disallow4x4) * (modeInfoStride >> disallow4x4)) + (modeInfoPosition.X >> disallow4x4); + return ref this.ModeInfoAllocation.Span[allocationOffset]; + } - // The grid stores references into the contiguous mode-info allocation, matching libaom's - // mi_grid_base/mi_alloc ownership without copying a tail for every coded block. + /// + /// Maps every coded 4x4 position covered by a block to the block's mode-information allocation entry. + /// + /// The block position in 4x4 mode-information units. + /// The coded block size. + public void MapModeInfoBlock(Point modeInfoPosition, Av1BlockSize blockSize) + { + int modeInfoStride = this.ModeInfoStride; int disallow4x4 = this.Disallow4x4AllFrames ? 1 : 0; int allocationOffset = ((modeInfoPosition.Y >> disallow4x4) * (modeInfoStride >> disallow4x4)) + (modeInfoPosition.X >> disallow4x4); - Av1ModeInfo modeInfo = this.ModeInfoAllocation[allocationOffset]; - this.ModeInfoGrid[offset] = modeInfo; - return modeInfo.MacroBlockModeInfo; + int mappedWidth = Math.Min(this.Parent.Common.ModeInfoColumnCount - modeInfoPosition.X, blockSize.Get4x4WideCount()); + int mappedHeight = Math.Min(this.Parent.Common.ModeInfoRowCount - modeInfoPosition.Y, blockSize.Get4x4HighCount()); + Span grid = this.ModeInfoGrid.Span; + + // Libaom's pointer grid aliases every covered 4x4 entry to one mode-info allocation. Integer indices keep + // the same aliasing without one managed object and one managed reference per grid position. + for (int row = 0; row < mappedHeight; row++) + { + int gridOffset = ((modeInfoPosition.Y + row) * modeInfoStride) + modeInfoPosition.X; + grid.Slice(gridOffset, mappedWidth).Fill(allocationOffset); + } } /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs index 1921f90b9d..b015fd7f2f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs @@ -633,9 +633,10 @@ internal partial class Av1TileWriter int mi_col = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2; int mi_stride = pcs.Parent.Common.ModeInfoStride; Point modeInfoPosition = new(mi_col, mi_row); - Av1MacroBlockModeInfo macroBlockModeInfo = pcs.GetMacroBlockModeInfo(modeInfoPosition); + ref Av1MacroBlockModeInfo macroBlockModeInfo = ref pcs.GetMacroBlockModeInfo(modeInfoPosition); Av1BlockSize blockSize = macroBlockModeInfo.Block.BlockSize; bool skipWritingCoefficients = macroBlockModeInfo.Block.Skip; + pcs.MapModeInfoBlock(modeInfoPosition, blockSize); entropyCodingContext.MacroBlockModeInfo = macroBlockModeInfo; Av1MacroBlockD macroBlock = entropyCodingContext.MacroBlock; @@ -888,12 +889,12 @@ internal partial class Av1TileWriter if (xd.IsLeftAvailable) { // Key-frame neighbors are intra blocks, so their luma modes directly select the context class. - intraLumaLeftMode = xd.GetRelativeModeInfo(-1).MacroBlockModeInfo.Block.Mode; + intraLumaLeftMode = xd.GetRelativeModeInfo(-1).Block.Mode; } if (xd.IsUpAvailable) { - intraLumaTopMode = xd.GetRelativeModeInfo(-xd.ModeInfoStride).MacroBlockModeInfo.Block.Mode; + intraLumaTopMode = xd.GetRelativeModeInfo(-xd.ModeInfoStride).Block.Mode; } above_ctx = IntraModeContextLookup[(int)intraLumaTopMode]; @@ -1025,7 +1026,7 @@ internal partial class Av1TileWriter Av1NeighborArrayUnit cr_dc_sign_level_coeff_na = pcs.CrDcSignLevelCoefficientNeighbors[tile_idx]; Av1NeighborArrayUnit cb_dc_sign_level_coeff_na = pcs.CbDcSignLevelCoefficientNeighbors[tile_idx]; Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2; - Av1MacroBlockModeInfo mbmi = pcs.GetMacroBlockModeInfo(modeInfoPosition); + ref Av1MacroBlockModeInfo mbmi = ref pcs.GetMacroBlockModeInfo(modeInfoPosition); bool skip_coeff = mbmi.Block.Skip; Size size = new(blockSize.GetWidth(), blockSize.GetHeight()); @@ -1138,12 +1139,12 @@ internal partial class Av1TileWriter Point firstBlockPosition = new( modeInfoPosition.X & firstBlockMask, modeInfoPosition.Y & firstBlockMask); - Av1ModeInfo firstBlock = pcs.GetFromModeInfoGrid(firstBlockPosition); + ref Av1MacroBlockModeInfo firstBlock = ref 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; + writer.WriteCdefStrength(firstBlock.CdefStrength, frameHeader.CdefParameters.BitCount); + pcs.CdefPreset[tileIndex][index] = firstBlock.CdefStrength; } } @@ -1181,25 +1182,7 @@ internal partial class Av1TileWriter macroBlock.IsUpAvailable = modeInfoPosition.Y > tile.ModeInfoRowStart; macroBlock.IsLeftAvailable = modeInfoPosition.X > tile.ModeInfoColumnStart; int modeInfoIndex = (modeInfoPosition.Y * modeInfoStride) + modeInfoPosition.X; - macroBlock.SetModeInfoGrid(pcs.ModeInfoGrid, modeInfoIndex); - - if (macroBlock.IsUpAvailable) - { - macroBlock.AboveMacroBlock = macroBlock.GetRelativeModeInfo(-modeInfoStride).MacroBlockModeInfo; - } - else - { - macroBlock.AboveMacroBlock = null; - } - - if (macroBlock.IsLeftAvailable) - { - macroBlock.LeftMacroBlock = macroBlock.GetRelativeModeInfo(-1).MacroBlockModeInfo; - } - else - { - macroBlock.LeftMacroBlock = null; - } + macroBlock.SetModeInfoGrid(pcs.ModeInfoGrid, pcs.ModeInfoAllocation, modeInfoIndex); } /// @@ -1740,10 +1723,8 @@ internal partial class Av1TileWriter /// The skip value to write. public static void EncodeSkipCoefficients(Av1SymbolEncoder writer, Av1MacroBlockD macroBlock, bool skip) { - 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; + int above_skip = macroBlock.IsUpAvailable && macroBlock.GetRelativeModeInfo(-macroBlock.ModeInfoStride).Block.Skip ? 1 : 0; + int left_skip = macroBlock.IsLeftAvailable && macroBlock.GetRelativeModeInfo(-1).Block.Skip ? 1 : 0; writer.WriteSkip(skip, above_skip + left_skip); } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index f3d0c874a7..15b966495d 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -76,16 +77,17 @@ public class Av1CoefficientsEntropyTests [Theory] [InlineData(false, 2, 1, 6, 6)] [InlineData(true, 2, 2, 10, 3)] - public void PictureControlSetMapsModeInfoAllocationByReference( + public void PictureControlSetMapsModeInfoAllocationByIndex( bool disallow4x4, int column, int row, int gridOffset, int allocationOffset) { - Av1ModeInfo expected = CreateModeInfo(Av1PredictionMode.Paeth); - Av1ModeInfo[] allocation = new Av1ModeInfo[8]; + Av1MacroBlockModeInfo expected = CreateModeInfo(Av1PredictionMode.Paeth); + Av1MacroBlockModeInfo[] allocation = new Av1MacroBlockModeInfo[16]; allocation[allocationOffset] = expected; + int[] grid = new int[16]; Av1PictureControlSet picture = new() { PartitionContexts = [], @@ -98,6 +100,9 @@ public class Av1CoefficientsEntropyTests { Common = new Av1EncoderCommon { + ModeInfoColumnCount = 4, + ModeInfoRowCount = 4, + ModeInfoStride = 4, FrameSize = new ObuFrameSize(), TilesInfo = new ObuTileGroupHeader() }, @@ -105,41 +110,51 @@ public class Av1CoefficientsEntropyTests PreviousQIndex = [] }, SegmentationNeighborMap = [], - ModeInfoGrid = new Av1ModeInfo[16], + ModeInfoGrid = grid, ModeInfoAllocation = allocation, ModeInfoStride = 4, Disallow4x4AllFrames = disallow4x4, CdefPreset = [] }; - Av1MacroBlockModeInfo result = picture.GetMacroBlockModeInfo(new Point(column, row)); + Point position = new(column, row); + ref Av1MacroBlockModeInfo result = ref picture.GetMacroBlockModeInfo(position); + result.Block.Mode = Av1PredictionMode.Smooth; + picture.MapModeInfoBlock(position, Av1BlockSize.Block8x8); - Assert.Same(expected.MacroBlockModeInfo, result); - Assert.Same(expected, picture.ModeInfoGrid[gridOffset]); + Assert.Equal(Av1PredictionMode.Smooth, allocation[allocationOffset].Block.Mode); + Assert.Equal(allocationOffset, grid[gridOffset]); + Assert.Equal(allocationOffset, grid[gridOffset + 1]); + Assert.Equal(allocationOffset, grid[gridOffset + 4]); + Assert.Equal(allocationOffset, grid[gridOffset + 5]); } [Fact] public void MacroBlockReadsNeighborsRelativeToCurrentGridEntry() { - Av1ModeInfo above = CreateModeInfo(Av1PredictionMode.Vertical); - Av1ModeInfo left = CreateModeInfo(Av1PredictionMode.Horizontal); - Av1ModeInfo current = CreateModeInfo(Av1PredictionMode.DC); - Av1ModeInfo[] grid = new Av1ModeInfo[9]; - grid[1] = above; - grid[3] = left; - grid[4] = current; + Av1MacroBlockModeInfo[] allocation = + [ + CreateModeInfo(Av1PredictionMode.Vertical), + CreateModeInfo(Av1PredictionMode.Horizontal), + CreateModeInfo(Av1PredictionMode.DC) + ]; + + int[] grid = new int[9]; + grid[1] = 0; + grid[3] = 1; + grid[4] = 2; Av1MacroBlockD macroBlock = CreateMacroBlock(); - macroBlock.SetModeInfoGrid(grid, 4); + macroBlock.SetModeInfoGrid(grid, allocation, 4); - Assert.Same(left, macroBlock.GetRelativeModeInfo(-1)); - Assert.Same(above, macroBlock.GetRelativeModeInfo(-3)); - Assert.Same(current, macroBlock.GetRelativeModeInfo(0)); + Assert.Equal(Av1PredictionMode.Horizontal, macroBlock.GetRelativeModeInfo(-1).Block.Mode); + Assert.Equal(Av1PredictionMode.Vertical, macroBlock.GetRelativeModeInfo(-3).Block.Mode); + Assert.Equal(Av1PredictionMode.DC, macroBlock.GetRelativeModeInfo(0).Block.Mode); } [Fact] public void EncoderBlockModeInfoStoresSelectedSyntax() { - Av1EncoderBlockModeInfo modeInfo = new(); + Av1EncoderBlockModeInfo modeInfo = default; Assert.False(modeInfo.Skip); Assert.False(modeInfo.SkipMode); @@ -162,6 +177,19 @@ public class Av1CoefficientsEntropyTests Assert.Equal(3, modeInfo.SegmentId); Assert.Equal(Av1PredictionMode.Smooth, modeInfo.Mode); Assert.Equal(Av1ChromaPredictionMode.Smooth, modeInfo.UvMode); + + modeInfo.SkipMode = false; + + Assert.True(modeInfo.Skip); + Assert.False(modeInfo.SkipMode); + Assert.True(modeInfo.UseIntraBlockCopy); + } + + [Fact] + public void EncoderModeInfoUsesPackedValueStorage() + { + Assert.Equal(7, Unsafe.SizeOf()); + Assert.Equal(8, Unsafe.SizeOf()); } [Fact] @@ -262,7 +290,7 @@ public class Av1CoefficientsEntropyTests public void EncoderLumaTraversalRepresentsAllTransformsIn128x128Block() { Av1PictureControlSet picture = CreateEncoderPicture(32, 32, use128x128Superblock: true); - Av1MacroBlockModeInfo modeInfo = picture.ModeInfoAllocation[0].MacroBlockModeInfo; + ref Av1MacroBlockModeInfo modeInfo = ref picture.ModeInfoAllocation.Span[0]; modeInfo.Block.BlockSize = Av1BlockSize.Block128x128; modeInfo.Block.TransformSize = Av1TransformSize.Size16x16; modeInfo.Block.SegmentId = 0; @@ -351,7 +379,7 @@ public class Av1CoefficientsEntropyTests { Av1PictureControlSet picture = CreateEncoderPicture(16, 16); picture.Parent.FrameHeader.TransformMode = Av1TransformMode.Select; - Av1MacroBlockModeInfo modeInfo = picture.ModeInfoAllocation[0].MacroBlockModeInfo; + ref Av1MacroBlockModeInfo modeInfo = ref picture.ModeInfoAllocation.Span[0]; modeInfo.Block.BlockSize = Av1BlockSize.Block16x32; modeInfo.Block.TransformSize = Av1TransformSize.Size8x8; modeInfo.Block.SegmentId = 0; @@ -489,6 +517,8 @@ public class Av1CoefficientsEntropyTests Av1TileInfo tile = new(0, 0, picture.Parent.FrameHeader); Av1MacroBlockD macroBlock = new() { Tile = tile }; Point position = new(2, 3); + picture.GetMacroBlockModeInfo(position).Block.Mode = Av1PredictionMode.Paeth; + picture.MapModeInfoBlock(position, Av1BlockSize.Block16x8); Av1TileWriter.SetModeInfoRowAndColumn( picture, @@ -504,8 +534,21 @@ public class Av1CoefficientsEntropyTests 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); + Assert.Equal( + picture.GetFromModeInfoGrid(new Point(2, 2)).Block.Mode, + macroBlock.GetRelativeModeInfo(-picture.ModeInfoStride).Block.Mode); + Assert.Equal( + picture.GetFromModeInfoGrid(new Point(1, 3)).Block.Mode, + macroBlock.GetRelativeModeInfo(-1).Block.Mode); + + for (int row = 0; row < picture.Parent.Common.ModeInfoRowCount; row++) + { + for (int column = 0; column < picture.Parent.Common.ModeInfoColumnCount; column++) + { + Av1PredictionMode expected = row >= 3 && column >= 2 ? Av1PredictionMode.Paeth : Av1PredictionMode.DC; + Assert.Equal(expected, picture.GetFromModeInfoGrid(new Point(column, row)).Block.Mode); + } + } } [Fact] @@ -513,8 +556,8 @@ public class Av1CoefficientsEntropyTests { 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; + picture.ModeInfoAllocation.Span[16].CdefStrength = 3; + picture.ModeInfoAllocation.Span[20].CdefStrength = 1; using Av1SymbolEncoder writer = new(Configuration.Default, 16, BaseQIndex); Av1TileWriter.WriteCdef( @@ -595,8 +638,8 @@ public class Av1CoefficientsEntropyTests for (int index = 0; index < blockPositions.Length; index++) { Point position = blockPositions[index]; - Av1EncoderBlockModeInfo blockMode = picture.ModeInfoAllocation[ - (position.Y * picture.ModeInfoStride) + position.X].MacroBlockModeInfo.Block; + ref Av1EncoderBlockModeInfo blockMode = ref picture.ModeInfoAllocation.Span[ + (position.Y * picture.ModeInfoStride) + position.X].Block; blockMode.BlockSize = Av1BlockSize.Block32x32; blockMode.Skip = true; @@ -622,7 +665,7 @@ public class Av1CoefficientsEntropyTests Av1TileWriter.Av1EntropyCodingContext context = new() { MacroBlock = new Av1MacroBlockD { Tile = tile }, - MacroBlockModeInfo = picture.ModeInfoAllocation[16].MacroBlockModeInfo, + MacroBlockModeInfo = picture.ModeInfoAllocation.Span[16], SuperblockOrigin = new Point(64, 0) }; using Av1EncoderCoefficientBuffer coefficients = new( @@ -871,14 +914,12 @@ public class Av1CoefficientsEntropyTests } } - private static Av1ModeInfo CreateModeInfo(Av1PredictionMode mode) - => new() - { - MacroBlockModeInfo = new Av1MacroBlockModeInfo - { - Block = new Av1EncoderBlockModeInfo { Mode = mode } - } - }; + private static Av1MacroBlockModeInfo CreateModeInfo(Av1PredictionMode mode) + { + Av1MacroBlockModeInfo result = default; + result.Block.Mode = mode; + return result; + } private static Av1PictureControlSet CreateEncoderPicture( int modeInfoColumnCount, @@ -901,10 +942,12 @@ public class Av1CoefficientsEntropyTests TilesInfo = tiles }; - Av1ModeInfo[] modeInfoGrid = new Av1ModeInfo[modeInfoColumnCount * modeInfoRowCount]; - for (int index = 0; index < modeInfoGrid.Length; index++) + Av1MacroBlockModeInfo[] modeInfoAllocation = new Av1MacroBlockModeInfo[modeInfoColumnCount * modeInfoRowCount]; + int[] modeInfoGrid = new int[modeInfoAllocation.Length]; + for (int index = 0; index < modeInfoAllocation.Length; index++) { - modeInfoGrid[index] = CreateModeInfo(Av1PredictionMode.DC); + modeInfoAllocation[index] = CreateModeInfo(Av1PredictionMode.DC); + modeInfoGrid[index] = index; } return new Av1PictureControlSet @@ -930,7 +973,7 @@ public class Av1CoefficientsEntropyTests }, SegmentationNeighborMap = [], ModeInfoGrid = modeInfoGrid, - ModeInfoAllocation = modeInfoGrid, + ModeInfoAllocation = modeInfoAllocation, ModeInfoStride = modeInfoColumnCount, CdefPreset = [[-1, -1, -1, -1]] };