mirror of https://github.com/SixLabors/ImageSharp
3 changed files with 224 additions and 1 deletions
@ -0,0 +1,92 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Buffers; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Owns the allocation-index grid and mode-information values for one encoded AV1 frame.
|
||||
|
/// </summary>
|
||||
|
internal sealed class Av1EncoderModeInfoBuffer : IDisposable |
||||
|
{ |
||||
|
private const int CodedDimensionAlignmentLog2 = 3; |
||||
|
private const int ModeInfoAlignmentLog2 = Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2; |
||||
|
private IMemoryOwner<byte>? owner; |
||||
|
private readonly ByteMemoryManager<int> grid; |
||||
|
private readonly ByteMemoryManager<Av1MacroBlockModeInfo> allocation; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1EncoderModeInfoBuffer"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration providing the memory allocator.</param>
|
||||
|
/// <param name="frameWidth">The visible frame width in luma samples.</param>
|
||||
|
/// <param name="frameHeight">The visible frame height in luma samples.</param>
|
||||
|
/// <param name="disallow4x4AllFrames">Whether each allocated mode-information value represents an 8x8 region.</param>
|
||||
|
public Av1EncoderModeInfoBuffer( |
||||
|
Configuration configuration, |
||||
|
int frameWidth, |
||||
|
int frameHeight, |
||||
|
bool disallow4x4AllFrames) |
||||
|
{ |
||||
|
this.ModeInfoColumnCount = Av1Math.AlignPowerOf2(frameWidth, CodedDimensionAlignmentLog2) >> Av1Constants.ModeInfoSizeLog2; |
||||
|
this.ModeInfoRowCount = Av1Math.AlignPowerOf2(frameHeight, CodedDimensionAlignmentLog2) >> Av1Constants.ModeInfoSizeLog2; |
||||
|
this.ModeInfoStride = Av1Math.AlignPowerOf2(this.ModeInfoColumnCount, ModeInfoAlignmentLog2); |
||||
|
int alignedModeInfoRowCount = Av1Math.AlignPowerOf2(this.ModeInfoRowCount, ModeInfoAlignmentLog2); |
||||
|
int allocationShift = disallow4x4AllFrames ? 1 : 0; |
||||
|
int gridLength = checked(this.ModeInfoStride * alignedModeInfoRowCount); |
||||
|
int allocationLength = checked((this.ModeInfoStride >> allocationShift) * (alignedModeInfoRowCount >> allocationShift)); |
||||
|
int gridByteLength = checked(gridLength * sizeof(int)); |
||||
|
int allocationByteLength = checked(allocationLength * Unsafe.SizeOf<Av1MacroBlockModeInfo>()); |
||||
|
int storageLength = checked(gridByteLength + allocationByteLength); |
||||
|
|
||||
|
// The pointer grid and value allocation share one frame lifetime. Packing both regions into one clean
|
||||
|
// owner retains libaom's independent typed layouts without its separate allocation and cleanup paths.
|
||||
|
this.owner = configuration.MemoryAllocator.Allocate<byte>(storageLength, AllocationOptions.Clean); |
||||
|
Memory<byte> storage = this.owner.Memory[..storageLength]; |
||||
|
this.grid = new ByteMemoryManager<int>(storage[..gridByteLength]); |
||||
|
this.allocation = new ByteMemoryManager<Av1MacroBlockModeInfo>(storage.Slice(gridByteLength, allocationByteLength)); |
||||
|
this.Disallow4x4AllFrames = disallow4x4AllFrames; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the visible frame height in 4x4 mode-information units.
|
||||
|
/// </summary>
|
||||
|
public int ModeInfoRowCount { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the visible frame width in 4x4 mode-information units.
|
||||
|
/// </summary>
|
||||
|
public int ModeInfoColumnCount { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the aligned row stride of the allocation-index grid in 4x4 mode-information units.
|
||||
|
/// </summary>
|
||||
|
public int ModeInfoStride { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether each allocated mode-information value represents an 8x8 region.
|
||||
|
/// </summary>
|
||||
|
public bool Disallow4x4AllFrames { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the frame grid that maps each 4x4 position to its mode-information allocation index.
|
||||
|
/// </summary>
|
||||
|
public Memory<int> Grid => this.grid.Memory; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the contiguous mode-information values addressed by <see cref="Grid"/>.
|
||||
|
/// </summary>
|
||||
|
public Memory<Av1MacroBlockModeInfo> Allocation => this.allocation.Memory; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the packed frame storage to the configured memory allocator.
|
||||
|
/// </summary>
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
this.owner?.Dispose(); |
||||
|
this.owner = null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,131 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.Tests.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
||||
|
|
||||
|
public class Av1EncoderModeInfoBufferTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData(false, 1024, 1024, 12_288)] |
||||
|
[InlineData(true, 1024, 256, 6_144)] |
||||
|
public void ConstructorMatchesLibaomAlignedModeInfoGeometry( |
||||
|
bool disallow4x4, |
||||
|
int expectedGridLength, |
||||
|
int expectedAllocationLength, |
||||
|
int expectedStorageLength) |
||||
|
{ |
||||
|
TestMemoryAllocator allocator = new(); |
||||
|
allocator.EnableNonThreadSafeLogging(); |
||||
|
Configuration configuration = Configuration.Default.Clone(); |
||||
|
configuration.MemoryAllocator = allocator; |
||||
|
|
||||
|
TestMemoryAllocator.AllocationRequest allocation; |
||||
|
using (Av1EncoderModeInfoBuffer buffer = new(configuration, 65, 33, disallow4x4)) |
||||
|
{ |
||||
|
allocation = Assert.Single(allocator.AllocationLog); |
||||
|
Assert.Empty(allocator.ReturnLog); |
||||
|
Assert.Equal(typeof(byte), allocation.ElementType); |
||||
|
Assert.Equal(AllocationOptions.Clean, allocation.AllocationOptions); |
||||
|
Assert.Equal(expectedStorageLength, allocation.Length); |
||||
|
Assert.Equal(18, buffer.ModeInfoColumnCount); |
||||
|
Assert.Equal(10, buffer.ModeInfoRowCount); |
||||
|
Assert.Equal(32, buffer.ModeInfoStride); |
||||
|
Assert.Equal(disallow4x4, buffer.Disallow4x4AllFrames); |
||||
|
Assert.Equal(expectedGridLength, buffer.Grid.Length); |
||||
|
Assert.Equal(expectedAllocationLength, buffer.Allocation.Length); |
||||
|
} |
||||
|
|
||||
|
TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); |
||||
|
Assert.Equal(allocation.AllocationId, returned.AllocationId); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[InlineData(false, 2, 3, 98)] |
||||
|
[InlineData(true, 2, 2, 17)] |
||||
|
public void PictureMappingUsesPackedAlignedStorage( |
||||
|
bool disallow4x4, |
||||
|
int column, |
||||
|
int row, |
||||
|
int expectedAllocationOffset) |
||||
|
{ |
||||
|
using Av1EncoderModeInfoBuffer buffer = new(Configuration.Default, 65, 33, disallow4x4); |
||||
|
Av1PictureControlSet picture = CreatePicture(buffer); |
||||
|
Point position = new(column, row); |
||||
|
ref Av1MacroBlockModeInfo modeInfo = ref picture.GetMacroBlockModeInfo(position); |
||||
|
modeInfo.Block.Mode = Av1PredictionMode.Paeth; |
||||
|
|
||||
|
picture.MapModeInfoBlock(position, Av1BlockSize.Block8x8); |
||||
|
|
||||
|
Assert.Equal(Av1PredictionMode.Paeth, buffer.Allocation.Span[expectedAllocationOffset].Block.Mode); |
||||
|
int alignedRowCount = buffer.Grid.Length / buffer.ModeInfoStride; |
||||
|
for (int y = 0; y < alignedRowCount; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < buffer.ModeInfoStride; x++) |
||||
|
{ |
||||
|
int gridOffset = (y * buffer.ModeInfoStride) + x; |
||||
|
bool isMapped = y >= row && y < row + 2 && x >= column && x < column + 2; |
||||
|
Assert.Equal(isMapped ? expectedAllocationOffset : 0, buffer.Grid.Span[gridOffset]); |
||||
|
|
||||
|
if (isMapped) |
||||
|
{ |
||||
|
Assert.Equal(Av1PredictionMode.Paeth, picture.GetFromModeInfoGrid(new Point(x, y)).Block.Mode); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Av1PictureControlSet CreatePicture(Av1EncoderModeInfoBuffer buffer) |
||||
|
{ |
||||
|
ObuTileGroupHeader tiles = new() |
||||
|
{ |
||||
|
TileColumnCount = 1, |
||||
|
TileRowCount = 1 |
||||
|
}; |
||||
|
|
||||
|
tiles.TileColumnStartModeInfo[1] = buffer.ModeInfoColumnCount; |
||||
|
tiles.TileRowStartModeInfo[1] = buffer.ModeInfoRowCount; |
||||
|
ObuSequenceHeader sequenceHeader = new(); |
||||
|
ObuFrameHeader frameHeader = new() |
||||
|
{ |
||||
|
ModeInfoColumnCount = buffer.ModeInfoColumnCount, |
||||
|
ModeInfoRowCount = buffer.ModeInfoRowCount, |
||||
|
TilesInfo = tiles |
||||
|
}; |
||||
|
|
||||
|
return new Av1PictureControlSet |
||||
|
{ |
||||
|
PartitionContexts = [], |
||||
|
LuminanceDcSignLevelCoefficientNeighbors = [], |
||||
|
CrDcSignLevelCoefficientNeighbors = [], |
||||
|
CbDcSignLevelCoefficientNeighbors = [], |
||||
|
TransformFunctionContexts = [], |
||||
|
Sequence = new Av1SequenceControlSet { SequenceHeader = sequenceHeader }, |
||||
|
Parent = new Av1PictureParentControlSet |
||||
|
{ |
||||
|
Common = new Av1EncoderCommon |
||||
|
{ |
||||
|
ModeInfoColumnCount = buffer.ModeInfoColumnCount, |
||||
|
ModeInfoRowCount = buffer.ModeInfoRowCount, |
||||
|
ModeInfoStride = buffer.ModeInfoStride, |
||||
|
FrameSize = new ObuFrameSize(), |
||||
|
TilesInfo = tiles |
||||
|
}, |
||||
|
FrameHeader = frameHeader, |
||||
|
PreviousQIndex = [] |
||||
|
}, |
||||
|
SegmentationNeighborMap = [], |
||||
|
ModeInfoGrid = buffer.Grid, |
||||
|
ModeInfoAllocation = buffer.Allocation, |
||||
|
ModeInfoStride = buffer.ModeInfoStride, |
||||
|
Disallow4x4AllFrames = buffer.Disallow4x4AllFrames, |
||||
|
CdefPreset = [] |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue