mirror of https://github.com/SixLabors/ImageSharp
5 changed files with 942 additions and 0 deletions
@ -0,0 +1,25 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies the differential pulse-code modulation applied to an HEVC residual block.
|
||||
|
/// </summary>
|
||||
|
internal enum HevcResidualDpcmMode : byte |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No residual differential pulse-code modulation is applied.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Residual differences accumulate from left to right within each row.
|
||||
|
/// </summary>
|
||||
|
Horizontal = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Residual differences accumulate from top to bottom within each column.
|
||||
|
/// </summary>
|
||||
|
Vertical = 2, |
||||
|
} |
||||
@ -0,0 +1,555 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs HEVC transform-skipped, bypassed, and differential residual blocks.
|
||||
|
/// </summary>
|
||||
|
internal static class HevcResidualReconstructor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The horizontal intra-prediction mode defined by H.265.
|
||||
|
/// </summary>
|
||||
|
private const int HorizontalIntraPredictionMode = 10; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical intra-prediction mode defined by H.265.
|
||||
|
/// </summary>
|
||||
|
private const int VerticalIntraPredictionMode = 26; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The minimum residual sample represented by the decoder reconstruction pipeline.
|
||||
|
/// </summary>
|
||||
|
private const int ResidualMinimum = short.MinValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The maximum residual sample represented by the decoder reconstruction pipeline.
|
||||
|
/// </summary>
|
||||
|
private const int ResidualMaximum = short.MaxValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines a closed transform-skip normalization operator for every SIMD width and the scalar tail.
|
||||
|
/// </summary>
|
||||
|
private interface ITransformSkipOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Normalizes sixteen transform-skipped coefficients.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The dequantized coefficients.</param>
|
||||
|
/// <param name="shift">The nonnegative shift magnitude.</param>
|
||||
|
/// <returns>The reconstructed residuals.</returns>
|
||||
|
static abstract Vector512<int> Invoke(Vector512<int> values, int shift); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Normalizes eight transform-skipped coefficients.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The dequantized coefficients.</param>
|
||||
|
/// <param name="shift">The nonnegative shift magnitude.</param>
|
||||
|
/// <returns>The reconstructed residuals.</returns>
|
||||
|
static abstract Vector256<int> Invoke(Vector256<int> values, int shift); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Normalizes four transform-skipped coefficients.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The dequantized coefficients.</param>
|
||||
|
/// <param name="shift">The nonnegative shift magnitude.</param>
|
||||
|
/// <returns>The reconstructed residuals.</returns>
|
||||
|
static abstract Vector128<int> Invoke(Vector128<int> values, int shift); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Normalizes one transform-skipped coefficient.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The dequantized coefficient.</param>
|
||||
|
/// <param name="shift">The nonnegative shift magnitude.</param>
|
||||
|
/// <returns>The reconstructed residual.</returns>
|
||||
|
static abstract int Invoke(int value, int shift); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the 4:2:2 chroma intra-angle remapping defined by H.265 Table 8-4.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<byte> Chroma422IntraAngleMap => |
||||
|
[ |
||||
|
0, 1, 2, 2, 2, 2, 3, 5, 7, 8, 10, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 29, 30, 31, |
||||
|
]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies one transquant-bypass coefficient block into residual sample order.
|
||||
|
/// </summary>
|
||||
|
/// <param name="coefficients">The decoded coefficients in raster order.</param>
|
||||
|
/// <param name="residual">The destination residual block in packed raster order.</param>
|
||||
|
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
||||
|
public static void CopyBypassed(ReadOnlySpan<int> coefficients, Span<int> residual, bool rotate) |
||||
|
{ |
||||
|
Span<int> destination = residual[..coefficients.Length]; |
||||
|
if (!rotate) |
||||
|
{ |
||||
|
coefficients.CopyTo(destination); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
CopyReversed(coefficients, destination); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs one transform-skipped residual block from dequantized coefficients.
|
||||
|
/// </summary>
|
||||
|
/// <param name="coefficients">The dequantized coefficients in raster order.</param>
|
||||
|
/// <param name="residual">The destination residual block in packed raster order.</param>
|
||||
|
/// <param name="width">The transform-block width.</param>
|
||||
|
/// <param name="height">The transform-block height.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
||||
|
/// <param name="equivalentLog2TransformSize">The base-two logarithm of the equivalent square transform size.</param>
|
||||
|
/// <param name="extendedPrecisionProcessingEnabled">Whether transform-skip precision is extended by the sequence.</param>
|
||||
|
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
||||
|
public static void ApplyTransformSkip( |
||||
|
ReadOnlySpan<int> coefficients, |
||||
|
Span<int> residual, |
||||
|
int width, |
||||
|
int height, |
||||
|
int bitDepth, |
||||
|
int maxTransformDynamicRange, |
||||
|
int equivalentLog2TransformSize, |
||||
|
bool extendedPrecisionProcessingEnabled, |
||||
|
bool rotate) |
||||
|
{ |
||||
|
int transformShift = maxTransformDynamicRange - bitDepth - equivalentLog2TransformSize; |
||||
|
if (extendedPrecisionProcessingEnabled) |
||||
|
{ |
||||
|
transformShift = Math.Max(0, transformShift); |
||||
|
} |
||||
|
|
||||
|
int coefficientCount = width * height; |
||||
|
if (transformShift >= 0) |
||||
|
{ |
||||
|
ApplyTransformSkip<RightShiftTransformSkipOperator>(coefficients[..coefficientCount], residual[..coefficientCount], transformShift, rotate); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ApplyTransformSkip<LeftShiftTransformSkipOperator>(coefficients[..coefficientCount], residual[..coefficientCount], -transformShift, rotate); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets whether a non-transformed residual block uses the HEVC Range Extensions coefficient rotation.
|
||||
|
/// </summary>
|
||||
|
/// <param name="transformSkipRotationEnabled">Whether the sequence enables transform-skip rotation.</param>
|
||||
|
/// <param name="isIntraPredicted">Whether the transform unit belongs to an intra-predicted coding unit.</param>
|
||||
|
/// <param name="width">The transform-block width.</param>
|
||||
|
/// <returns><see langword="true"/> when the complete coefficient order is reversed; otherwise, <see langword="false"/>.</returns>
|
||||
|
public static bool IsNonTransformedResidualRotated(bool transformSkipRotationEnabled, bool isIntraPredicted, int width) |
||||
|
=> transformSkipRotationEnabled && isIntraPredicted && width == 4; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the implicit residual differential mode selected by an intra-prediction direction.
|
||||
|
/// </summary>
|
||||
|
/// <param name="intraPredictionMode">The resolved luma or chroma intra-prediction mode.</param>
|
||||
|
/// <param name="remapChroma422">Whether the 4:2:2 chroma intra-angle remapping applies.</param>
|
||||
|
/// <returns>The residual differential mode selected by the prediction direction.</returns>
|
||||
|
public static HevcResidualDpcmMode GetImplicitResidualDpcmMode(int intraPredictionMode, bool remapChroma422) |
||||
|
{ |
||||
|
int predictionMode = remapChroma422 ? Chroma422IntraAngleMap[intraPredictionMode] : intraPredictionMode; |
||||
|
return predictionMode switch |
||||
|
{ |
||||
|
HorizontalIntraPredictionMode => HevcResidualDpcmMode.Horizontal, |
||||
|
VerticalIntraPredictionMode => HevcResidualDpcmMode.Vertical, |
||||
|
_ => HevcResidualDpcmMode.None, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies inverse residual differential pulse-code modulation to one packed residual block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="residual">The residual block in packed raster order.</param>
|
||||
|
/// <param name="width">The residual-block width.</param>
|
||||
|
/// <param name="height">The residual-block height.</param>
|
||||
|
/// <param name="mode">The differential accumulation direction.</param>
|
||||
|
public static void ApplyResidualDpcm(Span<int> residual, int width, int height, HevcResidualDpcmMode mode) |
||||
|
{ |
||||
|
if (mode == HevcResidualDpcmMode.Vertical) |
||||
|
{ |
||||
|
ApplyVerticalResidualDpcm(residual, width, height); |
||||
|
} |
||||
|
else if (mode == HevcResidualDpcmMode.Horizontal) |
||||
|
{ |
||||
|
ApplyHorizontalResidualDpcm(residual, width, height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies one transform-skip normalization operator to a complete coefficient block.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The signed shift operator selected before entering the hot loop.</typeparam>
|
||||
|
/// <param name="coefficients">The dequantized coefficients in raster order.</param>
|
||||
|
/// <param name="residual">The destination residual block in packed raster order.</param>
|
||||
|
/// <param name="shift">The nonnegative shift magnitude.</param>
|
||||
|
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
||||
|
private static void ApplyTransformSkip<TOperator>(ReadOnlySpan<int> coefficients, Span<int> residual, int shift, bool rotate) |
||||
|
where TOperator : struct, ITransformSkipOperator |
||||
|
{ |
||||
|
ref int sourceBase = ref MemoryMarshal.GetReference(coefficients); |
||||
|
ref int destinationBase = ref MemoryMarshal.GetReference(residual); |
||||
|
int count = coefficients.Length; |
||||
|
int index = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
for (; index <= count - Vector512<int>.Count; index += Vector512<int>.Count) |
||||
|
{ |
||||
|
Vector512<int> values = Load512(ref sourceBase, count, index, rotate); |
||||
|
TOperator.Invoke(values, shift).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
for (; index <= count - Vector256<int>.Count; index += Vector256<int>.Count) |
||||
|
{ |
||||
|
Vector256<int> values = Load256(ref sourceBase, count, index, rotate); |
||||
|
TOperator.Invoke(values, shift).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
for (; index <= count - Vector128<int>.Count; index += Vector128<int>.Count) |
||||
|
{ |
||||
|
Vector128<int> values = Load128(ref sourceBase, count, index, rotate); |
||||
|
TOperator.Invoke(values, shift).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; index < count; index++) |
||||
|
{ |
||||
|
int sourceIndex = rotate ? count - 1 - index : index; |
||||
|
Unsafe.Add(ref destinationBase, index) = TOperator.Invoke(Unsafe.Add(ref sourceBase, sourceIndex), shift); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies one coefficient block while reversing its complete raster order.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source coefficient block.</param>
|
||||
|
/// <param name="destination">The destination residual block.</param>
|
||||
|
private static void CopyReversed(ReadOnlySpan<int> source, Span<int> destination) |
||||
|
{ |
||||
|
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
int count = source.Length; |
||||
|
int index = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
for (; index <= count - Vector512<int>.Count; index += Vector512<int>.Count) |
||||
|
{ |
||||
|
Load512(ref sourceBase, count, index, true).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
for (; index <= count - Vector256<int>.Count; index += Vector256<int>.Count) |
||||
|
{ |
||||
|
Load256(ref sourceBase, count, index, true).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
for (; index <= count - Vector128<int>.Count; index += Vector128<int>.Count) |
||||
|
{ |
||||
|
Load128(ref sourceBase, count, index, true).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; index < count; index++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, index) = Unsafe.Add(ref sourceBase, count - 1 - index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Loads and optionally reverses sixteen source coefficients.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The first source coefficient.</param>
|
||||
|
/// <param name="count">The complete coefficient count.</param>
|
||||
|
/// <param name="index">The destination coefficient index.</param>
|
||||
|
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
||||
|
/// <returns>The source coefficients in destination order.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<int> Load512(ref int source, int count, int index, bool rotate) |
||||
|
{ |
||||
|
if (!rotate) |
||||
|
{ |
||||
|
return Vector512.LoadUnsafe(ref source, (nuint)index); |
||||
|
} |
||||
|
|
||||
|
Vector512<int> values = Vector512.LoadUnsafe(ref source, (nuint)(count - index - Vector512<int>.Count)); |
||||
|
return Vector512.Shuffle(values, Vector512.Create(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Loads and optionally reverses eight source coefficients.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The first source coefficient.</param>
|
||||
|
/// <param name="count">The complete coefficient count.</param>
|
||||
|
/// <param name="index">The destination coefficient index.</param>
|
||||
|
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
||||
|
/// <returns>The source coefficients in destination order.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> Load256(ref int source, int count, int index, bool rotate) |
||||
|
{ |
||||
|
if (!rotate) |
||||
|
{ |
||||
|
return Vector256.LoadUnsafe(ref source, (nuint)index); |
||||
|
} |
||||
|
|
||||
|
Vector256<int> values = Vector256.LoadUnsafe(ref source, (nuint)(count - index - Vector256<int>.Count)); |
||||
|
return Vector256.Shuffle(values, Vector256.Create(7, 6, 5, 4, 3, 2, 1, 0)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Loads and optionally reverses four source coefficients.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The first source coefficient.</param>
|
||||
|
/// <param name="count">The complete coefficient count.</param>
|
||||
|
/// <param name="index">The destination coefficient index.</param>
|
||||
|
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
||||
|
/// <returns>The source coefficients in destination order.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<int> Load128(ref int source, int count, int index, bool rotate) |
||||
|
{ |
||||
|
if (!rotate) |
||||
|
{ |
||||
|
return Vector128.LoadUnsafe(ref source, (nuint)index); |
||||
|
} |
||||
|
|
||||
|
Vector128<int> values = Vector128.LoadUnsafe(ref source, (nuint)(count - index - Vector128<int>.Count)); |
||||
|
return Vector128.Shuffle(values, Vector128.Create(3, 2, 1, 0)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Accumulates residual differences from top to bottom while processing independent columns in SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="residual">The residual block in packed raster order.</param>
|
||||
|
/// <param name="width">The residual-block width.</param>
|
||||
|
/// <param name="height">The residual-block height.</param>
|
||||
|
private static void ApplyVerticalResidualDpcm(Span<int> residual, int width, int height) |
||||
|
{ |
||||
|
ref int residualBase = ref MemoryMarshal.GetReference(residual); |
||||
|
int x = 0; |
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector512<int> minimum = Vector512.Create(ResidualMinimum); |
||||
|
Vector512<int> maximum = Vector512.Create(ResidualMaximum); |
||||
|
for (; x <= width - Vector512<int>.Count; x += Vector512<int>.Count) |
||||
|
{ |
||||
|
Vector512<int> accumulator = Vector512.LoadUnsafe(ref residualBase, (nuint)x); |
||||
|
for (int y = 1; y < height; y++) |
||||
|
{ |
||||
|
int index = (y * width) + x; |
||||
|
accumulator += Vector512.LoadUnsafe(ref residualBase, (nuint)index); |
||||
|
Vector512.Clamp(accumulator, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector256<int> minimum = Vector256.Create(ResidualMinimum); |
||||
|
Vector256<int> maximum = Vector256.Create(ResidualMaximum); |
||||
|
for (; x <= width - Vector256<int>.Count; x += Vector256<int>.Count) |
||||
|
{ |
||||
|
Vector256<int> accumulator = Vector256.LoadUnsafe(ref residualBase, (nuint)x); |
||||
|
for (int y = 1; y < height; y++) |
||||
|
{ |
||||
|
int index = (y * width) + x; |
||||
|
accumulator += Vector256.LoadUnsafe(ref residualBase, (nuint)index); |
||||
|
Vector256.Clamp(accumulator, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<int> minimum = Vector128.Create(ResidualMinimum); |
||||
|
Vector128<int> maximum = Vector128.Create(ResidualMaximum); |
||||
|
for (; x <= width - Vector128<int>.Count; x += Vector128<int>.Count) |
||||
|
{ |
||||
|
Vector128<int> accumulator = Vector128.LoadUnsafe(ref residualBase, (nuint)x); |
||||
|
for (int y = 1; y < height; y++) |
||||
|
{ |
||||
|
int index = (y * width) + x; |
||||
|
accumulator += Vector128.LoadUnsafe(ref residualBase, (nuint)index); |
||||
|
Vector128.Clamp(accumulator, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; x < width; x++) |
||||
|
{ |
||||
|
int accumulator = Unsafe.Add(ref residualBase, x); |
||||
|
for (int y = 1; y < height; y++) |
||||
|
{ |
||||
|
int index = (y * width) + x; |
||||
|
accumulator += Unsafe.Add(ref residualBase, index); |
||||
|
Unsafe.Add(ref residualBase, index) = Math.Clamp(accumulator, ResidualMinimum, ResidualMaximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Accumulates residual differences from left to right using an inclusive SIMD prefix sum for each row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="residual">The residual block in packed raster order.</param>
|
||||
|
/// <param name="width">The residual-block width.</param>
|
||||
|
/// <param name="height">The residual-block height.</param>
|
||||
|
private static void ApplyHorizontalResidualDpcm(Span<int> residual, int width, int height) |
||||
|
{ |
||||
|
ref int residualBase = ref MemoryMarshal.GetReference(residual); |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
int rowOffset = y * width; |
||||
|
int x = 0; |
||||
|
int accumulator = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector512<int> minimum = Vector512.Create(ResidualMinimum); |
||||
|
Vector512<int> maximum = Vector512.Create(ResidualMaximum); |
||||
|
for (; x <= width - Vector512<int>.Count; x += Vector512<int>.Count) |
||||
|
{ |
||||
|
Vector512<int> values = Vector512.LoadUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
||||
|
values = PrefixSum(values) + Vector512.Create(accumulator); |
||||
|
accumulator = values.GetElement(Vector512<int>.Count - 1); |
||||
|
Vector512.Clamp(values, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector256<int> minimum = Vector256.Create(ResidualMinimum); |
||||
|
Vector256<int> maximum = Vector256.Create(ResidualMaximum); |
||||
|
for (; x <= width - Vector256<int>.Count; x += Vector256<int>.Count) |
||||
|
{ |
||||
|
Vector256<int> values = Vector256.LoadUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
||||
|
values = PrefixSum(values) + Vector256.Create(accumulator); |
||||
|
accumulator = values.GetElement(Vector256<int>.Count - 1); |
||||
|
Vector256.Clamp(values, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<int> minimum = Vector128.Create(ResidualMinimum); |
||||
|
Vector128<int> maximum = Vector128.Create(ResidualMaximum); |
||||
|
for (; x <= width - Vector128<int>.Count; x += Vector128<int>.Count) |
||||
|
{ |
||||
|
Vector128<int> values = Vector128.LoadUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
||||
|
values = PrefixSum(values) + Vector128.Create(accumulator); |
||||
|
accumulator = values.GetElement(Vector128<int>.Count - 1); |
||||
|
Vector128.Clamp(values, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; x < width; x++) |
||||
|
{ |
||||
|
int index = rowOffset + x; |
||||
|
accumulator += Unsafe.Add(ref residualBase, index); |
||||
|
Unsafe.Add(ref residualBase, index) = x == 0 ? accumulator : Math.Clamp(accumulator, ResidualMinimum, ResidualMaximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Computes an inclusive prefix sum across sixteen signed lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The residual differences.</param>
|
||||
|
/// <returns>The accumulated residuals.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<int> PrefixSum(Vector512<int> values) |
||||
|
{ |
||||
|
values += Vector512.Shuffle(values, Vector512.Create(16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)); |
||||
|
values += Vector512.Shuffle(values, Vector512.Create(16, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)); |
||||
|
values += Vector512.Shuffle(values, Vector512.Create(16, 16, 16, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); |
||||
|
return values + Vector512.Shuffle(values, Vector512.Create(16, 16, 16, 16, 16, 16, 16, 16, 0, 1, 2, 3, 4, 5, 6, 7)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Computes an inclusive prefix sum across eight signed lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The residual differences.</param>
|
||||
|
/// <returns>The accumulated residuals.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> PrefixSum(Vector256<int> values) |
||||
|
{ |
||||
|
values += Vector256.Shuffle(values, Vector256.Create(8, 0, 1, 2, 3, 4, 5, 6)); |
||||
|
values += Vector256.Shuffle(values, Vector256.Create(8, 8, 0, 1, 2, 3, 4, 5)); |
||||
|
return values + Vector256.Shuffle(values, Vector256.Create(8, 8, 8, 8, 0, 1, 2, 3)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Computes an inclusive prefix sum across four signed lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The residual differences.</param>
|
||||
|
/// <returns>The accumulated residuals.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<int> PrefixSum(Vector128<int> values) |
||||
|
{ |
||||
|
values += Vector128.Shuffle(values, Vector128.Create(4, 0, 1, 2)); |
||||
|
return values + Vector128.Shuffle(values, Vector128.Create(4, 4, 0, 1)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the rounded right shift used by ordinary transform-skip reconstruction.
|
||||
|
/// </summary>
|
||||
|
private readonly struct RightShiftTransformSkipOperator : ITransformSkipOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<int> Invoke(Vector512<int> values, int shift) |
||||
|
=> shift == 0 ? values : (values + Vector512.Create(1 << (shift - 1))) >> shift; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<int> Invoke(Vector256<int> values, int shift) |
||||
|
=> shift == 0 ? values : (values + Vector256.Create(1 << (shift - 1))) >> shift; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<int> Invoke(Vector128<int> values, int shift) |
||||
|
=> shift == 0 ? values : (values + Vector128.Create(1 << (shift - 1))) >> shift; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static int Invoke(int value, int shift) => shift == 0 ? value : (value + (1 << (shift - 1))) >> shift; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the exact left shift used by high-bit-depth transform-skip reconstruction.
|
||||
|
/// </summary>
|
||||
|
private readonly struct LeftShiftTransformSkipOperator : ITransformSkipOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<int> Invoke(Vector512<int> values, int shift) => values << shift; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<int> Invoke(Vector256<int> values, int shift) => values << shift; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<int> Invoke(Vector128<int> values, int shift) => values << shift; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static int Invoke(int value, int shift) => value << shift; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Hevc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures complete coded-frame traversal through HEVC transform-skip and residual differential reconstruction.
|
||||
|
/// </summary>
|
||||
|
[MemoryDiagnoser(displayGenColumns: false)] |
||||
|
public class HevcResidualReconstructionBenchmarks |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The coded frame width, which is an exact multiple of the maximum transform-block side.
|
||||
|
/// </summary>
|
||||
|
private const int Width = 1920; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The coded frame height including the final padded coding-tree row for a 1080-line presentation.
|
||||
|
/// </summary>
|
||||
|
private const int Height = 1088; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The benchmark transform-block side in samples.
|
||||
|
/// </summary>
|
||||
|
private const int BlockSize = 32; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The deterministic dequantized coefficients reused by each benchmark block.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] coefficients = new int[BlockSize * BlockSize]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The reusable packed residual block.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] residual = new int[BlockSize * BlockSize]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Populates a dense, deterministic twelve-bit transform-skip workload outside the measured frame traversal.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
for (int i = 0; i < this.coefficients.Length; i++) |
||||
|
{ |
||||
|
this.coefficients[i] = (((i * 104729) + 4099) & 8191) - 4096; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures frame-wide transform-skip normalization.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final residual, keeping the block output observable.</returns>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
public int TransformSkipFrame() => this.ReconstructFrame(HevcResidualDpcmMode.None); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures frame-wide transform-skip normalization followed by horizontal residual differential reconstruction.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final residual, keeping the block output observable.</returns>
|
||||
|
[Benchmark] |
||||
|
public int HorizontalResidualDpcmFrame() => this.ReconstructFrame(HevcResidualDpcmMode.Horizontal); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures frame-wide transform-skip normalization followed by vertical residual differential reconstruction.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final residual, keeping the block output observable.</returns>
|
||||
|
[Benchmark] |
||||
|
public int VerticalResidualDpcmFrame() => this.ReconstructFrame(HevcResidualDpcmMode.Vertical); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs every maximum-size transform block in the coded benchmark frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="mode">The residual differential mode applied after transform-skip normalization.</param>
|
||||
|
/// <returns>The final reconstructed residual.</returns>
|
||||
|
private int ReconstructFrame(HevcResidualDpcmMode mode) |
||||
|
{ |
||||
|
for (int y = 0; y < Height; y += BlockSize) |
||||
|
{ |
||||
|
for (int x = 0; x < Width; x += BlockSize) |
||||
|
{ |
||||
|
HevcResidualReconstructor.ApplyTransformSkip(this.coefficients, this.residual, BlockSize, BlockSize, 12, 18, 5, true, false); |
||||
|
HevcResidualReconstructor.ApplyResidualDpcm(this.residual, BlockSize, BlockSize, mode); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return this.residual[^1]; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,268 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Hevc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies HEVC transform-skip, transquant-bypass, rotation, and residual differential reconstruction.
|
||||
|
/// </summary>
|
||||
|
[Trait("Format", "Heic")] |
||||
|
public class HevcResidualReconstructorTests |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Verifies that lossless transquant bypass preserves or completely reverses coefficient order.
|
||||
|
/// </summary>
|
||||
|
/// <param name="rotate">Whether the coefficient order is reversed.</param>
|
||||
|
[Theory] |
||||
|
[InlineData(false)] |
||||
|
[InlineData(true)] |
||||
|
public void CopyBypassedPreservesOrRotatesCoefficientOrder(bool rotate) |
||||
|
{ |
||||
|
int[] coefficients = new int[1024]; |
||||
|
int[] actual = new int[coefficients.Length]; |
||||
|
int[] expected = new int[coefficients.Length]; |
||||
|
for (int i = 0; i < coefficients.Length; i++) |
||||
|
{ |
||||
|
coefficients[i] = (i * 17) - 8000; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < coefficients.Length; i++) |
||||
|
{ |
||||
|
expected[i] = rotate ? coefficients[coefficients.Length - 1 - i] : coefficients[i]; |
||||
|
} |
||||
|
|
||||
|
HevcResidualReconstructor.CopyBypassed(coefficients, actual, rotate); |
||||
|
|
||||
|
int mismatch = expected.AsSpan().SequenceEqual(actual) ? -1 : FindFirstMismatch(expected, actual); |
||||
|
Assert.True(mismatch < 0, mismatch < 0 ? string.Empty : $"Mismatch at {mismatch}: expected {expected[mismatch]}, actual {actual[mismatch]}."); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares SIMD transform-skip reconstruction with a scalar oracle across transform sizes and signed shift directions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="width">The transform-block width.</param>
|
||||
|
/// <param name="height">The transform-block height.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
||||
|
/// <param name="equivalentLog2TransformSize">The base-two logarithm of the equivalent square transform size.</param>
|
||||
|
/// <param name="extendedPrecisionProcessingEnabled">Whether extended transform-skip precision applies.</param>
|
||||
|
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
||||
|
[Theory] |
||||
|
[InlineData(4, 4, 8, 15, 2, false, true)] |
||||
|
[InlineData(4, 8, 8, 15, 3, false, false)] |
||||
|
[InlineData(8, 4, 10, 15, 2, false, false)] |
||||
|
[InlineData(8, 8, 10, 15, 3, false, false)] |
||||
|
[InlineData(16, 16, 12, 15, 4, false, false)] |
||||
|
[InlineData(16, 16, 12, 15, 4, true, false)] |
||||
|
[InlineData(32, 32, 12, 18, 5, false, false)] |
||||
|
public void TransformSkipMatchesScalarOracle( |
||||
|
int width, |
||||
|
int height, |
||||
|
int bitDepth, |
||||
|
int maxTransformDynamicRange, |
||||
|
int equivalentLog2TransformSize, |
||||
|
bool extendedPrecisionProcessingEnabled, |
||||
|
bool rotate) |
||||
|
{ |
||||
|
int coefficientCount = width * height; |
||||
|
int[] coefficients = new int[coefficientCount]; |
||||
|
int[] actual = new int[coefficientCount]; |
||||
|
int[] expected = new int[coefficientCount]; |
||||
|
for (int i = 0; i < coefficientCount; i++) |
||||
|
{ |
||||
|
coefficients[i] = (((i * 7919) + (width * 257)) & 65535) - 32768; |
||||
|
} |
||||
|
|
||||
|
ApplyTransformSkipScalar( |
||||
|
coefficients, |
||||
|
expected, |
||||
|
bitDepth, |
||||
|
maxTransformDynamicRange, |
||||
|
equivalentLog2TransformSize, |
||||
|
extendedPrecisionProcessingEnabled, |
||||
|
rotate); |
||||
|
|
||||
|
HevcResidualReconstructor.ApplyTransformSkip( |
||||
|
coefficients, |
||||
|
actual, |
||||
|
width, |
||||
|
height, |
||||
|
bitDepth, |
||||
|
maxTransformDynamicRange, |
||||
|
equivalentLog2TransformSize, |
||||
|
extendedPrecisionProcessingEnabled, |
||||
|
rotate); |
||||
|
|
||||
|
Assert.True(expected.AsSpan().SequenceEqual(actual)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares SIMD residual differential reconstruction with the sequential normative recurrence.
|
||||
|
/// </summary>
|
||||
|
/// <param name="size">The square residual-block side.</param>
|
||||
|
/// <param name="modeValue">The numeric differential accumulation direction.</param>
|
||||
|
[Theory] |
||||
|
[InlineData(4, 1)] |
||||
|
[InlineData(4, 2)] |
||||
|
[InlineData(8, 1)] |
||||
|
[InlineData(8, 2)] |
||||
|
[InlineData(16, 1)] |
||||
|
[InlineData(16, 2)] |
||||
|
[InlineData(32, 1)] |
||||
|
[InlineData(32, 2)] |
||||
|
public void ResidualDpcmMatchesScalarOracle(int size, int modeValue) |
||||
|
{ |
||||
|
HevcResidualDpcmMode mode = (HevcResidualDpcmMode)modeValue; |
||||
|
int[] actual = new int[size * size]; |
||||
|
for (int i = 0; i < actual.Length; i++) |
||||
|
{ |
||||
|
actual[i] = (((i * 104729) + (size * 4099)) & 8191) - 4096; |
||||
|
} |
||||
|
|
||||
|
int[] expected = (int[])actual.Clone(); |
||||
|
ApplyResidualDpcmScalar(expected, size, size, mode); |
||||
|
|
||||
|
HevcResidualReconstructor.ApplyResidualDpcm(actual, size, size, mode); |
||||
|
|
||||
|
Assert.True(expected.AsSpan().SequenceEqual(actual)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies signed residual clipping without clipping the thirty-two-bit recurrence accumulator.
|
||||
|
/// </summary>
|
||||
|
/// <param name="modeValue">The numeric differential accumulation direction.</param>
|
||||
|
[Theory] |
||||
|
[InlineData(1)] |
||||
|
[InlineData(2)] |
||||
|
public void ResidualDpcmClipsStoredSamples(int modeValue) |
||||
|
{ |
||||
|
HevcResidualDpcmMode mode = (HevcResidualDpcmMode)modeValue; |
||||
|
int[] actual = new int[32 * 32]; |
||||
|
actual.AsSpan().Fill(3000); |
||||
|
int[] expected = (int[])actual.Clone(); |
||||
|
ApplyResidualDpcmScalar(expected, 32, 32, mode); |
||||
|
|
||||
|
HevcResidualReconstructor.ApplyResidualDpcm(actual, 32, 32, mode); |
||||
|
|
||||
|
Assert.True(expected.AsSpan().SequenceEqual(actual)); |
||||
|
Assert.Contains(short.MaxValue, actual); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies the Range Extensions rotation constraint for non-transformed intra blocks.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void RotationRequiresEnabledFourWideIntraBlock() |
||||
|
{ |
||||
|
Assert.True(HevcResidualReconstructor.IsNonTransformedResidualRotated(true, true, 4)); |
||||
|
Assert.False(HevcResidualReconstructor.IsNonTransformedResidualRotated(false, true, 4)); |
||||
|
Assert.False(HevcResidualReconstructor.IsNonTransformedResidualRotated(true, false, 4)); |
||||
|
Assert.False(HevcResidualReconstructor.IsNonTransformedResidualRotated(true, true, 8)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies implicit residual differential mode selection, including 4:2:2 chroma angle remapping.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void ImplicitResidualDpcmFollowsPredictionDirection() |
||||
|
{ |
||||
|
Assert.Equal(HevcResidualDpcmMode.Horizontal, HevcResidualReconstructor.GetImplicitResidualDpcmMode(10, false)); |
||||
|
Assert.Equal(HevcResidualDpcmMode.Vertical, HevcResidualReconstructor.GetImplicitResidualDpcmMode(26, false)); |
||||
|
Assert.Equal(HevcResidualDpcmMode.None, HevcResidualReconstructor.GetImplicitResidualDpcmMode(18, false)); |
||||
|
Assert.Equal(HevcResidualDpcmMode.Horizontal, HevcResidualReconstructor.GetImplicitResidualDpcmMode(10, true)); |
||||
|
Assert.Equal(HevcResidualDpcmMode.Vertical, HevcResidualReconstructor.GetImplicitResidualDpcmMode(26, true)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the normative transform-skip normalization as a scalar test oracle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="coefficients">The dequantized coefficients.</param>
|
||||
|
/// <param name="residual">The destination residual block.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
||||
|
/// <param name="equivalentLog2TransformSize">The base-two logarithm of the equivalent square transform size.</param>
|
||||
|
/// <param name="extendedPrecisionProcessingEnabled">Whether extended transform-skip precision applies.</param>
|
||||
|
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
||||
|
private static void ApplyTransformSkipScalar( |
||||
|
ReadOnlySpan<int> coefficients, |
||||
|
Span<int> residual, |
||||
|
int bitDepth, |
||||
|
int maxTransformDynamicRange, |
||||
|
int equivalentLog2TransformSize, |
||||
|
bool extendedPrecisionProcessingEnabled, |
||||
|
bool rotate) |
||||
|
{ |
||||
|
int shift = maxTransformDynamicRange - bitDepth - equivalentLog2TransformSize; |
||||
|
if (extendedPrecisionProcessingEnabled) |
||||
|
{ |
||||
|
shift = Math.Max(0, shift); |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < coefficients.Length; i++) |
||||
|
{ |
||||
|
int value = coefficients[rotate ? coefficients.Length - 1 - i : i]; |
||||
|
residual[i] = shift > 0 |
||||
|
? (value + (1 << (shift - 1))) >> shift |
||||
|
: value << -shift; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the normative inverse residual differential recurrence as a scalar test oracle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="residual">The residual block in packed raster order.</param>
|
||||
|
/// <param name="width">The residual-block width.</param>
|
||||
|
/// <param name="height">The residual-block height.</param>
|
||||
|
/// <param name="mode">The differential accumulation direction.</param>
|
||||
|
private static void ApplyResidualDpcmScalar(Span<int> residual, int width, int height, HevcResidualDpcmMode mode) |
||||
|
{ |
||||
|
if (mode == HevcResidualDpcmMode.Vertical) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
int accumulator = residual[x]; |
||||
|
for (int y = 1; y < height; y++) |
||||
|
{ |
||||
|
int index = (y * width) + x; |
||||
|
accumulator += residual[index]; |
||||
|
residual[index] = Math.Clamp(accumulator, short.MinValue, short.MaxValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else if (mode == HevcResidualDpcmMode.Horizontal) |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
int rowOffset = y * width; |
||||
|
int accumulator = residual[rowOffset]; |
||||
|
for (int x = 1; x < width; x++) |
||||
|
{ |
||||
|
int index = rowOffset + x; |
||||
|
accumulator += residual[index]; |
||||
|
residual[index] = Math.Clamp(accumulator, short.MinValue, short.MaxValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Finds the first unequal element in two equally sized test buffers.
|
||||
|
/// </summary>
|
||||
|
/// <param name="expected">The expected values.</param>
|
||||
|
/// <param name="actual">The actual values.</param>
|
||||
|
/// <returns>The first unequal index.</returns>
|
||||
|
private static int FindFirstMismatch(ReadOnlySpan<int> expected, ReadOnlySpan<int> actual) |
||||
|
{ |
||||
|
for (int i = 0; i < expected.Length; i++) |
||||
|
{ |
||||
|
if (expected[i] != actual[i]) |
||||
|
{ |
||||
|
return i; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return -1; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue