Browse Source

Consolidate AV1 palette scratch

pull/2633/head
James Jackson-South 1 month ago
parent
commit
6e01c2763a
  1. 11
      HEIF_IMPLEMENTATION_PLAN.md
  2. 42
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  3. 8
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs

11
HEIF_IMPLEMENTATION_PLAN.md

@ -646,11 +646,12 @@ Verified partition, mode, segmentation, quantization, and transform checkpoint e
Each transform resets and clears only its active padded geometry, so no transform creates an allocation.
Allocation tracking over all eight minimum- and maximum-quantizer frames proves exactly one coefficient
scratch allocation per frame and exactly-once return after decoder disposal.
- [x] Palette index maps use two allocator-backed 128x128 decoder-session scratch buffers, one for luma and
one for chroma. Each parsed superblock is reconstructed before either buffer is reused, and each block
clears only its transient `Buffer2DRegion` view after prediction. The fixed 32 KiB session cost replaces
the former two full-frame maps without copies or per-block allocations. A 1 KiB constrained allocator
splits both buffers across memory groups. The four-case palette set passes on net10.0 and net11.0 with
- [x] Palette index maps use one allocator-backed 32 KiB decoder-session owner with non-owning 128x128 luma
and chroma views. Each parsed superblock is reconstructed before either view is reused, and each block
clears only its transient `Buffer2DRegion` after prediction. The fixed session cost replaces the former
full-frame maps without copies, fragmented memory groups, constructor rollback, or per-block allocations.
The one-rent ownership regression and native palette reconstruction pass on net11.0, the complete HEIF/AV1
namespace passes 8,808 of 8,808 direct VSTest cases in Release, and the four-case palette set passes with
exact native and presentation output, truncated-entropy rejection, and balanced exactly-once disposal.
- [x] `Av1BlockModeInfo` is value storage, removing the managed object allocation formerly created for every
decoded coding block. Explicit `ModeInfoIndex` values preserve libaom's mode-info identity semantics at

42
src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using SixLabors.ImageSharp.Formats.Heif.Av1.Color;
using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
@ -41,6 +42,11 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
/// </summary>
private readonly Buffer2D<byte> chromaPaletteColorIndexMap;
/// <summary>
/// The shared backing owner for both reusable palette maps.
/// </summary>
private readonly IMemoryOwner<byte> paletteColorIndexMapOwner;
/// <summary>
/// The reconstructed references and selected presentation output owned by the current bounded decode session.
/// </summary>
@ -96,26 +102,21 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
this.configuration = configuration;
this.obuReader = new(operatingPointIndex, this.referenceFrames);
Buffer2D<byte>? lumaPaletteColorIndexMap = null;
Buffer2D<byte>? chromaPaletteColorIndexMap = null;
try
{
// Sequential tile decoding needs only the palette indices belonging to the current superblock. Keeping
// two maximum-superblock surfaces at decoder scope makes the memory bound independent of frame size and
// reuses the same allocator rents across every frame in the bounded sequence.
int paletteMapLength = 1 << Av1Constants.MaxSuperBlockSizeLog2;
lumaPaletteColorIndexMap = configuration.MemoryAllocator.Allocate2D<byte>(paletteMapLength, paletteMapLength);
chromaPaletteColorIndexMap = configuration.MemoryAllocator.Allocate2D<byte>(paletteMapLength, paletteMapLength);
this.lumaPaletteColorIndexMap = lumaPaletteColorIndexMap;
this.chromaPaletteColorIndexMap = chromaPaletteColorIndexMap;
}
catch
{
chromaPaletteColorIndexMap?.Dispose();
lumaPaletteColorIndexMap?.Dispose();
throw;
}
// Sequential tile decoding needs only the palette indices belonging to the current superblock. One fixed
// owner keeps both maximum-superblock maps reusable across the bounded session without fragmented group rents.
int paletteMapLength = 1 << Av1Constants.MaxSuperBlockSizeLog2;
int paletteMapArea = paletteMapLength * paletteMapLength;
this.paletteColorIndexMapOwner = configuration.MemoryAllocator.Allocate<byte>(2 * paletteMapArea);
Memory<byte> paletteMaps = this.paletteColorIndexMapOwner.Memory;
this.lumaPaletteColorIndexMap = Buffer2D<byte>.WrapMemory(
paletteMaps[..paletteMapArea],
paletteMapLength,
paletteMapLength);
this.chromaPaletteColorIndexMap = Buffer2D<byte>.WrapMemory(
paletteMaps[paletteMapArea..],
paletteMapLength,
paletteMapLength);
}
/// <summary>
@ -958,6 +959,7 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
this.FrameInfo = null;
this.lumaPaletteColorIndexMap.Dispose();
this.chromaPaletteColorIndexMap.Dispose();
this.paletteColorIndexMapOwner.Dispose();
}
/// <summary>

8
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs

@ -576,15 +576,15 @@ public class Av1ReconstructionConformanceTests
using (Av1Decoder decoder = new(configuration))
{
int paletteMapLength = 1 << Av1Constants.MaxSuperBlockSizeLog2;
int expectedPaletteAllocationCount = (2 * paletteMapLength * paletteMapLength) / allocator.BufferCapacityInBytes;
Assert.Equal(expectedPaletteAllocationCount, allocator.AllocationLog.Count);
int finalPaletteAllocationId = allocator.AllocationLog[^1].AllocationId;
TestMemoryAllocator.AllocationRequest paletteMaps = Assert.Single(allocator.AllocationLog);
Assert.Equal(2 * paletteMapLength * paletteMapLength, paletteMaps.Length);
int paletteAllocationId = paletteMaps.AllocationId;
using Av1FrameBuffer<byte> frameBuffer = decoder.DecodeFrameBuffer(payload, null, null, out _);
Assert.Equal(RequiredPaletteCoverage, GetPaletteCoverage(decoder));
AssertNativePlanesEqual(decoder, frameBuffer, reference);
Assert.DoesNotContain(allocator.ReturnLog, returned => returned.AllocationId <= finalPaletteAllocationId);
Assert.DoesNotContain(allocator.ReturnLog, returned => returned.AllocationId == paletteAllocationId);
}
Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count);

Loading…
Cancel
Save