Browse Source

Inline AV1 encoder block state

pull/2633/head
James Jackson-South 1 month ago
parent
commit
d6d77c6f1b
  1. 1
      HEIF_IMPLEMENTATION_PLAN.md
  2. 36
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs
  3. 28
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs
  4. 20
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
  5. 38
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs
  6. 62
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs

1
HEIF_IMPLEMENTATION_PLAN.md

@ -822,6 +822,7 @@ Encoder verification contract:
- [ ] Implement real rate-distortion selection and make quality and effort change work, size, and output quality. - [ ] 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. - [~] 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 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.
- [ ] Implement legal deblocking, CDEF, restoration, super-resolution, and film-grain signaling 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 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. - [~] 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.

36
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary> /// <summary>
@ -8,10 +10,25 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// </summary> /// </summary>
internal class Av1EncoderBlockStruct internal class Av1EncoderBlockStruct
{ {
/// <summary>
/// Stores transform-unit state inline with the block.
/// </summary>
private InlineArray16<Av1TransformUnit> transformBlocks;
/// <summary>
/// Stores the luma and shared chroma palette sizes inline with the block.
/// </summary>
private InlineArray2<byte> paletteSize;
/// <summary>
/// Stores the block's prediction-unit syntax inline.
/// </summary>
private Av1EncoderPredictionUnit predictionUnit;
/// <summary> /// <summary>
/// Gets the transform-unit state in transform traversal order. /// Gets the transform-unit state in transform traversal order.
/// </summary> /// </summary>
public Av1TransformUnit[] TransformBlocks { get; } = new Av1TransformUnit[Av1Constants.MaxTransformUnitCount]; public Span<Av1TransformUnit> TransformBlocks => this.transformBlocks;
/// <summary> /// <summary>
/// Gets or sets the macroblock edge and neighbor state used while writing the block. /// Gets or sets the macroblock edge and neighbor state used while writing the block.
@ -39,12 +56,21 @@ internal class Av1EncoderBlockStruct
public Av1FilterIntraMode FilterIntraMode { get; set; } public Av1FilterIntraMode FilterIntraMode { get; set; }
/// <summary> /// <summary>
/// Gets or sets the palette size for luma and for the shared chroma mode. /// Gets the writable palette sizes for luma and for the shared chroma mode.
/// </summary>
public Span<byte> PaletteSize => this.paletteSize;
/// <summary>
/// Gets the encoder prediction-unit state for the block.
/// </summary> /// </summary>
public required int[] PaletteSize { get; set; } public ref Av1EncoderPredictionUnit PredictionUnit => ref this.predictionUnit;
/// <summary> /// <summary>
/// Gets or sets the encoder prediction-unit state for the block. /// Stores the two palette-size values embedded by libaom in block mode information.
/// </summary> /// </summary>
public required Av1EncoderPredictionUnit[] PredictionUnits { get; set; } [InlineArray(2)]
private struct InlineArray2<T>
{
private T element;
}
} }

28
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderPredictionUnit.cs

@ -1,25 +1,43 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary> /// <summary>
/// Stores encoder-selected intra prediction modes and directional-angle adjustments for one block. /// Stores encoder-selected intra prediction modes and directional-angle adjustments for one block.
/// </summary> /// </summary>
internal class Av1EncoderPredictionUnit internal struct Av1EncoderPredictionUnit
{ {
/// <summary> /// <summary>
/// Gets or sets the directional angle adjustment for each prediction plane. /// Stores the signed luma and chroma directional adjustments.
/// </summary>
private InlineArray2<sbyte> angleDelta;
/// <summary>
/// Gets the directional angle adjustment for each prediction plane.
/// </summary> /// </summary>
public required byte[] AngleDelta { get; set; } [UnscopedRef]
public Span<sbyte> AngleDelta => this.angleDelta;
/// <summary> /// <summary>
/// Gets or sets the chroma-from-luma alpha magnitude index. /// Gets or sets the chroma-from-luma alpha magnitude index.
/// </summary> /// </summary>
public int ChromaFromLumaIndex { get; set; } public byte ChromaFromLumaIndex { get; set; }
/// <summary> /// <summary>
/// Gets or sets the packed chroma-from-luma alpha signs for the U and V planes. /// Gets or sets the packed chroma-from-luma alpha signs for the U and V planes.
/// </summary> /// </summary>
public int ChromaFromLumaSigns { get; set; } public sbyte ChromaFromLumaSigns { get; set; }
/// <summary>
/// Stores the two signed angle deltas embedded by libaom in block mode information.
/// </summary>
[InlineArray(Av1Constants.PlaneTypeCount)]
private struct InlineArray2<T>
{
private T element;
}
} }

20
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs

