mirror of https://github.com/SixLabors/ImageSharp
6 changed files with 1634 additions and 1 deletions
@ -0,0 +1,423 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
||||
|
|
||||
|
/// <content>
|
||||
|
/// Provides shared SIMD operations used by the closed prediction operators.
|
||||
|
/// </content>
|
||||
|
internal static partial class HevcIntraPredictor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Calculates one 512-bit half of a planar prediction row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="indices">The zero-based X coordinates.</param>
|
||||
|
/// <param name="left">The left reference sample for the row.</param>
|
||||
|
/// <param name="topRight">The top-right reference sample.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
||||
|
/// <param name="topWeight">The top-reference weight.</param>
|
||||
|
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="rounding">The division rounding constant.</param>
|
||||
|
/// <param name="shift">The division shift.</param>
|
||||
|
/// <returns>The predicted samples as widened lanes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<uint> CalculatePlanarVector( |
||||
|
Vector512<uint> top, |
||||
|
Vector512<uint> indices, |
||||
|
uint left, |
||||
|
uint topRight, |
||||
|
uint bottomLeft, |
||||
|
uint topWeight, |
||||
|
uint bottomWeight, |
||||
|
uint size, |
||||
|
uint rounding, |
||||
|
int shift) |
||||
|
{ |
||||
|
Vector512<uint> horizontal = ((Vector512.Create(size - 1) - indices) * left) + ((indices + Vector512<uint>.One) * topRight); |
||||
|
Vector512<uint> vertical = (top * topWeight) + Vector512.Create(bottomLeft * bottomWeight); |
||||
|
return (horizontal + vertical + Vector512.Create(rounding)) >> shift; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates one 256-bit half of a planar prediction row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="indices">The zero-based X coordinates.</param>
|
||||
|
/// <param name="left">The left reference sample for the row.</param>
|
||||
|
/// <param name="topRight">The top-right reference sample.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
||||
|
/// <param name="topWeight">The top-reference weight.</param>
|
||||
|
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="rounding">The division rounding constant.</param>
|
||||
|
/// <param name="shift">The division shift.</param>
|
||||
|
/// <returns>The predicted samples as widened lanes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<uint> CalculatePlanarVector( |
||||
|
Vector256<uint> top, |
||||
|
Vector256<uint> indices, |
||||
|
uint left, |
||||
|
uint topRight, |
||||
|
uint bottomLeft, |
||||
|
uint topWeight, |
||||
|
uint bottomWeight, |
||||
|
uint size, |
||||
|
uint rounding, |
||||
|
int shift) |
||||
|
{ |
||||
|
Vector256<uint> horizontal = ((Vector256.Create(size - 1) - indices) * left) + ((indices + Vector256<uint>.One) * topRight); |
||||
|
Vector256<uint> vertical = (top * topWeight) + Vector256.Create(bottomLeft * bottomWeight); |
||||
|
return (horizontal + vertical + Vector256.Create(rounding)) >> shift; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates one 128-bit half of a planar prediction row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="indices">The zero-based X coordinates.</param>
|
||||
|
/// <param name="left">The left reference sample for the row.</param>
|
||||
|
/// <param name="topRight">The top-right reference sample.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
||||
|
/// <param name="topWeight">The top-reference weight.</param>
|
||||
|
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="rounding">The division rounding constant.</param>
|
||||
|
/// <param name="shift">The division shift.</param>
|
||||
|
/// <returns>The predicted samples as widened lanes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<uint> CalculatePlanarVector( |
||||
|
Vector128<uint> top, |
||||
|
Vector128<uint> indices, |
||||
|
uint left, |
||||
|
uint topRight, |
||||
|
uint bottomLeft, |
||||
|
uint topWeight, |
||||
|
uint bottomWeight, |
||||
|
uint size, |
||||
|
uint rounding, |
||||
|
int shift) |
||||
|
{ |
||||
|
Vector128<uint> horizontal = ((Vector128.Create(size - 1) - indices) * left) + ((indices + Vector128<uint>.One) * topRight); |
||||
|
Vector128<uint> vertical = (top * topWeight) + Vector128.Create(bottomLeft * bottomWeight); |
||||
|
return (horizontal + vertical + Vector128.Create(rounding)) >> shift; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sums reconstructed reference samples without overflowing their 16-bit storage.
|
||||
|
/// </summary>
|
||||
|
/// <param name="samples">The samples to sum.</param>
|
||||
|
/// <returns>The exact unsigned sum.</returns>
|
||||
|
private static uint SumSamples(ReadOnlySpan<ushort> samples) |
||||
|
{ |
||||
|
ref ushort samplesBase = ref MemoryMarshal.GetReference(samples); |
||||
|
uint sum = 0; |
||||
|
int i = 0; |
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector512<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
||||
|
{ |
||||
|
(Vector512<uint> low, Vector512<uint> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)i)); |
||||
|
sum += Vector512.Sum(low) + Vector512.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector256<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
||||
|
{ |
||||
|
(Vector256<uint> low, Vector256<uint> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)i)); |
||||
|
sum += Vector256.Sum(low) + Vector256.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector128<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
||||
|
{ |
||||
|
(Vector128<uint> low, Vector128<uint> high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)i)); |
||||
|
sum += Vector128.Sum(low) + Vector128.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; i < samples.Length; i++) |
||||
|
{ |
||||
|
sum += Unsafe.Add(ref samplesBase, i); |
||||
|
} |
||||
|
|
||||
|
return sum; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies the top reference into every row and optionally filters the first column.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference samples.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the vertical luma edge filter applies.</param>
|
||||
|
private static void PredictVertical( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges) |
||||
|
{ |
||||
|
ReadOnlySpan<ushort> row = top.Slice(1, size); |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
row.CopyTo(destination.Slice(y * destinationStride, size)); |
||||
|
if (filterPredictionEdges) |
||||
|
{ |
||||
|
int sample = destination[y * destinationStride] + ((left[y + 1] - left[0]) >> 1); |
||||
|
destination[y * destinationStride] = (ushort)Math.Clamp(sample, 0, maximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Fills each row from its left reference and optionally filters the first row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference samples.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the horizontal luma edge filter applies.</param>
|
||||
|
private static void PredictHorizontal( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges) |
||||
|
{ |
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
destination.Slice(y * destinationStride, size).Fill(left[y + 1]); |
||||
|
} |
||||
|
|
||||
|
if (!filterPredictionEdges) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
for (int x = 0; x < size; x++) |
||||
|
{ |
||||
|
int sample = destination[x] + ((top[x + 1] - top[0]) >> 1); |
||||
|
destination[x] = (ushort)Math.Clamp(sample, 0, maximum); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Generates a vertical-oriented angular block using contiguous SIMD interpolation within each row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="main">The main reference beginning at logical index zero.</param>
|
||||
|
/// <param name="mainOrigin">The span index corresponding to logical reference index zero.</param>
|
||||
|
/// <param name="destination">The contiguous destination or transposition scratch block.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="angle">The signed prediction displacement in thirty-second-sample units.</param>
|
||||
|
private static void PredictAngularRows( |
||||
|
ReadOnlySpan<ushort> main, |
||||
|
int mainOrigin, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int angle) |
||||
|
{ |
||||
|
for (int y = 0, deltaPosition = angle; y < size; y++, deltaPosition += angle) |
||||
|
{ |
||||
|
int deltaInteger = deltaPosition >> 5; |
||||
|
int deltaFraction = deltaPosition & 31; |
||||
|
int sourceOffset = mainOrigin + deltaInteger + 1; |
||||
|
Span<ushort> row = destination.Slice(y * destinationStride, size); |
||||
|
if (deltaFraction == 0) |
||||
|
{ |
||||
|
main.Slice(sourceOffset, size).CopyTo(row); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
InterpolateAngularRow(main[sourceOffset..], row, deltaFraction); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates one angular prediction row between consecutive main-reference samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The first main-reference sample for the row.</param>
|
||||
|
/// <param name="destination">The destination prediction row.</param>
|
||||
|
/// <param name="fraction">The right-hand weight with a denominator of thirty-two.</param>
|
||||
|
private static void InterpolateAngularRow(ReadOnlySpan<ushort> source, Span<ushort> destination, int fraction) |
||||
|
{ |
||||
|
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
uint leftWeight = (uint)(32 - fraction); |
||||
|
uint rightWeight = (uint)fraction; |
||||
|
int i = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = destination.Length - Vector512<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
||||
|
{ |
||||
|
Vector512<ushort> left = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); |
||||
|
Vector512<ushort> right = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
||||
|
(Vector512<uint> leftLow, Vector512<uint> leftHigh) = Vector512.Widen(left); |
||||
|
(Vector512<uint> rightLow, Vector512<uint> rightHigh) = Vector512.Widen(right); |
||||
|
Vector512<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector512.Create(16U)) >> 5; |
||||
|
Vector512<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector512.Create(16U)) >> 5; |
||||
|
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = destination.Length - Vector256<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
||||
|
{ |
||||
|
Vector256<ushort> left = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); |
||||
|
Vector256<ushort> right = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
||||
|
(Vector256<uint> leftLow, Vector256<uint> leftHigh) = Vector256.Widen(left); |
||||
|
(Vector256<uint> rightLow, Vector256<uint> rightHigh) = Vector256.Widen(right); |
||||
|
Vector256<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector256.Create(16U)) >> 5; |
||||
|
Vector256<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector256.Create(16U)) >> 5; |
||||
|
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = destination.Length - Vector128<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
||||
|
{ |
||||
|
Vector128<ushort> left = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); |
||||
|
Vector128<ushort> right = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
||||
|
(Vector128<uint> leftLow, Vector128<uint> leftHigh) = Vector128.Widen(left); |
||||
|
(Vector128<uint> rightLow, Vector128<uint> rightHigh) = Vector128.Widen(right); |
||||
|
Vector128<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector128.Create(16U)) >> 5; |
||||
|
Vector128<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector128.Create(16U)) >> 5; |
||||
|
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = (ushort)(((source[i] * leftWeight) + (source[i + 1] * rightWeight) + 16) >> 5); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transposes a square horizontal prediction block into the reconstructed destination.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The contiguous transposed prediction block.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
private static void TransposeBlock(ReadOnlySpan<ushort> source, Span<ushort> destination, int destinationStride, int size) |
||||
|
{ |
||||
|
if (Vector128.IsHardwareAccelerated && size >= Vector128<ushort>.Count) |
||||
|
{ |
||||
|
for (int y = 0; y < size; y += Vector128<ushort>.Count) |
||||
|
{ |
||||
|
for (int x = 0; x < size; x += Vector128<ushort>.Count) |
||||
|
{ |
||||
|
Transpose8x8(source, destination, destinationStride, size, x, y); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < size; x++) |
||||
|
{ |
||||
|
destination[(x * destinationStride) + y] = source[(y * size) + x]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transposes one eight-by-eight tile of 16-bit prediction samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The contiguous source block.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="sourceStride">The contiguous source row stride.</param>
|
||||
|
/// <param name="x">The tile X coordinate in the source block.</param>
|
||||
|
/// <param name="y">The tile Y coordinate in the source block.</param>
|
||||
|
private static void Transpose8x8( |
||||
|
ReadOnlySpan<ushort> source, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int sourceStride, |
||||
|
int x, |
||||
|
int y) |
||||
|
{ |
||||
|
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
Vector128<short> row0 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 0) * sourceStride) + x)).AsInt16(); |
||||
|
Vector128<short> row1 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 1) * sourceStride) + x)).AsInt16(); |
||||
|
Vector128<short> row2 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 2) * sourceStride) + x)).AsInt16(); |
||||
|
Vector128<short> row3 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 3) * sourceStride) + x)).AsInt16(); |
||||
|
Vector128<short> row4 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 4) * sourceStride) + x)).AsInt16(); |
||||
|
Vector128<short> row5 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 5) * sourceStride) + x)).AsInt16(); |
||||
|
Vector128<short> row6 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 6) * sourceStride) + x)).AsInt16(); |
||||
|
Vector128<short> row7 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 7) * sourceStride) + x)).AsInt16(); |
||||
|
|
||||
|
// Three zip stages exchange one, two, then four 16-bit coordinates. The resulting vectors are the eight
|
||||
|
// source columns in row order, so each can be stored contiguously into one destination row.
|
||||
|
Vector128<short> pair0 = Vector128_.UnpackLow(row0, row1); |
||||
|
Vector128<short> pair1 = Vector128_.UnpackHigh(row0, row1); |
||||
|
Vector128<short> pair2 = Vector128_.UnpackLow(row2, row3); |
||||
|
Vector128<short> pair3 = Vector128_.UnpackHigh(row2, row3); |
||||
|
Vector128<short> pair4 = Vector128_.UnpackLow(row4, row5); |
||||
|
Vector128<short> pair5 = Vector128_.UnpackHigh(row4, row5); |
||||
|
Vector128<short> pair6 = Vector128_.UnpackLow(row6, row7); |
||||
|
Vector128<short> pair7 = Vector128_.UnpackHigh(row6, row7); |
||||
|
Vector128<int> quad0 = Vector128_.UnpackLow(pair0.AsInt32(), pair2.AsInt32()); |
||||
|
Vector128<int> quad1 = Vector128_.UnpackHigh(pair0.AsInt32(), pair2.AsInt32()); |
||||
|
Vector128<int> quad2 = Vector128_.UnpackLow(pair1.AsInt32(), pair3.AsInt32()); |
||||
|
Vector128<int> quad3 = Vector128_.UnpackHigh(pair1.AsInt32(), pair3.AsInt32()); |
||||
|
Vector128<int> quad4 = Vector128_.UnpackLow(pair4.AsInt32(), pair6.AsInt32()); |
||||
|
Vector128<int> quad5 = Vector128_.UnpackHigh(pair4.AsInt32(), pair6.AsInt32()); |
||||
|
Vector128<int> quad6 = Vector128_.UnpackLow(pair5.AsInt32(), pair7.AsInt32()); |
||||
|
Vector128<int> quad7 = Vector128_.UnpackHigh(pair5.AsInt32(), pair7.AsInt32()); |
||||
|
Vector128<ushort> column0 = Vector128_.UnpackLow(quad0.AsInt64(), quad4.AsInt64()).AsUInt16(); |
||||
|
Vector128<ushort> column1 = Vector128_.UnpackHigh(quad0.AsInt64(), quad4.AsInt64()).AsUInt16(); |
||||
|
Vector128<ushort> column2 = Vector128_.UnpackLow(quad1.AsInt64(), quad5.AsInt64()).AsUInt16(); |
||||
|
Vector128<ushort> column3 = Vector128_.UnpackHigh(quad1.AsInt64(), quad5.AsInt64()).AsUInt16(); |
||||
|
Vector128<ushort> column4 = Vector128_.UnpackLow(quad2.AsInt64(), quad6.AsInt64()).AsUInt16(); |
||||
|
Vector128<ushort> column5 = Vector128_.UnpackHigh(quad2.AsInt64(), quad6.AsInt64()).AsUInt16(); |
||||
|
Vector128<ushort> column6 = Vector128_.UnpackLow(quad3.AsInt64(), quad7.AsInt64()).AsUInt16(); |
||||
|
Vector128<ushort> column7 = Vector128_.UnpackHigh(quad3.AsInt64(), quad7.AsInt64()).AsUInt16(); |
||||
|
column0.StoreUnsafe(ref destinationBase, (nuint)(((x + 0) * destinationStride) + y)); |
||||
|
column1.StoreUnsafe(ref destinationBase, (nuint)(((x + 1) * destinationStride) + y)); |
||||
|
column2.StoreUnsafe(ref destinationBase, (nuint)(((x + 2) * destinationStride) + y)); |
||||
|
column3.StoreUnsafe(ref destinationBase, (nuint)(((x + 3) * destinationStride) + y)); |
||||
|
column4.StoreUnsafe(ref destinationBase, (nuint)(((x + 4) * destinationStride) + y)); |
||||
|
column5.StoreUnsafe(ref destinationBase, (nuint)(((x + 5) * destinationStride) + y)); |
||||
|
column6.StoreUnsafe(ref destinationBase, (nuint)(((x + 6) * destinationStride) + y)); |
||||
|
column7.StoreUnsafe(ref destinationBase, (nuint)(((x + 7) * destinationStride) + y)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,280 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
||||
|
|
||||
|
/// <content>
|
||||
|
/// Provides the closed planar, DC, and angular prediction operators.
|
||||
|
/// </content>
|
||||
|
internal static partial class HevcIntraPredictor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements planar interpolation between the top, left, bottom-left, and top-right references.
|
||||
|
/// </summary>
|
||||
|
private readonly struct PlanarPredictionOperator : IHevcIntraPredictionOperator<PlanarPredictionOperator> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static void Predict( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int mode, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges, |
||||
|
Span<ushort> scratch) |
||||
|
{ |
||||
|
ref ushort topBase = ref MemoryMarshal.GetReference(top); |
||||
|
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
uint bottomLeft = left[size]; |
||||
|
uint topRight = top[size]; |
||||
|
int shift = BitOperations.Log2((uint)size) + 1; |
||||
|
uint rounding = (uint)size; |
||||
|
|
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
uint leftSample = left[y + 1]; |
||||
|
uint topWeight = (uint)(size - y - 1); |
||||
|
uint bottomWeight = (uint)(y + 1); |
||||
|
ref ushort rowBase = ref Unsafe.Add(ref destinationBase, y * destinationStride); |
||||
|
int x = 0; |
||||
|
|
||||
|
// The two widened halves carry consecutive X coordinates. Each lane evaluates the normative
|
||||
|
// horizontal and vertical ramps, then narrows after the common rounded power-of-two division.
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector512<uint> indices = CreateIndicesVector512(); |
||||
|
int oneVectorFromEnd = size - Vector512<ushort>.Count; |
||||
|
for (; x <= oneVectorFromEnd; x += Vector512<ushort>.Count) |
||||
|
{ |
||||
|
Vector512<ushort> topSamples = Vector512.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
||||
|
(Vector512<uint> topLow, Vector512<uint> topHigh) = Vector512.Widen(topSamples); |
||||
|
Vector512<uint> lowIndices = indices + Vector512.Create((uint)x); |
||||
|
Vector512<uint> highIndices = lowIndices + Vector512.Create((uint)Vector512<uint>.Count); |
||||
|
Vector512<uint> low = CalculatePlanarVector( |
||||
|
topLow, |
||||
|
lowIndices, |
||||
|
leftSample, |
||||
|
topRight, |
||||
|
bottomLeft, |
||||
|
topWeight, |
||||
|
bottomWeight, |
||||
|
(uint)size, |
||||
|
rounding, |
||||
|
shift); |
||||
|
|
||||
|
Vector512<uint> high = CalculatePlanarVector( |
||||
|
topHigh, |
||||
|
highIndices, |
||||
|
leftSample, |
||||
|
topRight, |
||||
|
bottomLeft, |
||||
|
topWeight, |
||||
|
bottomWeight, |
||||
|
(uint)size, |
||||
|
rounding, |
||||
|
shift); |
||||
|
|
||||
|
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector256<uint> indices = CreateIndicesVector256(); |
||||
|
int oneVectorFromEnd = size - Vector256<ushort>.Count; |
||||
|
for (; x <= oneVectorFromEnd; x += Vector256<ushort>.Count) |
||||
|
{ |
||||
|
Vector256<ushort> topSamples = Vector256.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
||||
|
(Vector256<uint> topLow, Vector256<uint> topHigh) = Vector256.Widen(topSamples); |
||||
|
Vector256<uint> lowIndices = indices + Vector256.Create((uint)x); |
||||
|
Vector256<uint> highIndices = lowIndices + Vector256.Create((uint)Vector256<uint>.Count); |
||||
|
Vector256<uint> low = CalculatePlanarVector( |
||||
|
topLow, |
||||
|
lowIndices, |
||||
|
leftSample, |
||||
|
topRight, |
||||
|
bottomLeft, |
||||
|
topWeight, |
||||
|
bottomWeight, |
||||
|
(uint)size, |
||||
|
rounding, |
||||
|
shift); |
||||
|
|
||||
|
Vector256<uint> high = CalculatePlanarVector( |
||||
|
topHigh, |
||||
|
highIndices, |
||||
|
leftSample, |
||||
|
topRight, |
||||
|
bottomLeft, |
||||
|
topWeight, |
||||
|
bottomWeight, |
||||
|
(uint)size, |
||||
|
rounding, |
||||
|
shift); |
||||
|
|
||||
|
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<uint> indices = CreateIndicesVector128(); |
||||
|
int oneVectorFromEnd = size - Vector128<ushort>.Count; |
||||
|
for (; x <= oneVectorFromEnd; x += Vector128<ushort>.Count) |
||||
|
{ |
||||
|
Vector128<ushort> topSamples = Vector128.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
||||
|
(Vector128<uint> topLow, Vector128<uint> topHigh) = Vector128.Widen(topSamples); |
||||
|
Vector128<uint> lowIndices = indices + Vector128.Create((uint)x); |
||||
|
Vector128<uint> highIndices = lowIndices + Vector128.Create((uint)Vector128<uint>.Count); |
||||
|
Vector128<uint> low = CalculatePlanarVector( |
||||
|
topLow, |
||||
|
lowIndices, |
||||
|
leftSample, |
||||
|
topRight, |
||||
|
bottomLeft, |
||||
|
topWeight, |
||||
|
bottomWeight, |
||||
|
(uint)size, |
||||
|
rounding, |
||||
|
shift); |
||||
|
|
||||
|
Vector128<uint> high = CalculatePlanarVector( |
||||
|
topHigh, |
||||
|
highIndices, |
||||
|
leftSample, |
||||
|
topRight, |
||||
|
bottomLeft, |
||||
|
topWeight, |
||||
|
bottomWeight, |
||||
|
(uint)size, |
||||
|
rounding, |
||||
|
shift); |
||||
|
|
||||
|
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; x < size; x++) |
||||
|
{ |
||||
|
uint horizontal = ((uint)(size - x - 1) * leftSample) + ((uint)(x + 1) * topRight); |
||||
|
uint vertical = ((uint)(size - y - 1) * top[x + 1]) + ((uint)(y + 1) * bottomLeft); |
||||
|
Unsafe.Add(ref rowBase, x) = (ushort)((horizontal + vertical + (uint)size) >> shift); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implements DC prediction and its optional luma boundary filter.
|
||||
|
/// </summary>
|
||||
|
private readonly struct DcPredictionOperator : IHevcIntraPredictionOperator<DcPredictionOperator> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static void Predict( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int mode, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges, |
||||
|
Span<ushort> scratch) |
||||
|
{ |
||||
|
uint sum = SumSamples(top.Slice(1, size)) + SumSamples(left.Slice(1, size)); |
||||
|
ushort dc = (ushort)((sum + (uint)size) >> (BitOperations.Log2((uint)size) + 1)); |
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
destination.Slice(y * destinationStride, size).Fill(dc); |
||||
|
} |
||||
|
|
||||
|
if (!filterPredictionEdges) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
destination[0] = (ushort)((top[1] + left[1] + (2 * dc) + 2) >> 2); |
||||
|
for (int x = 1; x < size; x++) |
||||
|
{ |
||||
|
destination[x] = (ushort)((top[x + 1] + (3 * dc) + 2) >> 2); |
||||
|
} |
||||
|
|
||||
|
for (int y = 1; y < size; y++) |
||||
|
{ |
||||
|
destination[y * destinationStride] = (ushort)((left[y + 1] + (3 * dc) + 2) >> 2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Implements the thirty-three directional intra-prediction modes.
|
||||
|
/// </summary>
|
||||
|
private readonly struct AngularPredictionOperator : IHevcIntraPredictionOperator<AngularPredictionOperator> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static void Predict( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int mode, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges, |
||||
|
Span<ushort> scratch) |
||||
|
{ |
||||
|
if (mode == VerticalMode) |
||||
|
{ |
||||
|
PredictVertical(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (mode == HorizontalMode) |
||||
|
{ |
||||
|
PredictHorizontal(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
bool vertical = mode >= FirstVerticalMode; |
||||
|
int angleMode = vertical ? mode - VerticalMode : HorizontalMode - mode; |
||||
|
int absoluteAngleMode = Math.Abs(angleMode); |
||||
|
int angle = PredictionAngles[absoluteAngleMode] * Math.Sign(angleMode); |
||||
|
ReadOnlySpan<ushort> main = vertical ? top : left; |
||||
|
ReadOnlySpan<ushort> side = vertical ? left : top; |
||||
|
Span<ushort> temporaryBlock = scratch[..(size * size)]; |
||||
|
Span<ushort> extendedReference = scratch.Slice(size * size, (4 * size) + 1); |
||||
|
int mainOrigin = 0; |
||||
|
|
||||
|
if (angle < 0) |
||||
|
{ |
||||
|
mainOrigin = size * 2; |
||||
|
main[..(size + 1)].CopyTo(extendedReference[mainOrigin..]); |
||||
|
int inverseAngle = InversePredictionAngles[absoluteAngleMode]; |
||||
|
int inverseAngleSum = 128; |
||||
|
int minimumIndex = (size * angle) >> 5; |
||||
|
for (int index = -1; index > minimumIndex; index--) |
||||
|
{ |
||||
|
inverseAngleSum += inverseAngle; |
||||
|
extendedReference[mainOrigin + index] = side[inverseAngleSum >> 8]; |
||||
|
} |
||||
|
|
||||
|
main = extendedReference; |
||||
|
} |
||||
|
|
||||
|
Span<ushort> prediction = vertical ? destination : temporaryBlock; |
||||
|
int predictionStride = vertical ? destinationStride : size; |
||||
|
PredictAngularRows(main, mainOrigin, prediction, predictionStride, size, angle); |
||||
|
if (!vertical) |
||||
|
{ |
||||
|
TransposeBlock(temporaryBlock, destination, destinationStride, size); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,412 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs HEVC intra-prediction blocks from prepared neighboring samples.
|
||||
|
/// </summary>
|
||||
|
internal static partial class HevcIntraPredictor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The HEVC planar prediction mode.
|
||||
|
/// </summary>
|
||||
|
private const int PlanarMode = 0; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The HEVC DC prediction mode.
|
||||
|
/// </summary>
|
||||
|
private const int DcMode = 1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The HEVC horizontal prediction mode.
|
||||
|
/// </summary>
|
||||
|
private const int HorizontalMode = 10; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The first prediction mode whose main reference is the top row.
|
||||
|
/// </summary>
|
||||
|
private const int FirstVerticalMode = 18; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The HEVC vertical prediction mode.
|
||||
|
/// </summary>
|
||||
|
private const int VerticalMode = 26; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The largest transform-block side supported by HEVC intra prediction.
|
||||
|
/// </summary>
|
||||
|
private const int MaximumBlockSize = 32; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines one closed intra-prediction operation selected by the decoded mode.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The implementing operator type.</typeparam>
|
||||
|
private interface IHevcIntraPredictionOperator<TOperator> |
||||
|
where TOperator : struct, IHevcIntraPredictionOperator<TOperator> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Reconstructs one square prediction block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top-left, top, and top-right reference samples.</param>
|
||||
|
/// <param name="left">The top-left, left, and below-left reference samples.</param>
|
||||
|
/// <param name="destination">The destination buffer beginning at the block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="size">The square block side in samples.</param>
|
||||
|
/// <param name="mode">The decoded prediction mode.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the luma edge filter applies to the selected block.</param>
|
||||
|
/// <param name="scratch">The caller-owned block and extended-reference scratch space.</param>
|
||||
|
public static abstract void Predict( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int mode, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges, |
||||
|
Span<ushort> scratch); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the angle selected by each absolute angular-mode displacement.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> PredictionAngles => [0, 2, 5, 9, 13, 17, 21, 26, 32]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the reciprocal angle used to extend the main reference for negative directions.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> InversePredictionAngles => [0, 4096, 1638, 910, 630, 482, 390, 315, 256]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the scratch length required to predict a block of the specified size.
|
||||
|
/// </summary>
|
||||
|
/// <param name="log2Size">The base-two logarithm of the square block side.</param>
|
||||
|
/// <returns>The required number of <see cref="ushort"/> elements.</returns>
|
||||
|
public static int GetScratchLength(int log2Size) |
||||
|
{ |
||||
|
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
||||
|
int size = 1 << log2Size; |
||||
|
return (size * size) + (4 * size) + 1; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs one square intra-prediction block using a closed operator selected by the decoded mode.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top-left, top, and top-right reference samples.</param>
|
||||
|
/// <param name="left">The top-left, left, and below-left reference samples.</param>
|
||||
|
/// <param name="destination">The destination buffer beginning at the block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="log2Size">The base-two logarithm of the square block side.</param>
|
||||
|
/// <param name="mode">The decoded prediction mode in the inclusive range zero through thirty-four.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the luma edge filter applies to the selected block.</param>
|
||||
|
/// <param name="scratch">The caller-owned scratch returned by <see cref="GetScratchLength(int)"/>.</param>
|
||||
|
public static void Predict( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int log2Size, |
||||
|
int mode, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges, |
||||
|
Span<ushort> scratch) |
||||
|
{ |
||||
|
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
||||
|
DebugGuard.MustBeBetweenOrEqualTo(mode, PlanarMode, 34, nameof(mode)); |
||||
|
int size = 1 << log2Size; |
||||
|
|
||||
|
switch (mode) |
||||
|
{ |
||||
|
case PlanarMode: |
||||
|
Predict<PlanarPredictionOperator>( |
||||
|
top, |
||||
|
left, |
||||
|
destination, |
||||
|
destinationStride, |
||||
|
size, |
||||
|
mode, |
||||
|
bitDepth, |
||||
|
filterPredictionEdges, |
||||
|
scratch); |
||||
|
break; |
||||
|
case DcMode: |
||||
|
Predict<DcPredictionOperator>( |
||||
|
top, |
||||
|
left, |
||||
|
destination, |
||||
|
destinationStride, |
||||
|
size, |
||||
|
mode, |
||||
|
bitDepth, |
||||
|
filterPredictionEdges, |
||||
|
scratch); |
||||
|
break; |
||||
|
default: |
||||
|
Predict<AngularPredictionOperator>( |
||||
|
top, |
||||
|
left, |
||||
|
destination, |
||||
|
destinationStride, |
||||
|
size, |
||||
|
mode, |
||||
|
bitDepth, |
||||
|
filterPredictionEdges, |
||||
|
scratch); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters prepared reference samples using the normative three-tap or strong bilinear filter.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The unfiltered top-left, top, and top-right samples.</param>
|
||||
|
/// <param name="left">The unfiltered top-left, left, and below-left samples.</param>
|
||||
|
/// <param name="filteredTop">The destination top reference.</param>
|
||||
|
/// <param name="filteredLeft">The destination left reference.</param>
|
||||
|
/// <param name="log2Size">The base-two logarithm of the square prediction-block side.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed luma precision.</param>
|
||||
|
/// <param name="strongIntraSmoothingEnabled">Whether the sequence permits strong intra smoothing.</param>
|
||||
|
public static void FilterReferenceSamples( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> filteredTop, |
||||
|
Span<ushort> filteredLeft, |
||||
|
int log2Size, |
||||
|
int bitDepth, |
||||
|
bool strongIntraSmoothingEnabled) |
||||
|
{ |
||||
|
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
||||
|
int size = 1 << log2Size; |
||||
|
int referenceLength = (size * 2) + 1; |
||||
|
bool useStrongSmoothing = strongIntraSmoothingEnabled && size == MaximumBlockSize; |
||||
|
if (useStrongSmoothing) |
||||
|
{ |
||||
|
int threshold = 1 << (bitDepth - 5); |
||||
|
int last = referenceLength - 1; |
||||
|
bool leftIsBilinear = Math.Abs((left[last] + left[0]) - (2 * left[size])) < threshold; |
||||
|
bool topIsBilinear = Math.Abs((top[0] + top[last]) - (2 * top[size])) < threshold; |
||||
|
useStrongSmoothing = leftIsBilinear && topIsBilinear; |
||||
|
} |
||||
|
|
||||
|
if (useStrongSmoothing) |
||||
|
{ |
||||
|
FilterReferenceBilinear(top[..referenceLength], filteredTop, size); |
||||
|
FilterReferenceBilinear(left[..referenceLength], filteredLeft, size); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// The corner belongs to both references. Filtering it once from the first samples on both sides keeps the
|
||||
|
// two logical arrays identical at index zero before their independent one-dimensional filters continue.
|
||||
|
ushort filteredCorner = (ushort)((left[1] + (2 * top[0]) + top[1] + 2) >> 2); |
||||
|
filteredTop[0] = filteredCorner; |
||||
|
filteredLeft[0] = filteredCorner; |
||||
|
FilterReferenceThreeTap(top[..referenceLength], filteredTop); |
||||
|
FilterReferenceThreeTap(left[..referenceLength], filteredLeft); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes one statically selected prediction operator without interface dispatch in the block loop.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The selected prediction operator.</typeparam>
|
||||
|
/// <param name="top">The prepared top reference.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="size">The square block side in samples.</param>
|
||||
|
/// <param name="mode">The decoded prediction mode.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
||||
|
/// <param name="scratch">The caller-owned prediction scratch.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void Predict<TOperator>( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int mode, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges, |
||||
|
Span<ushort> scratch) |
||||
|
where TOperator : struct, IHevcIntraPredictionOperator<TOperator> |
||||
|
=> TOperator.Predict( |
||||
|
top, |
||||
|
left, |
||||
|
destination, |
||||
|
destinationStride, |
||||
|
size, |
||||
|
mode, |
||||
|
bitDepth, |
||||
|
filterPredictionEdges, |
||||
|
scratch); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the strong bilinear filter between the reference endpoints.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The complete unfiltered reference.</param>
|
||||
|
/// <param name="destination">The complete filtered reference.</param>
|
||||
|
/// <param name="size">The prediction-block side in samples.</param>
|
||||
|
private static void FilterReferenceBilinear(ReadOnlySpan<ushort> source, Span<ushort> destination, int size) |
||||
|
{ |
||||
|
int last = source.Length - 1; |
||||
|
destination[0] = source[0]; |
||||
|
destination[last] = source[last]; |
||||
|
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
uint first = source[0]; |
||||
|
uint final = source[last]; |
||||
|
int shift = BitOperations.Log2((uint)(size * 2)); |
||||
|
uint rounding = (uint)size; |
||||
|
int i = 1; |
||||
|
|
||||
|
// Each widened lane represents one reference coordinate. The weights sum to 2N, so narrowing is exact
|
||||
|
// after the rounded shift for every supported 8, 10, and 12-bit sample.
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector512<uint> indices = CreateIndicesVector512(); |
||||
|
int oneVectorFromEnd = last - Vector512<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
||||
|
{ |
||||
|
Vector512<uint> lowerIndices = indices + Vector512.Create((uint)i); |
||||
|
Vector512<uint> upperIndices = lowerIndices + Vector512.Create((uint)Vector512<uint>.Count); |
||||
|
Vector512<uint> lower = (((Vector512.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector512.Create(rounding)) >> shift; |
||||
|
Vector512<uint> upper = (((Vector512.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector512.Create(rounding)) >> shift; |
||||
|
Vector512.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector256<uint> indices = CreateIndicesVector256(); |
||||
|
int oneVectorFromEnd = last - Vector256<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
||||
|
{ |
||||
|
Vector256<uint> lowerIndices = indices + Vector256.Create((uint)i); |
||||
|
Vector256<uint> upperIndices = lowerIndices + Vector256.Create((uint)Vector256<uint>.Count); |
||||
|
Vector256<uint> lower = (((Vector256.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector256.Create(rounding)) >> shift; |
||||
|
Vector256<uint> upper = (((Vector256.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector256.Create(rounding)) >> shift; |
||||
|
Vector256.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<uint> indices = CreateIndicesVector128(); |
||||
|
int oneVectorFromEnd = last - Vector128<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
||||
|
{ |
||||
|
Vector128<uint> lowerIndices = indices + Vector128.Create((uint)i); |
||||
|
Vector128<uint> upperIndices = lowerIndices + Vector128.Create((uint)Vector128<uint>.Count); |
||||
|
Vector128<uint> lower = (((Vector128.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector128.Create(rounding)) >> shift; |
||||
|
Vector128<uint> upper = (((Vector128.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector128.Create(rounding)) >> shift; |
||||
|
Vector128.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; i < last; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = (ushort)((((last - i) * first) + (i * final) + rounding) >> shift); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the normal three-tap reference filter to every non-endpoint sample.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The complete unfiltered reference.</param>
|
||||
|
/// <param name="destination">The complete filtered reference with its corner already initialized.</param>
|
||||
|
private static void FilterReferenceThreeTap(ReadOnlySpan<ushort> source, Span<ushort> destination) |
||||
|
{ |
||||
|
int last = source.Length - 1; |
||||
|
destination[last] = source[last]; |
||||
|
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
int i = 1; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = last - Vector512<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
||||
|
{ |
||||
|
Vector512<ushort> previous = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
||||
|
Vector512<ushort> current = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); |
||||
|
Vector512<ushort> next = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
||||
|
(Vector512<uint> previousLow, Vector512<uint> previousHigh) = Vector512.Widen(previous); |
||||
|
(Vector512<uint> currentLow, Vector512<uint> currentHigh) = Vector512.Widen(current); |
||||
|
(Vector512<uint> nextLow, Vector512<uint> nextHigh) = Vector512.Widen(next); |
||||
|
Vector512<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector512.Create(2U)) >> 2; |
||||
|
Vector512<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector512.Create(2U)) >> 2; |
||||
|
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = last - Vector256<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
||||
|
{ |
||||
|
Vector256<ushort> previous = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
||||
|
Vector256<ushort> current = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); |
||||
|
Vector256<ushort> next = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
||||
|
(Vector256<uint> previousLow, Vector256<uint> previousHigh) = Vector256.Widen(previous); |
||||
|
(Vector256<uint> currentLow, Vector256<uint> currentHigh) = Vector256.Widen(current); |
||||
|
(Vector256<uint> nextLow, Vector256<uint> nextHigh) = Vector256.Widen(next); |
||||
|
Vector256<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector256.Create(2U)) >> 2; |
||||
|
Vector256<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector256.Create(2U)) >> 2; |
||||
|
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = last - Vector128<ushort>.Count; |
||||
|
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
||||
|
{ |
||||
|
Vector128<ushort> previous = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
||||
|
Vector128<ushort> current = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); |
||||
|
Vector128<ushort> next = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
||||
|
(Vector128<uint> previousLow, Vector128<uint> previousHigh) = Vector128.Widen(previous); |
||||
|
(Vector128<uint> currentLow, Vector128<uint> currentHigh) = Vector128.Widen(current); |
||||
|
(Vector128<uint> nextLow, Vector128<uint> nextHigh) = Vector128.Widen(next); |
||||
|
Vector128<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector128.Create(2U)) >> 2; |
||||
|
Vector128<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector128.Create(2U)) >> 2; |
||||
|
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; i < last; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = (ushort)((source[i - 1] + (2 * source[i]) + source[i + 1] + 2) >> 2); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates the zero-through-fifteen lane indices used by 512-bit weighted interpolation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The ordered lane indices.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<uint> CreateIndicesVector512() |
||||
|
=> Vector512.Create(0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 11U, 12U, 13U, 14U, 15U); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates the zero-through-seven lane indices used by 256-bit weighted interpolation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The ordered lane indices.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<uint> CreateIndicesVector256() => Vector256.Create(0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates the zero-through-three lane indices used by 128-bit weighted interpolation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The ordered lane indices.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<uint> CreateIndicesVector128() => Vector128.Create(0U, 1U, 2U, 3U); |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
// 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 representative HEVC intra-prediction modes.
|
||||
|
/// </summary>
|
||||
|
[MemoryDiagnoser(displayGenColumns: false)] |
||||
|
public class HevcIntraPredictionBenchmarks |
||||
|
{ |
||||
|
/// <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 base-two logarithm of the benchmark prediction-block side.
|
||||
|
/// </summary>
|
||||
|
private const int BlockLog2 = 5; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The prediction-block side in samples.
|
||||
|
/// </summary>
|
||||
|
private const int BlockSize = 1 << BlockLog2; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The prepared top reference shared by deterministic benchmark blocks.
|
||||
|
/// </summary>
|
||||
|
private readonly ushort[] top = new ushort[(BlockSize * 2) + 1]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The prepared left reference shared by deterministic benchmark blocks.
|
||||
|
/// </summary>
|
||||
|
private readonly ushort[] left = new ushort[(BlockSize * 2) + 1]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The frame-wide reconstructed prediction samples.
|
||||
|
/// </summary>
|
||||
|
private readonly ushort[] destination = new ushort[Width * Height]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The maximum-block scratch reused throughout each coded frame.
|
||||
|
/// </summary>
|
||||
|
private readonly ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(BlockLog2)]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Populates deterministic twelve-bit reference samples outside the measured frame traversal.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
this.top[0] = this.left[0] = 1365; |
||||
|
for (int i = 1; i < this.top.Length; i++) |
||||
|
{ |
||||
|
this.top[i] = (ushort)((1365 + (37 * i)) & 4095); |
||||
|
this.left[i] = (ushort)((1365 + (53 * i)) & 4095); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures frame-wide planar prediction with 512-, 256-, and 128-bit row dispatch where available.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
||||
|
[Benchmark(Baseline = true)] |
||||
|
public ushort PredictPlanarFrame() => this.PredictFrame(0); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures frame-wide fractional vertical prediction using contiguous SIMD interpolation.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
||||
|
[Benchmark] |
||||
|
public ushort PredictVerticalAngularFrame() => this.PredictFrame(30); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures frame-wide horizontal prediction including the SIMD block transposition stage.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
||||
|
[Benchmark] |
||||
|
public ushort PredictHorizontalAngularFrame() => this.PredictFrame(2); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs every maximum-size prediction block in the coded benchmark frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="mode">The HEVC intra-prediction mode.</param>
|
||||
|
/// <returns>The final reconstructed sample.</returns>
|
||||
|
private ushort PredictFrame(int mode) |
||||
|
{ |
||||
|
for (int y = 0; y < Height; y += BlockSize) |
||||
|
{ |
||||
|
for (int x = 0; x < Width; x += BlockSize) |
||||
|
{ |
||||
|
HevcIntraPredictor.Predict( |
||||
|
this.top, |
||||
|
this.left, |
||||
|
this.destination.AsSpan((y * Width) + x), |
||||
|
Width, |
||||
|
BlockLog2, |
||||
|
mode, |
||||
|
12, |
||||
|
true, |
||||
|
this.scratch); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,396 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Hevc; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies HEVC planar, DC, angular, reference-filter, and SIMD prediction behavior.
|
||||
|
/// </summary>
|
||||
|
[Trait("Format", "Heic")] |
||||
|
public class HevcIntraPredictorTests |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Verifies fixed four-by-four prediction results derived from the HEVC intra-prediction equations.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void PredictsKnownFourByFourBlocks() |
||||
|
{ |
||||
|
ushort[] top = [64, 80, 96, 112, 128, 144, 160, 176, 192]; |
||||
|
ushort[] left = [64, 70, 76, 82, 88, 94, 100, 106, 112]; |
||||
|
int[] modes = [0, 1, 2, 9, 18, 30, 34]; |
||||
|
ushort[][] expected = |
||||
|
[ |
||||
|
[83, 97, 110, 123, 87, 97, 108, 118, 90, 98, 105, 113, 93, 98, 103, 108], |
||||
|
[84, 93, 97, 101, 88, 92, 92, 92, 90, 92, 92, 92, 91, 92, 92, 92], |
||||
|
[76, 82, 88, 94, 82, 88, 94, 100, 88, 94, 100, 106, 94, 100, 106, 112], |
||||
|
[70, 71, 71, 72, 76, 77, 77, 78, 82, 83, 83, 84, 88, 89, 89, 90], |
||||
|
[64, 80, 96, 112, 70, 64, 80, 96, 76, 70, 64, 80, 82, 76, 70, 64], |
||||
|
[87, 103, 119, 135, 93, 109, 125, 141, 100, 116, 132, 148, 106, 122, 138, 154], |
||||
|
[96, 112, 128, 144, 112, 128, 144, 160, 128, 144, 160, 176, 144, 160, 176, 192] |
||||
|
]; |
||||
|
|
||||
|
const int size = 4; |
||||
|
const int stride = 7; |
||||
|
ushort[] destination = new ushort[stride * size]; |
||||
|
ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(2)]; |
||||
|
for (int caseIndex = 0; caseIndex < modes.Length; caseIndex++) |
||||
|
{ |
||||
|
destination.AsSpan().Fill(ushort.MaxValue); |
||||
|
int mode = modes[caseIndex]; |
||||
|
HevcIntraPredictor.Predict(top, left, destination, stride, 2, mode, 8, mode == 1, scratch); |
||||
|
|
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
ReadOnlySpan<ushort> expectedRow = expected[caseIndex].AsSpan(y * size, size); |
||||
|
ReadOnlySpan<ushort> actualRow = destination.AsSpan(y * stride, size); |
||||
|
Assert.True(expectedRow.SequenceEqual(actualRow), $"Mode {mode}, row {y} did not match the fixed HEVC result."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies the optional luma boundary filter for the pure horizontal and vertical modes.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void FiltersPureDirectionPredictionEdges() |
||||
|
{ |
||||
|
ushort[] top = [64, 80, 96, 112, 128, 144, 160, 176, 192]; |
||||
|
ushort[] left = [64, 70, 76, 82, 88, 94, 100, 106, 112]; |
||||
|
ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(2)]; |
||||
|
ushort[] horizontal = new ushort[16]; |
||||
|
ushort[] vertical = new ushort[16]; |
||||
|
|
||||
|
HevcIntraPredictor.Predict(top, left, horizontal, 4, 2, 10, 8, true, scratch); |
||||
|
HevcIntraPredictor.Predict(top, left, vertical, 4, 2, 26, 8, true, scratch); |
||||
|
|
||||
|
ushort[] expectedHorizontal = [78, 86, 94, 102, 76, 76, 76, 76, 82, 82, 82, 82, 88, 88, 88, 88]; |
||||
|
ushort[] expectedVertical = [83, 96, 112, 128, 86, 96, 112, 128, 89, 96, 112, 128, 92, 96, 112, 128]; |
||||
|
Assert.True(expectedHorizontal.AsSpan().SequenceEqual(horizontal)); |
||||
|
Assert.True(expectedVertical.AsSpan().SequenceEqual(vertical)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies exact three-tap filtering, including the shared top-left sample.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void FiltersReferenceSamplesWithThreeTapKernel() |
||||
|
{ |
||||
|
ushort[] top = [64, 80, 96, 112, 128, 144, 160, 176, 192]; |
||||
|
ushort[] left = [64, 70, 76, 82, 88, 94, 100, 106, 112]; |
||||
|
ushort[] filteredTop = new ushort[top.Length]; |
||||
|
ushort[] filteredLeft = new ushort[left.Length]; |
||||
|
|
||||
|
HevcIntraPredictor.FilterReferenceSamples(top, left, filteredTop, filteredLeft, 2, 8, true); |
||||
|
|
||||
|
ushort[] expectedTop = [70, 80, 96, 112, 128, 144, 160, 176, 192]; |
||||
|
ushort[] expectedLeft = [70, 70, 76, 82, 88, 94, 100, 106, 112]; |
||||
|
Assert.True(expectedTop.AsSpan().SequenceEqual(filteredTop)); |
||||
|
Assert.True(expectedLeft.AsSpan().SequenceEqual(filteredLeft)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies that eligible thirty-two-sample references use strong bilinear smoothing rather than local three-tap filtering.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void StrongSmoothingReplacesEligibleNonlinearReferences() |
||||
|
{ |
||||
|
const int size = 32; |
||||
|
ushort[] top = new ushort[(size * 2) + 1]; |
||||
|
ushort[] left = new ushort[top.Length]; |
||||
|
ushort[] filteredTop = new ushort[top.Length]; |
||||
|
ushort[] filteredLeft = new ushort[left.Length]; |
||||
|
for (int i = 0; i < top.Length; i++) |
||||
|
{ |
||||
|
top[i] = (ushort)(100 + i + (i % 3)); |
||||
|
left[i] = (ushort)(100 + (2 * i) + (i % 5)); |
||||
|
} |
||||
|
|
||||
|
// Strong smoothing is selected from the endpoint/midpoint test, so keep those six values exactly bilinear
|
||||
|
// while the remaining samples deliberately differ from the expected straight lines.
|
||||
|
top[0] = left[0] = 100; |
||||
|
top[size] = 132; |
||||
|
top[size * 2] = 164; |
||||
|
left[size] = 164; |
||||
|
left[size * 2] = 228; |
||||
|
|
||||
|
HevcIntraPredictor.FilterReferenceSamples(top, left, filteredTop, filteredLeft, 5, 10, true); |
||||
|
|
||||
|
for (int i = 0; i < top.Length; i++) |
||||
|
{ |
||||
|
Assert.Equal((ushort)(100 + i), filteredTop[i]); |
||||
|
Assert.Equal((ushort)(100 + (2 * i)), filteredLeft[i]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Compares every prediction mode and block width with a specification-shaped scalar oracle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="log2Size">The base-two logarithm of the tested block side.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
[Theory] |
||||
|
[InlineData(2, 8)] |
||||
|
[InlineData(3, 10)] |
||||
|
[InlineData(4, 12)] |
||||
|
[InlineData(5, 12)] |
||||
|
public void EveryModeMatchesScalarOracle(int log2Size, int bitDepth) |
||||
|
{ |
||||
|
int size = 1 << log2Size; |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
int referenceLength = (size * 2) + 1; |
||||
|
ushort[] top = new ushort[referenceLength]; |
||||
|
ushort[] left = new ushort[referenceLength]; |
||||
|
top[0] = left[0] = (ushort)(maximum / 3); |
||||
|
for (int i = 1; i < referenceLength; i++) |
||||
|
{ |
||||
|
top[i] = (ushort)((top[0] + (37 * i) + (3 * size)) & maximum); |
||||
|
left[i] = (ushort)((left[0] + (53 * i) + (5 * size)) & maximum); |
||||
|
} |
||||
|
|
||||
|
int stride = size + 3; |
||||
|
ushort[] expected = new ushort[stride * size]; |
||||
|
ushort[] actual = new ushort[stride * size]; |
||||
|
ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(log2Size)]; |
||||
|
for (int mode = 0; mode <= 34; mode++) |
||||
|
{ |
||||
|
expected.AsSpan().Clear(); |
||||
|
actual.AsSpan().Clear(); |
||||
|
PredictScalar(top, left, expected, stride, size, mode, bitDepth, true); |
||||
|
HevcIntraPredictor.Predict(top, left, actual, stride, log2Size, mode, bitDepth, true, scratch); |
||||
|
Assert.True(expected.AsSpan().SequenceEqual(actual), $"Mode {mode}, size {size}, and bit depth {bitDepth} did not match the scalar oracle."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs one block directly from the HEVC planar, DC, and angular prediction equations.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference samples.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="mode">The prediction mode.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
||||
|
private static void PredictScalar( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int mode, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges) |
||||
|
{ |
||||
|
if (mode == 0) |
||||
|
{ |
||||
|
int shift = BitOperations.Log2((uint)size) + 1; |
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < size; x++) |
||||
|
{ |
||||
|
int horizontal = ((size - x - 1) * left[y + 1]) + ((x + 1) * top[size]); |
||||
|
int vertical = ((size - y - 1) * top[x + 1]) + ((y + 1) * left[size]); |
||||
|
destination[(y * destinationStride) + x] = (ushort)((horizontal + vertical + size) >> shift); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (mode == 1) |
||||
|
{ |
||||
|
PredictDcScalar(top, left, destination, destinationStride, size, filterPredictionEdges); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (mode == 10) |
||||
|
{ |
||||
|
PredictHorizontalScalar(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (mode == 26) |
||||
|
{ |
||||
|
PredictVerticalScalar(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
PredictAngularScalar(top, left, destination, destinationStride, size, mode); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs a scalar DC block and its optional boundary filter.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference samples.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
||||
|
private static void PredictDcScalar( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
bool filterPredictionEdges) |
||||
|
{ |
||||
|
int sum = 0; |
||||
|
for (int i = 1; i <= size; i++) |
||||
|
{ |
||||
|
sum += top[i] + left[i]; |
||||
|
} |
||||
|
|
||||
|
ushort dc = (ushort)((sum + size) >> (BitOperations.Log2((uint)size) + 1)); |
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
destination.Slice(y * destinationStride, size).Fill(dc); |
||||
|
} |
||||
|
|
||||
|
if (!filterPredictionEdges) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
destination[0] = (ushort)((top[1] + left[1] + (2 * dc) + 2) >> 2); |
||||
|
for (int i = 1; i < size; i++) |
||||
|
{ |
||||
|
destination[i] = (ushort)((top[i + 1] + (3 * dc) + 2) >> 2); |
||||
|
destination[i * destinationStride] = (ushort)((left[i + 1] + (3 * dc) + 2) >> 2); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs scalar horizontal prediction and its optional boundary filter.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference samples.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
||||
|
private static void PredictHorizontalScalar( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges) |
||||
|
{ |
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
destination.Slice(y * destinationStride, size).Fill(left[y + 1]); |
||||
|
} |
||||
|
|
||||
|
if (filterPredictionEdges) |
||||
|
{ |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
for (int x = 0; x < size; x++) |
||||
|
{ |
||||
|
destination[x] = (ushort)Math.Clamp(destination[x] + ((top[x + 1] - top[0]) >> 1), 0, maximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs scalar vertical prediction and its optional boundary filter.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference samples.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed component precision.</param>
|
||||
|
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
||||
|
private static void PredictVerticalScalar( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int bitDepth, |
||||
|
bool filterPredictionEdges) |
||||
|
{ |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
top.Slice(1, size).CopyTo(destination[(y * destinationStride)..]); |
||||
|
if (filterPredictionEdges) |
||||
|
{ |
||||
|
int offset = y * destinationStride; |
||||
|
destination[offset] = (ushort)Math.Clamp(destination[offset] + ((left[y + 1] - left[0]) >> 1), 0, maximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs a scalar angular block, including negative-reference extension and horizontal transposition.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference samples.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="size">The square block side.</param>
|
||||
|
/// <param name="mode">The angular prediction mode.</param>
|
||||
|
private static void PredictAngularScalar( |
||||
|
ReadOnlySpan<ushort> top, |
||||
|
ReadOnlySpan<ushort> left, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int size, |
||||
|
int mode) |
||||
|
{ |
||||
|
ReadOnlySpan<int> angles = [0, 2, 5, 9, 13, 17, 21, 26, 32]; |
||||
|
ReadOnlySpan<int> inverseAngles = [0, 4096, 1638, 910, 630, 482, 390, 315, 256]; |
||||
|
bool vertical = mode >= 18; |
||||
|
int angleMode = vertical ? mode - 26 : 10 - mode; |
||||
|
int absoluteAngleMode = Math.Abs(angleMode); |
||||
|
int angle = angles[absoluteAngleMode] * Math.Sign(angleMode); |
||||
|
ReadOnlySpan<ushort> main = vertical ? top : left; |
||||
|
ReadOnlySpan<ushort> side = vertical ? left : top; |
||||
|
int mainOrigin = size * 2; |
||||
|
int[] extendedMain = new int[(4 * size) + 1]; |
||||
|
for (int i = 0; i < main.Length; i++) |
||||
|
{ |
||||
|
extendedMain[mainOrigin + i] = main[i]; |
||||
|
} |
||||
|
|
||||
|
if (angle < 0) |
||||
|
{ |
||||
|
int inverseAngleSum = 128; |
||||
|
for (int index = -1; index > ((size * angle) >> 5); index--) |
||||
|
{ |
||||
|
inverseAngleSum += inverseAngles[absoluteAngleMode]; |
||||
|
extendedMain[mainOrigin + index] = side[inverseAngleSum >> 8]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ushort[] temporary = new ushort[size * size]; |
||||
|
for (int y = 0, deltaPosition = angle; y < size; y++, deltaPosition += angle) |
||||
|
{ |
||||
|
int deltaInteger = deltaPosition >> 5; |
||||
|
int deltaFraction = deltaPosition & 31; |
||||
|
for (int x = 0; x < size; x++) |
||||
|
{ |
||||
|
int index = mainOrigin + x + deltaInteger + 1; |
||||
|
temporary[(y * size) + x] = deltaFraction == 0 |
||||
|
? (ushort)extendedMain[index] |
||||
|
: (ushort)(((extendedMain[index] * (32 - deltaFraction)) + (extendedMain[index + 1] * deltaFraction) + 16) >> 5); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int y = 0; y < size; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < size; x++) |
||||
|
{ |
||||
|
int sourceIndex = vertical ? (y * size) + x : (x * size) + y; |
||||
|
destination[(y * destinationStride) + x] = temporary[sourceIndex]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue