From 1f533dc8837d195e21ce261960a776fe2d2325e8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 2 Sep 2026 15:47:50 +1000 Subject: [PATCH] Initialize AV1 block syntax defaults --- HEIF_IMPLEMENTATION_PLAN.md | 2 +- .../Tiling/Av1EncoderSuperblockWorkspace.cs | 18 +++++++++++++++--- .../Heif/Av1/Av1CoefficientsEntropyTests.cs | 8 +++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index eda710904c..9f10ba394f 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -829,7 +829,7 @@ 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 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. -- [~] The final-block decision workspace uses one reusable 10.3 KiB ImageSharp allocator owner. It contains 1,024 explicitly packed 10-byte final-block entries and the 341 preorder partition bytes required by a complete 128x128-through-8x8 quadtree, replacing separate managed arrays. Initial rental is clean, and the explicit per-superblock reset clears the complete owner so pooled palette, quantizer, prediction, and partition bytes cannot leak into the next decision pass. Exact allocation, size, reset, return, repeated-run, writer, entropy, and OBU coverage pass 1,948 of 1,948 direct net11 VSTest cases in Release; complete mode decision still remains. +- [~] The final-block decision workspace uses one reusable 10.3 KiB ImageSharp allocator owner. It contains 1,024 explicitly packed 10-byte final-block entries and the 341 preorder partition bytes required by a complete 128x128-through-8x8 quadtree, replacing separate managed arrays. Construction and the explicit per-superblock reset initialize every syntax field, including the nonzero sentinel that disables filter-intra prediction; pooled palette, quantizer, prediction, and partition bytes cannot leak into the next decision pass. Exact allocation, size, initialization, reset, return, repeated-run, writer, entropy, and OBU coverage pass 1,957 of 1,957 direct net11 VSTest cases in Release; 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. Bottom-edge blocks use the horizontal-alike partition CDF and right-edge blocks use the vertical-alike CDF; byte-exact regressions cover both paths after the previous calls were found reversed. Lossless chroma-from-luma availability now uses the subsampled plane block size shared with the decoder instead of the lossy 32x32 limit, preserving the correct UV-mode alphabet for each segment. 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. Writer, entropy, and OBU coverage passes 1,957 of 1,957 direct net11 VSTest cases in Release, with 20 of 20 focused encoder and decoder chroma-from-luma cases. Partition and mode analysis still need to populate these retained decisions; variable inter-transform syntax remains part of later inter-frame support. - [ ] Implement legal deblocking, CDEF, restoration, super-resolution, and film-grain signaling decisions. diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderSuperblockWorkspace.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderSuperblockWorkspace.cs index 5b63694936..b255ec189e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderSuperblockWorkspace.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderSuperblockWorkspace.cs @@ -3,7 +3,6 @@ using System.Buffers; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; @@ -34,7 +33,10 @@ internal sealed class Av1EncoderSuperblockWorkspace : IDisposable /// /// The configuration providing the encoder allocator. public Av1EncoderSuperblockWorkspace(Configuration configuration) - => this.owner = configuration.MemoryAllocator.Allocate(StorageLength, AllocationOptions.Clean); + { + this.owner = configuration.MemoryAllocator.Allocate(StorageLength); + this.Reset(); + } /// /// Gets the maximum-size final-block decision span in partition traversal order. @@ -50,7 +52,17 @@ internal sealed class Av1EncoderSuperblockWorkspace : IDisposable /// /// Clears all decisions before the workspace is reused for another superblock. /// - public void Reset() => this.owner.Memory.Span.Clear(); + public void Reset() + { + // Zero selects a real filter-intra kernel, so each cleared block must carry the disabled sentinel explicitly. + Av1EncoderBlockStruct initialBlock = new() + { + FilterIntraMode = Av1FilterIntraMode.AllFilterIntraModes + }; + + this.FinalBlocks.Fill(initialBlock); + MemoryMarshal.AsBytes(this.owner.Memory.Span[MaximumFinalBlockCount..]).Clear(); + } /// /// Releases the reusable superblock workspace. diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index 67f5fb03e2..644c58ae1c 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -207,22 +207,28 @@ public class Av1CoefficientsEntropyTests allocation = Assert.Single(allocator.AllocationLog); Assert.Empty(allocator.ReturnLog); Assert.Equal(typeof(Av1EncoderBlockStruct), allocation.ElementType); - Assert.Equal(AllocationOptions.Clean, allocation.AllocationOptions); + Assert.Equal(AllocationOptions.None, allocation.AllocationOptions); Assert.Equal(Av1EncoderSuperblockWorkspace.StorageLength, allocation.Length); Assert.Equal(Av1EncoderSuperblockWorkspace.MaximumFinalBlockCount, workspace.FinalBlocks.Length); Assert.Equal(Av1EncoderSuperblockWorkspace.MaximumPartitionCount, workspace.PartitionTypes.Length); Assert.Equal(Av1EncoderBlockStruct.StorageSize, Unsafe.SizeOf()); Assert.Equal(0, workspace.FinalBlocks[0].PaletteSize[0]); Assert.Equal(0, workspace.FinalBlocks[^1].QuantizationIndex); + Assert.Equal(Av1FilterIntraMode.AllFilterIntraModes, workspace.FinalBlocks[0].FilterIntraMode); + Assert.Equal(Av1FilterIntraMode.AllFilterIntraModes, workspace.FinalBlocks[^1].FilterIntraMode); Assert.Equal(0, workspace.PartitionTypes[^1]); workspace.FinalBlocks[0].PaletteSize[0] = 7; + workspace.FinalBlocks[0].FilterIntraMode = Av1FilterIntraMode.DC; workspace.FinalBlocks[^1].QuantizationIndex = 255; + workspace.FinalBlocks[^1].FilterIntraMode = Av1FilterIntraMode.Paeth; workspace.PartitionTypes.Fill(byte.MaxValue); workspace.Reset(); Assert.Equal(0, workspace.FinalBlocks[0].PaletteSize[0]); Assert.Equal(0, workspace.FinalBlocks[^1].QuantizationIndex); + Assert.Equal(Av1FilterIntraMode.AllFilterIntraModes, workspace.FinalBlocks[0].FilterIntraMode); + Assert.Equal(Av1FilterIntraMode.AllFilterIntraModes, workspace.FinalBlocks[^1].FilterIntraMode); for (int index = 0; index < workspace.PartitionTypes.Length; index++) { Assert.Equal(0, workspace.PartitionTypes[index]);