@ -575,14 +575,14 @@ internal partial class Av1TileWriter
if (chromaMode == Av1ChromaPredictionMode.ChromaFromLuma) if (chromaMode == Av1ChromaPredictionMode.ChromaFromLuma)
{ {
writer.WriteChromaFromLumaAlphas( writer.WriteChromaFromLumaAlphas(
blk_ptr.PredictionUnits[0].ChromaFromLumaIndex, blk_ptr.PredictionUnit.ChromaFromLumaIndex,
blk_ptr.PredictionUnits[0].ChromaFromLumaSigns); blk_ptr.PredictionUnit.ChromaFromLumaSigns);
} }
if (blockSize >= Av1BlockSize.Block8x8 && macroBlockModeInfo.Block.UvMode.IsDirectional()) if (blockSize >= Av1BlockSize.Block8x8 && macroBlockModeInfo.Block.UvMode.IsDirectional())
{ {
writer.WriteAngleDelta( writer.WriteAngleDelta(
blk_ptr.PredictionUnits[0].AngleDelta[(int)Av1PlaneType.Uv] + Av1Constants.MaxAngleDelta, blk_ptr.PredictionUnit.AngleDelta[(int)Av1PlaneType.Uv] + Av1Constants.MaxAngleDelta,
chromaMode.ToLumaMode()); chromaMode.ToLumaMode());
} }
} }
@ -632,7 +632,7 @@ internal partial class Av1TileWriter
if (blockSize >= Av1BlockSize.Block8x8 && macroBlockModeInfo.Block.Mode.IsDirectional()) if (blockSize >= Av1BlockSize.Block8x8 && macroBlockModeInfo.Block.Mode.IsDirectional())
{ {
writer.WriteAngleDelta(blk_ptr.PredictionUnits[0].AngleDelta[(int)Av1PlaneType.Y] + Av1Constants.MaxAngleDelta, lumaMode); writer.WriteAngleDelta(blk_ptr.PredictionUnit.AngleDelta[(int)Av1PlaneType.Y] + Av1Constants.MaxAngleDelta, lumaMode);
} }
} }
@ -1120,15 +1120,15 @@ internal partial class Av1TileWriter
} }
int tx_depth = mbmi.Block.TransformDepth; int tx_depth = mbmi.Block.TransformDepth;
uint txb_count = 1; int transformBlockCount = 1;
ObuFrameHeader frameHeader = pcs.Parent.FrameHeader; ObuFrameHeader frameHeader = pcs.Parent.FrameHeader;
for (uint tx_index = 0; tx_index < txb_count; ++tx_index) for (int transformBlockIndex = 0; transformBlockIndex < transformBlockCount; ++transformBlockIndex)
{ {
Av1TransformSize chromaTransformSize = blockGeometry.TransformSizeUv[tx_depth]; Av1TransformSize chromaTransformSize = blockGeometry.TransformSizeUv[tx_depth];
int transformWidth = chromaTransformSize.GetWidth(); int transformWidth = chromaTransformSize.GetWidth();
int transformHeight = chromaTransformSize.GetHeight(); int transformHeight = chromaTransformSize.GetHeight();
Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][tx_index]; Point transformOrigin = blockGeometry.TransformOrigin[tx_depth][transformBlockIndex];
Point chromaOrigin = RoundUv(blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin) >> 1; Point chromaOrigin = RoundUv(blockOrigin + (Size)transformOrigin - (Size)blockGeometry.Origin) >> 1;
// Both chroma planes share transform geometry but retain independent coefficient contexts. // Both chroma planes share transform geometry but retain independent coefficient contexts.
@ -1139,8 +1139,8 @@ internal partial class Av1TileWriter
chromaOrigin, chromaOrigin,
blockGeometry.BlockSizeUv, blockGeometry.BlockSizeUv,
chromaTransformSize); chromaTransformSize);
Av1TransformType chromaTransformType = blk_ptr.TransformBlocks[tx_index].TransformType[(int)Av1ComponentType.Chroma]; Av1TransformType chromaTransformType = blk_ptr.TransformBlocks[transformBlockIndex].TransformType[(int)Av1ComponentType.Chroma];
int endOfBlockCb = blk_ptr.TransformBlocks[tx_index].NzCoefficientCount[1]; int endOfBlockCb = blk_ptr.TransformBlocks[transformBlockIndex].NzCoefficientCount[1];
int culLevelCb = writer.WriteCoefficients( int culLevelCb = writer.WriteCoefficients(
chromaTransformSize, chromaTransformSize,
chromaTransformType, chromaTransformType,
@ -1153,7 +1153,7 @@ internal partial class Av1TileWriter
blk_ptr.FilterIntraMode); blk_ptr.FilterIntraMode);
coefficientBuffer = coeff_ptr.GetPlaneBuffer(Av1Plane.V).DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv); coefficientBuffer = coeff_ptr.GetPlaneBuffer(Av1Plane.V).DangerousGetSingleSpan().Slice(entropyCodingContext.CodedAreaSuperblockUv);
int endOfBlockCr = blk_ptr.TransformBlocks[tx_index].NzCoefficientCount[2]; int endOfBlockCr = blk_ptr.TransformBlocks[transformBlockIndex].NzCoefficientCount[2];
blockContext = GetTransformBlockContexts( blockContext = GetTransformBlockContexts(
Av1ComponentType.Chroma, Av1ComponentType.Chroma,

38
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
@ -8,15 +10,45 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
/// <summary> /// <summary>
/// Stores the transform syntax and coefficient range for one AV1 transform unit. /// Stores the transform syntax and coefficient range for one AV1 transform unit.
/// </summary> /// </summary>
internal class Av1TransformUnit internal struct Av1TransformUnit
{ {
/// <summary>
/// Stores the luma, blue-difference, and red-difference end-of-block positions.
/// </summary>
private InlineArray3<ushort> nzCoefficientCount;
/// <summary>
/// Stores the luma and shared chroma transform types.
/// </summary>
private InlineArray2<Av1TransformType> transformType;
/// <summary> /// <summary>
/// Gets the nonzero-coefficient count for each color plane. /// Gets the nonzero-coefficient count for each color plane.
/// </summary> /// </summary>
public ushort[] NzCoefficientCount { get; } = new ushort[3]; [UnscopedRef]
public Span<ushort> NzCoefficientCount => this.nzCoefficientCount;
/// <summary> /// <summary>
/// Gets the transform type selected for each color plane. /// Gets the transform type selected for each color plane.
/// </summary> /// </summary>
public Av1TransformType[] TransformType { get; } = new Av1TransformType[Av1Constants.PlaneTypeCount]; [UnscopedRef]
public Span<Av1TransformType> TransformType => this.transformType;
/// <summary>
/// Stores the three per-plane coefficient counts inline.
/// </summary>
[InlineArray(3)]
private struct InlineArray3<T>
{
private T element;
}
/// <summary>
/// Stores the luma and shared chroma transform types inline.
/// </summary>
[InlineArray(Av1Constants.PlaneTypeCount)]
private struct InlineArray2<T>
{
private T element;
}
} }

62
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs

@ -129,22 +129,7 @@ public class Av1CoefficientsEntropyTests
grid[1] = above; grid[1] = above;
grid[3] = left; grid[3] = left;
grid[4] = current; grid[4] = current;
ObuTileGroupHeader tiles = new() Av1MacroBlockD macroBlock = CreateMacroBlock();
{
TileColumnCount = 1,
TileRowCount = 1
};
tiles.TileColumnStartModeInfo[1] = 3;
tiles.TileRowStartModeInfo[1] = 3;
ObuFrameHeader frameHeader = new()
{
ModeInfoColumnCount = 3,
ModeInfoRowCount = 3,
TilesInfo = tiles
};
Av1MacroBlockD macroBlock = new() { Tile = new Av1TileInfo(0, 0, frameHeader) };
macroBlock.SetModeInfoGrid(grid, 4); macroBlock.SetModeInfoGrid(grid, 4);
Assert.Same(left, macroBlock.GetRelativeModeInfo(-1)); Assert.Same(left, macroBlock.GetRelativeModeInfo(-1));
@ -180,6 +165,31 @@ public class Av1CoefficientsEntropyTests
Assert.Equal(Av1ChromaPredictionMode.Smooth, modeInfo.UvMode); Assert.Equal(Av1ChromaPredictionMode.Smooth, modeInfo.UvMode);
} }
[Fact]
public void EncoderBlockInlineStateSupportsEveryTransformWithoutTraversalAllocations()
{
Av1EncoderBlockStruct block = new() { MacroBlock = CreateMacroBlock() };
long before = GC.GetAllocatedBytesForCurrentThread();
Span<Av1TransformUnit> transforms = block.TransformBlocks;
transforms[^1].NzCoefficientCount[2] = 17;
transforms[^1].TransformType[(int)Av1PlaneType.Uv] = Av1TransformType.VerticalAdst;
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(0, allocated);
}
[Fact] [Fact]
public void RoundTripZeroEndOfBlock() public void RoundTripZeroEndOfBlock()
{ {
@ -394,6 +404,26 @@ public class Av1CoefficientsEntropyTests
} }
}; };
private static Av1MacroBlockD CreateMacroBlock()
{
ObuTileGroupHeader tiles = new()
{
TileColumnCount = 1,
TileRowCount = 1
};
tiles.TileColumnStartModeInfo[1] = 3;
tiles.TileRowStartModeInfo[1] = 3;
ObuFrameHeader frameHeader = new()
{
ModeInfoColumnCount = 3,
ModeInfoRowCount = 3,
TilesInfo = tiles
};
return new Av1MacroBlockD { Tile = new Av1TileInfo(0, 0, frameHeader) };
}
public static TheoryData<int> GetTransformTypes() public static TheoryData<int> GetTransformTypes()
{ {
TheoryData<int> result = []; TheoryData<int> result = [];

Loading…
Cancel
Save