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