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. 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 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. 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 - [x] Palette index maps use one allocator-backed 32 KiB decoder-session owner with non-owning 128x128 luma
one for chroma. Each parsed superblock is reconstructed before either buffer is reused, and each block and chroma views. Each parsed superblock is reconstructed before either view is reused, and each block
clears only its transient `Buffer2DRegion` view after prediction. The fixed 32 KiB session cost replaces clears only its transient `Buffer2DRegion` after prediction. The fixed session cost replaces the former
the former two full-frame maps without copies or per-block allocations. A 1 KiB constrained allocator full-frame maps without copies, fragmented memory groups, constructor rollback, or per-block allocations.
splits both buffers across memory groups. The four-case palette set passes on net10.0 and net11.0 with 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. 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 - [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 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. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Buffers;
using SixLabors.ImageSharp.Formats.Heif.Av1.Color; using SixLabors.ImageSharp.Formats.Heif.Av1.Color;
using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
@ -41,6 +42,11 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
/// </summary> /// </summary>
private readonly Buffer2D<byte> chromaPaletteColorIndexMap; private readonly Buffer2D<byte> chromaPaletteColorIndexMap;
/// <summary>
/// The shared backing owner for both reusable palette maps.
/// </summary>
private readonly IMemoryOwner<byte> paletteColorIndexMapOwner;
/// <summary> /// <summary>
/// The reconstructed references and selected presentation output owned by the current bounded decode session. /// The reconstructed references and selected presentation output owned by the current bounded decode session.
/// </summary> /// </summary>
@ -96,26 +102,21 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
this.configuration = configuration; this.configuration = configuration;
this.obuReader = new(operatingPointIndex, this.referenceFrames); this.obuReader = new(operatingPointIndex, this.referenceFrames);
Buffer2D<byte>? lumaPaletteColorIndexMap = null; // Sequential tile decoding needs only the palette indices belonging to the current superblock. One fixed
Buffer2D<byte>? chromaPaletteColorIndexMap = null; // owner keeps both maximum-superblock maps reusable across the bounded session without fragmented group rents.
int paletteMapLength = 1 << Av1Constants.MaxSuperBlockSizeLog2;
try int paletteMapArea = paletteMapLength * paletteMapLength;
{ this.paletteColorIndexMapOwner = configuration.MemoryAllocator.Allocate<byte>(2 * paletteMapArea);
// Sequential tile decoding needs only the palette indices belonging to the current superblock. Keeping Memory<byte> paletteMaps = this.paletteColorIndexMapOwner.Memory;
// two maximum-superblock surfaces at decoder scope makes the memory bound independent of frame size and this.lumaPaletteColorIndexMap = Buffer2D<byte>.WrapMemory(
// reuses the same allocator rents across every frame in the bounded sequence. paletteMaps[..paletteMapArea],
int paletteMapLength = 1 << Av1Constants.MaxSuperBlockSizeLog2; paletteMapLength,
lumaPaletteColorIndexMap = configuration.MemoryAllocator.Allocate2D<byte>(paletteMapLength, paletteMapLength); paletteMapLength);
chromaPaletteColorIndexMap = configuration.MemoryAllocator.Allocate2D<byte>(paletteMapLength, paletteMapLength);
this.lumaPaletteColorIndexMap = lumaPaletteColorIndexMap; this.chromaPaletteColorIndexMap = Buffer2D<byte>.WrapMemory(
this.chromaPaletteColorIndexMap = chromaPaletteColorIndexMap; paletteMaps[paletteMapArea..],
} paletteMapLength,
catch paletteMapLength);
{
chromaPaletteColorIndexMap?.Dispose();
lumaPaletteColorIndexMap?.Dispose();
throw;
}
} }
/// <summary> /// <summary>
@ -958,6 +959,7 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable
this.FrameInfo = null; this.FrameInfo = null;
this.lumaPaletteColorIndexMap.Dispose(); this.lumaPaletteColorIndexMap.Dispose();
this.chromaPaletteColorIndexMap.Dispose(); this.chromaPaletteColorIndexMap.Dispose();
this.paletteColorIndexMapOwner.Dispose();
} }
/// <summary> /// <summary>

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

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

Loading…
Cancel
Save