mirror of https://github.com/SixLabors/ImageSharp
79 changed files with 10285 additions and 5611 deletions
@ -1 +1 @@ |
|||
Subproject commit 03471c6b458a2c11a0b1df1f5bf2777c8c773b99 |
|||
Subproject commit a835a9d74e82b2d32b580a7902eb2699ebc47098 |
|||
@ -0,0 +1,434 @@ |
|||
// 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.Av1.Prediction; |
|||
|
|||
/// <content>
|
|||
/// Defines reference reduction and rounded mean arithmetic for AV1 DC intra prediction.
|
|||
/// </content>
|
|||
internal static class Av1DcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines scalar and SIMD reference reduction for AV1 DC intra prediction.
|
|||
/// </summary>
|
|||
internal interface IDcPredictionOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Sums one 8-bit reference sample.
|
|||
/// </summary>
|
|||
/// <param name="sample">The reference sample.</param>
|
|||
/// <returns>The sample value.</returns>
|
|||
public static abstract int Sum(byte sample); |
|||
|
|||
/// <summary>
|
|||
/// Sums sixteen 8-bit reference samples.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
public static abstract int Sum(Vector128<byte> samples); |
|||
|
|||
/// <summary>
|
|||
/// Sums thirty-two 8-bit reference samples.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
public static abstract int Sum(Vector256<byte> samples); |
|||
|
|||
/// <summary>
|
|||
/// Sums sixty-four 8-bit reference samples.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
public static abstract int Sum(Vector512<byte> samples); |
|||
|
|||
/// <summary>
|
|||
/// Sums one high-bit-depth reference sample.
|
|||
/// </summary>
|
|||
/// <param name="sample">The reference sample.</param>
|
|||
/// <returns>The sample value.</returns>
|
|||
public static abstract int Sum(short sample); |
|||
|
|||
/// <summary>
|
|||
/// Sums eight high-bit-depth reference samples.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
public static abstract int Sum(Vector128<short> samples); |
|||
|
|||
/// <summary>
|
|||
/// Sums sixteen high-bit-depth reference samples.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
public static abstract int Sum(Vector256<short> samples); |
|||
|
|||
/// <summary>
|
|||
/// Sums thirty-two high-bit-depth reference samples.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
public static abstract int Sum(Vector512<short> samples); |
|||
|
|||
/// <summary>
|
|||
/// Calculates the 8-bit DC prediction.
|
|||
/// </summary>
|
|||
/// <param name="sum">The sum of available reference samples.</param>
|
|||
/// <param name="count">The number of available reference samples.</param>
|
|||
/// <returns>The rounded DC prediction.</returns>
|
|||
public static abstract byte Predict(int sum, int count); |
|||
|
|||
/// <summary>
|
|||
/// Calculates the high-bit-depth DC prediction.
|
|||
/// </summary>
|
|||
/// <param name="sum">The sum of available reference samples.</param>
|
|||
/// <param name="count">The number of available reference samples.</param>
|
|||
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
|||
/// <returns>The rounded DC prediction.</returns>
|
|||
public static abstract short Predict(int sum, int count, int bitDepth); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts an 8-bit DC block.
|
|||
/// </summary>
|
|||
public static void Predict(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
|||
=> Predictor<DcOperator>.Predict(hasLeft, hasAbove, destination, destinationStride, above, left, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth DC block.
|
|||
/// </summary>
|
|||
public static void Predict(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth) |
|||
=> Predictor<DcOperator>.Predict(hasLeft, hasAbove, destination, destinationStride, above, left, width, height, bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Predicts an 8-bit DC block without hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
|||
=> Predictor<DcOperator>.PredictScalar(hasLeft, hasAbove, destination, destinationStride, above, left, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth DC block without hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth) |
|||
=> Predictor<DcOperator>.PredictScalar(hasLeft, hasAbove, destination, destinationStride, above, left, width, height, bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Calculates the DC value from the available neighboring samples.
|
|||
/// </summary>
|
|||
internal readonly struct DcOperator : IDcPredictionOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Sum(byte sample) => sample; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Sum(Vector128<byte> samples) |
|||
{ |
|||
(Vector128<ushort> lower, Vector128<ushort> upper) = Vector128.Widen(samples); |
|||
|
|||
return Vector128.Sum(lower) + Vector128.Sum(upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Sum(Vector256<byte> samples) |
|||
{ |
|||
(Vector256<ushort> lower, Vector256<ushort> upper) = Vector256.Widen(samples); |
|||
|
|||
return Vector256.Sum(lower) + Vector256.Sum(upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Sum(Vector512<byte> samples) |
|||
{ |
|||
(Vector512<ushort> lower, Vector512<ushort> upper) = Vector512.Widen(samples); |
|||
|
|||
return Vector512.Sum(lower) + Vector512.Sum(upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Sum(short sample) => sample; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Sum(Vector128<short> samples) |
|||
{ |
|||
(Vector128<int> lower, Vector128<int> upper) = Vector128.Widen(samples); |
|||
|
|||
return Vector128.Sum(lower) + Vector128.Sum(upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Sum(Vector256<short> samples) |
|||
{ |
|||
(Vector256<int> lower, Vector256<int> upper) = Vector256.Widen(samples); |
|||
|
|||
return Vector256.Sum(lower) + Vector256.Sum(upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Sum(Vector512<short> samples) |
|||
{ |
|||
(Vector512<int> lower, Vector512<int> upper) = Vector512.Widen(samples); |
|||
|
|||
return Vector512.Sum(lower) + Vector512.Sum(upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Predict(int sum, int count) => count == 0 ? (byte)128 : (byte)((sum + (count >> 1)) / count); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static short Predict(int sum, int count, int bitDepth) |
|||
=> count == 0 ? (short)(1 << (bitDepth - 1)) : (short)((sum + (count >> 1)) / count); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs DC blocks through one closed reduction operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The reference reduction and rounded mean arithmetic.</typeparam>
|
|||
private static class Predictor<TOperator> |
|||
where TOperator : struct, IDcPredictionOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Predicts an 8-bit DC block.
|
|||
/// </summary>
|
|||
/// <param name="hasLeft">Whether the left reference is available.</param>
|
|||
/// <param name="hasAbove">Whether the top reference is available.</param>
|
|||
/// <param name="destination">The destination block.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="above">The top reference.</param>
|
|||
/// <param name="left">The left reference.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
public static void Predict(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
|||
{ |
|||
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
|||
int sum = (hasAbove ? Sum(above[..width]) : 0) + (hasLeft ? Sum(left[..height]) : 0); |
|||
byte prediction = TOperator.Predict(sum, count); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
destination.Slice(row * destinationStride, width).Fill(prediction); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth DC block.
|
|||
/// </summary>
|
|||
/// <param name="hasLeft">Whether the left reference is available.</param>
|
|||
/// <param name="hasAbove">Whether the top reference is available.</param>
|
|||
/// <param name="destination">The destination block.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="above">The top reference.</param>
|
|||
/// <param name="left">The left reference.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
|||
public static void Predict(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth) |
|||
{ |
|||
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
|||
int sum = (hasAbove ? Sum(above[..width]) : 0) + (hasLeft ? Sum(left[..height]) : 0); |
|||
short prediction = TOperator.Predict(sum, count, bitDepth); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
destination.Slice(row * destinationStride, width).Fill(prediction); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts an 8-bit DC block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="hasLeft">Whether the left reference is available.</param>
|
|||
/// <param name="hasAbove">Whether the top reference is available.</param>
|
|||
/// <param name="destination">The destination block.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="above">The top reference.</param>
|
|||
/// <param name="left">The left reference.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
|||
{ |
|||
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
|||
int sum = (hasAbove ? SumScalar(above[..width]) : 0) + (hasLeft ? SumScalar(left[..height]) : 0); |
|||
byte prediction = TOperator.Predict(sum, count); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte destinationRow = ref destination[row * destinationStride]; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = prediction; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth DC block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="hasLeft">Whether the left reference is available.</param>
|
|||
/// <param name="hasAbove">Whether the top reference is available.</param>
|
|||
/// <param name="destination">The destination block.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="above">The top reference.</param>
|
|||
/// <param name="left">The left reference.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
|||
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth) |
|||
{ |
|||
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
|||
int sum = (hasAbove ? SumScalar(above[..width]) : 0) + (hasLeft ? SumScalar(left[..height]) : 0); |
|||
short prediction = TOperator.Predict(sum, count, bitDepth); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short destinationRow = ref destination[row * destinationStride]; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = prediction; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums 8-bit references through the widest available SIMD widths.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
private static int Sum(ReadOnlySpan<byte> samples) |
|||
{ |
|||
ref byte samplesBase = ref MemoryMarshal.GetReference(samples); |
|||
int sum = 0; |
|||
int index = 0; |
|||
|
|||
// The shared index deliberately continues through narrower widths. This handles every legal AV1 edge
|
|||
// length without a separate dispatch tree and leaves only an incomplete final vector to scalar code.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector512<byte>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector512<byte>.Count) |
|||
{ |
|||
sum += TOperator.Sum(Vector512.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector256<byte>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector256<byte>.Count) |
|||
{ |
|||
sum += TOperator.Sum(Vector256.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector128<byte>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector128<byte>.Count) |
|||
{ |
|||
sum += TOperator.Sum(Vector128.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
} |
|||
} |
|||
|
|||
for (; index < samples.Length; index++) |
|||
{ |
|||
sum += TOperator.Sum(Unsafe.Add(ref samplesBase, index)); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums high-bit-depth references through the widest available SIMD widths.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
private static int Sum(ReadOnlySpan<short> samples) |
|||
{ |
|||
ref short samplesBase = ref MemoryMarshal.GetReference(samples); |
|||
int sum = 0; |
|||
int index = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector512<short>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector512<short>.Count) |
|||
{ |
|||
sum += TOperator.Sum(Vector512.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector256<short>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector256<short>.Count) |
|||
{ |
|||
sum += TOperator.Sum(Vector256.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector128<short>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector128<short>.Count) |
|||
{ |
|||
sum += TOperator.Sum(Vector128.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
} |
|||
} |
|||
|
|||
for (; index < samples.Length; index++) |
|||
{ |
|||
sum += TOperator.Sum(Unsafe.Add(ref samplesBase, index)); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums 8-bit references without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
private static int SumScalar(ReadOnlySpan<byte> samples) |
|||
{ |
|||
ref byte samplesBase = ref MemoryMarshal.GetReference(samples); |
|||
int sum = 0; |
|||
|
|||
for (int index = 0; index < samples.Length; index++) |
|||
{ |
|||
sum += TOperator.Sum(Unsafe.Add(ref samplesBase, index)); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums high-bit-depth references without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="samples">The reference samples.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
private static int SumScalar(ReadOnlySpan<short> samples) |
|||
{ |
|||
ref short samplesBase = ref MemoryMarshal.GetReference(samples); |
|||
int sum = 0; |
|||
|
|||
for (int index = 0; index < samples.Length; index++) |
|||
{ |
|||
sum += TOperator.Sum(Unsafe.Add(ref samplesBase, index)); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
} |
|||
} |
|||
@ -1,254 +0,0 @@ |
|||
// 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.Av1.Prediction; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs AV1 DC intra-prediction blocks from the available neighboring samples.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Reference reduction uses the widest available integer lanes, then one rounded scalar mean is broadcast across each
|
|||
/// destination row. Row filling is delegated to span operations so the runtime supplies its native vectorized store;
|
|||
/// explicit scalar entry points remain available for feature-disabled conformance tests.
|
|||
/// </remarks>
|
|||
internal static class Av1DcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Predicts an 8-bit DC block.
|
|||
/// </summary>
|
|||
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
|
|||
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="above">The prepared top reference.</param>
|
|||
/// <param name="left">The prepared left reference.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
public static void Predict(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
|||
{ |
|||
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
|||
int sum = (hasAbove ? Sum(above[..width]) : 0) + (hasLeft ? Sum(left[..height]) : 0); |
|||
|
|||
// Section 7.11.2.2 defines the unsigned midpoint when no reference is available. Otherwise adding half
|
|||
// the reference count implements the specified rounded mean before extending it across the whole block.
|
|||
byte prediction = count == 0 ? (byte)128 : (byte)((sum + (count >> 1)) / count); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
destination.Slice(row * destinationStride, width).Fill(prediction); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth DC block.
|
|||
/// </summary>
|
|||
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
|
|||
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="above">The prepared top reference.</param>
|
|||
/// <param name="left">The prepared left reference.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
|||
public static void Predict(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth) |
|||
{ |
|||
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
|||
int sum = (hasAbove ? Sum(above[..width]) : 0) + (hasLeft ? Sum(left[..height]) : 0); |
|||
short prediction = count == 0 ? (short)(1 << (bitDepth - 1)) : (short)((sum + (count >> 1)) / count); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
destination.Slice(row * destinationStride, width).Fill(prediction); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts an 8-bit DC block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
|
|||
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="above">The prepared top reference.</param>
|
|||
/// <param name="left">The prepared left reference.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
|||
{ |
|||
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
|||
int sum = (hasAbove ? SumScalar(above[..width]) : 0) + (hasLeft ? SumScalar(left[..height]) : 0); |
|||
byte prediction = count == 0 ? (byte)128 : (byte)((sum + (count >> 1)) / count); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte destinationRow = ref destination[row * destinationStride]; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = prediction; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth DC block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
|
|||
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="above">The prepared top reference.</param>
|
|||
/// <param name="left">The prepared left reference.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
|||
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth) |
|||
{ |
|||
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
|||
int sum = (hasAbove ? SumScalar(above[..width]) : 0) + (hasLeft ? SumScalar(left[..height]) : 0); |
|||
short prediction = count == 0 ? (short)(1 << (bitDepth - 1)) : (short)((sum + (count >> 1)) / count); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short destinationRow = ref destination[row * destinationStride]; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = prediction; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums 8-bit neighboring samples using the widest available SIMD width.
|
|||
/// </summary>
|
|||
/// <param name="samples">The samples to sum.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
private static int Sum(ReadOnlySpan<byte> samples) |
|||
{ |
|||
ref byte samplesBase = ref MemoryMarshal.GetReference(samples); |
|||
int sum = 0; |
|||
int index = 0; |
|||
|
|||
// Widening prevents the packed-byte reduction from overflowing before the horizontal sum. The shared index
|
|||
// advances through every available vector width and leaves only the incomplete final group to the scalar loop.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector512<byte>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector512<byte>.Count) |
|||
{ |
|||
(Vector512<ushort> low, Vector512<ushort> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
sum += Vector512.Sum(low) + Vector512.Sum(high); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector256<byte>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector256<byte>.Count) |
|||
{ |
|||
(Vector256<ushort> low, Vector256<ushort> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
sum += Vector256.Sum(low) + Vector256.Sum(high); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector128<byte>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector128<byte>.Count) |
|||
{ |
|||
(Vector128<ushort> low, Vector128<ushort> high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
sum += Vector128.Sum(low) + Vector128.Sum(high); |
|||
} |
|||
} |
|||
|
|||
for (; index < samples.Length; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref samplesBase, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums high-bit-depth neighboring samples using the widest available SIMD width.
|
|||
/// </summary>
|
|||
/// <param name="samples">The samples to sum.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
private static int Sum(ReadOnlySpan<short> samples) |
|||
{ |
|||
ref short samplesBase = ref MemoryMarshal.GetReference(samples); |
|||
int sum = 0; |
|||
int index = 0; |
|||
|
|||
// Signed 16-bit storage is nonnegative for supported bit depths. Widening to Int32 preserves the exact sum of
|
|||
// the largest permitted reference edge before the rounded mean is calculated once outside this loop.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector512<short>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector512<short>.Count) |
|||
{ |
|||
(Vector512<int> low, Vector512<int> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
sum += Vector512.Sum(low) + Vector512.Sum(high); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector256<short>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector256<short>.Count) |
|||
{ |
|||
(Vector256<int> low, Vector256<int> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
sum += Vector256.Sum(low) + Vector256.Sum(high); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector128<short>.Count; |
|||
for (; index <= oneVectorFromEnd; index += Vector128<short>.Count) |
|||
{ |
|||
(Vector128<int> low, Vector128<int> high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)index)); |
|||
sum += Vector128.Sum(low) + Vector128.Sum(high); |
|||
} |
|||
} |
|||
|
|||
for (; index < samples.Length; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref samplesBase, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums 8-bit neighboring samples without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="samples">The samples to sum.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
private static int SumScalar(ReadOnlySpan<byte> samples) |
|||
{ |
|||
int sum = 0; |
|||
foreach (byte sample in samples) |
|||
{ |
|||
sum += sample; |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums high-bit-depth neighboring samples without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="samples">The samples to sum.</param>
|
|||
/// <returns>The exact sum.</returns>
|
|||
private static int SumScalar(ReadOnlySpan<short> samples) |
|||
{ |
|||
int sum = 0; |
|||
foreach (short sample in samples) |
|||
{ |
|||
sum += sample; |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,239 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|||
|
|||
/// <content>
|
|||
/// Defines Q5 interpolation for AV1 directional intra prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1DirectionalIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The largest number of samples required to transpose a directional prediction block.
|
|||
/// </summary>
|
|||
public const int ScratchLength = 64 * 64; |
|||
|
|||
/// <summary>
|
|||
/// Defines scalar and SIMD interpolation for AV1 directional intra prediction.
|
|||
/// </summary>
|
|||
internal interface IDirectionalPredictionOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Interpolates one 8-bit pair.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference sample.</param>
|
|||
/// <param name="right">The second reference sample.</param>
|
|||
/// <param name="weight">The Q5 weight of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated sample.</returns>
|
|||
public static abstract byte Interpolate(byte left, byte right, int weight); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates one high-bit-depth pair.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference sample.</param>
|
|||
/// <param name="right">The second reference sample.</param>
|
|||
/// <param name="weight">The Q5 weight of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated sample.</returns>
|
|||
public static abstract short Interpolate(short left, short right, int weight); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates sixteen 8-bit pairs.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference samples.</param>
|
|||
/// <param name="right">The second reference samples.</param>
|
|||
/// <param name="weight">The Q5 weight of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated samples.</returns>
|
|||
public static abstract Vector128<byte> Interpolate(Vector128<byte> left, Vector128<byte> right, int weight); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates thirty-two 8-bit pairs.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference samples.</param>
|
|||
/// <param name="right">The second reference samples.</param>
|
|||
/// <param name="weight">The Q5 weight of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated samples.</returns>
|
|||
public static abstract Vector256<byte> Interpolate(Vector256<byte> left, Vector256<byte> right, int weight); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates sixty-four 8-bit pairs.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference samples.</param>
|
|||
/// <param name="right">The second reference samples.</param>
|
|||
/// <param name="weight">The Q5 weight of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated samples.</returns>
|
|||
public static abstract Vector512<byte> Interpolate(Vector512<byte> left, Vector512<byte> right, int weight); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates eight high-bit-depth pairs.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference samples.</param>
|
|||
/// <param name="right">The second reference samples.</param>
|
|||
/// <param name="weight">The Q5 weight of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated samples.</returns>
|
|||
public static abstract Vector128<short> Interpolate(Vector128<short> left, Vector128<short> right, int weight); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates sixteen high-bit-depth pairs.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference samples.</param>
|
|||
/// <param name="right">The second reference samples.</param>
|
|||
/// <param name="weight">The Q5 weight of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated samples.</returns>
|
|||
public static abstract Vector256<short> Interpolate(Vector256<short> left, Vector256<short> right, int weight); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates thirty-two high-bit-depth pairs.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference samples.</param>
|
|||
/// <param name="right">The second reference samples.</param>
|
|||
/// <param name="weight">The Q5 weight of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated samples.</returns>
|
|||
public static abstract Vector512<short> Interpolate(Vector512<short> left, Vector512<short> right, int weight); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates four widened pairs with independent weights.
|
|||
/// </summary>
|
|||
/// <param name="left">The first reference samples.</param>
|
|||
/// <param name="right">The second reference samples.</param>
|
|||
/// <param name="weights">The Q5 weights of <paramref name="right"/>.</param>
|
|||
/// <returns>The interpolated samples.</returns>
|
|||
public static abstract Vector128<int> Interpolate(Vector128<int> left, Vector128<int> right, Vector128<int> weights); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal Q8 projection derivative for an adjusted angle.
|
|||
/// </summary>
|
|||
public static int GetDeltaX(int angle) => Predictor<DirectionalOperator>.GetDeltaX(angle); |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical Q8 projection derivative for an adjusted angle.
|
|||
/// </summary>
|
|||
public static int GetDeltaY(int angle) => Predictor<DirectionalOperator>.GetDeltaY(angle); |
|||
|
|||
/// <summary>
|
|||
/// Predicts an 8-bit directional block.
|
|||
/// </summary>
|
|||
public static void Predict(Span<byte> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int angle, Span<byte> scratch) |
|||
=> Predictor<DirectionalOperator>.Predict(destination, destinationStride, transformSize, above, left, upsampleAbove, upsampleLeft, angle, scratch); |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth directional block.
|
|||
/// </summary>
|
|||
public static void Predict(Span<short> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int angle, Span<short> scratch) |
|||
=> Predictor<DirectionalOperator>.Predict(destination, destinationStride, transformSize, above, left, upsampleAbove, upsampleLeft, angle, scratch); |
|||
|
|||
/// <summary>
|
|||
/// Predicts an 8-bit directional block without hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictScalar(Span<byte> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int angle) |
|||
=> Predictor<DirectionalOperator>.PredictScalar(destination, destinationStride, transformSize, above, left, upsampleAbove, upsampleLeft, angle); |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth directional block without hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictScalar(Span<short> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int angle) |
|||
=> Predictor<DirectionalOperator>.PredictScalar(destination, destinationStride, transformSize, above, left, upsampleAbove, upsampleLeft, angle); |
|||
|
|||
/// <summary>
|
|||
/// Interpolates projected neighboring samples for all directional prediction zones.
|
|||
/// </summary>
|
|||
internal readonly struct DirectionalOperator : IDirectionalPredictionOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Interpolate(byte left, byte right, int weight) |
|||
=> (byte)(((left * (32 - weight)) + (right * weight) + 16) >> 5); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static short Interpolate(short left, short right, int weight) |
|||
=> (short)(((left * (32 - weight)) + (right * weight) + 16) >> 5); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Interpolate(Vector128<byte> left, Vector128<byte> right, int weight) |
|||
{ |
|||
(Vector128<ushort> leftLow, Vector128<ushort> leftHigh) = Vector128.Widen(left); |
|||
(Vector128<ushort> rightLow, Vector128<ushort> rightHigh) = Vector128.Widen(right); |
|||
Vector128<ushort> rounding = Vector128.Create((ushort)16); |
|||
Vector128<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5; |
|||
Vector128<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5; |
|||
|
|||
return Vector128.Narrow(low, high); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Interpolate(Vector256<byte> left, Vector256<byte> right, int weight) |
|||
{ |
|||
(Vector256<ushort> leftLow, Vector256<ushort> leftHigh) = Vector256.Widen(left); |
|||
(Vector256<ushort> rightLow, Vector256<ushort> rightHigh) = Vector256.Widen(right); |
|||
Vector256<ushort> rounding = Vector256.Create((ushort)16); |
|||
Vector256<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5; |
|||
Vector256<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5; |
|||
|
|||
return Vector256.Narrow(low, high); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Interpolate(Vector512<byte> left, Vector512<byte> right, int weight) |
|||
{ |
|||
(Vector512<ushort> leftLow, Vector512<ushort> leftHigh) = Vector512.Widen(left); |
|||
(Vector512<ushort> rightLow, Vector512<ushort> rightHigh) = Vector512.Widen(right); |
|||
Vector512<ushort> rounding = Vector512.Create((ushort)16); |
|||
Vector512<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5; |
|||
Vector512<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5; |
|||
|
|||
return Vector512.Narrow(low, high); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<short> Interpolate(Vector128<short> left, Vector128<short> right, int weight) |
|||
{ |
|||
(Vector128<int> leftLow, Vector128<int> leftHigh) = Vector128.Widen(left); |
|||
(Vector128<int> rightLow, Vector128<int> rightHigh) = Vector128.Widen(right); |
|||
Vector128<int> rounding = Vector128.Create(16); |
|||
Vector128<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5; |
|||
Vector128<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5; |
|||
|
|||
return Vector128.Narrow(low, high); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<short> Interpolate(Vector256<short> left, Vector256<short> right, int weight) |
|||
{ |
|||
(Vector256<int> leftLow, Vector256<int> leftHigh) = Vector256.Widen(left); |
|||
(Vector256<int> rightLow, Vector256<int> rightHigh) = Vector256.Widen(right); |
|||
Vector256<int> rounding = Vector256.Create(16); |
|||
Vector256<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5; |
|||
Vector256<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5; |
|||
|
|||
return Vector256.Narrow(low, high); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<short> Interpolate(Vector512<short> left, Vector512<short> right, int weight) |
|||
{ |
|||
(Vector512<int> leftLow, Vector512<int> leftHigh) = Vector512.Widen(left); |
|||
(Vector512<int> rightLow, Vector512<int> rightHigh) = Vector512.Widen(right); |
|||
Vector512<int> rounding = Vector512.Create(16); |
|||
Vector512<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5; |
|||
Vector512<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5; |
|||
|
|||
return Vector512.Narrow(low, high); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> Interpolate(Vector128<int> left, Vector128<int> right, Vector128<int> weights) |
|||
=> ((left * (Vector128.Create(32) - weights)) + (right * weights) + Vector128.Create(16)) >> 5; |
|||
} |
|||
} |
|||
@ -1,443 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs AV1 directional intra-prediction blocks from prepared neighboring samples.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The three projection zones implement the directional prediction process in section 7.11.2.4 of the AV1 specification.
|
|||
/// </remarks>
|
|||
internal static partial class Av1DirectionalIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The largest number of samples required to transpose a directional prediction block.
|
|||
/// </summary>
|
|||
public const int ScratchLength = 64 * 64; |
|||
|
|||
/// <summary>
|
|||
/// Gets the Q8 directional derivatives indexed by acute prediction angle.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<int> DirectionalIntraDerivative => |
|||
[ |
|||
|
|||
// Zero entries represent angles which AV1 never signals. Direct indexing avoids a search or division in
|
|||
// each directional block while retaining the exact fixed-point projections from the normative table.
|
|||
0, 0, 0, 1023, 0, 0, 547, 0, 0, 372, 0, 0, 0, 0, 273, 0, 0, 215, 0, 0, 178, 0, 0, |
|||
151, 0, 0, 132, 0, 0, 116, 0, 0, 102, 0, 0, 0, 90, 0, 0, 80, 0, 0, 71, 0, 0, 64, 0, 0, |
|||
57, 0, 0, 51, 0, 0, 45, 0, 0, 0, 40, 0, 0, 35, 0, 0, 31, 0, 0, 27, 0, 0, 23, 0, 0, |
|||
19, 0, 0, 15, 0, 0, 0, 0, 11, 0, 0, 7, 0, 0, 3, 0, 0, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Predicts an 8-bit directional block using the widest available SIMD path.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|||
/// <param name="above">The prepared top reference, including any required extension.</param>
|
|||
/// <param name="left">The prepared left reference, including any required extension.</param>
|
|||
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
|
|||
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
|
|||
/// <param name="angle">The adjusted prediction angle.</param>
|
|||
/// <param name="scratch">The caller-owned block transposition workspace.</param>
|
|||
public static void Predict(Span<byte> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int angle, Span<byte> scratch) |
|||
{ |
|||
int width = transformSize.GetWidth(); |
|||
int height = transformSize.GetHeight(); |
|||
|
|||
if (angle is > 0 and < 90) |
|||
{ |
|||
PredictZone1(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height); |
|||
} |
|||
else if (angle is > 90 and < 180) |
|||
{ |
|||
PredictZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height); |
|||
} |
|||
else if (angle is > 180 and < 270) |
|||
{ |
|||
// libaom computes zone 3 as a zone 1 block with swapped dimensions, then transposes it. This preserves
|
|||
// contiguous reference reads and destination stores in both hot stages instead of scattering columns.
|
|||
Span<byte> transposed = scratch[..(width * height)]; |
|||
PredictZone1(transposed, height, left, upsampleLeft, GetDeltaY(angle), height, width); |
|||
Transpose(transposed, destination, height, width, destinationStride); |
|||
} |
|||
else |
|||
{ |
|||
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal; |
|||
Av1IntraPredictorBase.GetPredictor(mode).Predict(destination, destinationStride, above, left, width, height); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth directional block using the widest available SIMD path.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|||
/// <param name="above">The prepared top reference, including any required extension.</param>
|
|||
/// <param name="left">The prepared left reference, including any required extension.</param>
|
|||
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
|
|||
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
|
|||
/// <param name="angle">The adjusted prediction angle.</param>
|
|||
/// <param name="scratch">The caller-owned block transposition workspace.</param>
|
|||
public static void Predict(Span<short> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int angle, Span<short> scratch) |
|||
{ |
|||
int width = transformSize.GetWidth(); |
|||
int height = transformSize.GetHeight(); |
|||
|
|||
if (angle is > 0 and < 90) |
|||
{ |
|||
PredictZone1(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height); |
|||
} |
|||
else if (angle is > 90 and < 180) |
|||
{ |
|||
PredictZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height); |
|||
} |
|||
else if (angle is > 180 and < 270) |
|||
{ |
|||
Span<short> transposed = scratch[..(width * height)]; |
|||
PredictZone1(transposed, height, left, upsampleLeft, GetDeltaY(angle), height, width); |
|||
Transpose(transposed, destination, height, width, destinationStride); |
|||
} |
|||
else |
|||
{ |
|||
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal; |
|||
Av1IntraPredictorBase.GetPredictor(mode).Predict(destination, destinationStride, above, left, width, height); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts an 8-bit directional block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|||
/// <param name="above">The prepared top reference, including any required extension.</param>
|
|||
/// <param name="left">The prepared left reference, including any required extension.</param>
|
|||
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
|
|||
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
|
|||
/// <param name="angle">The adjusted prediction angle.</param>
|
|||
public static void PredictScalar(Span<byte> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int angle) |
|||
{ |
|||
int width = transformSize.GetWidth(); |
|||
int height = transformSize.GetHeight(); |
|||
|
|||
if (angle is > 0 and < 90) |
|||
{ |
|||
PredictZone1Scalar(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height); |
|||
} |
|||
else if (angle is > 90 and < 180) |
|||
{ |
|||
PredictZone2Scalar(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height); |
|||
} |
|||
else if (angle is > 180 and < 270) |
|||
{ |
|||
PredictZone3Scalar(destination, destinationStride, left, upsampleLeft, GetDeltaY(angle), width, height); |
|||
} |
|||
else |
|||
{ |
|||
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal; |
|||
Av1IntraPredictorBase.GetPredictor(mode).PredictScalar(destination, destinationStride, above, left, width, height); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts a high-bit-depth directional block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|||
/// <param name="above">The prepared top reference, including any required extension.</param>
|
|||
/// <param name="left">The prepared left reference, including any required extension.</param>
|
|||
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
|
|||
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
|
|||
/// <param name="angle">The adjusted prediction angle.</param>
|
|||
public static void PredictScalar(Span<short> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int angle) |
|||
{ |
|||
int width = transformSize.GetWidth(); |
|||
int height = transformSize.GetHeight(); |
|||
|
|||
if (angle is > 0 and < 90) |
|||
{ |
|||
PredictZone1Scalar(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height); |
|||
} |
|||
else if (angle is > 90 and < 180) |
|||
{ |
|||
PredictZone2Scalar(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height); |
|||
} |
|||
else if (angle is > 180 and < 270) |
|||
{ |
|||
PredictZone3Scalar(destination, destinationStride, left, upsampleLeft, GetDeltaY(angle), width, height); |
|||
} |
|||
else |
|||
{ |
|||
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal; |
|||
Av1IntraPredictorBase.GetPredictor(mode).PredictScalar(destination, destinationStride, above, left, width, height); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal Q8 projection derivative for an adjusted angle.
|
|||
/// </summary>
|
|||
/// <param name="angle">The adjusted prediction angle.</param>
|
|||
/// <returns>The horizontal derivative, or one when the selected zone does not consume it.</returns>
|
|||
public static int GetDeltaX(int angle) |
|||
=> angle switch |
|||
{ |
|||
> 0 and < 90 => DirectionalIntraDerivative[angle], |
|||
> 90 and < 180 => DirectionalIntraDerivative[180 - angle], |
|||
_ => 1, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical Q8 projection derivative for an adjusted angle.
|
|||
/// </summary>
|
|||
/// <param name="angle">The adjusted prediction angle.</param>
|
|||
/// <returns>The vertical derivative, or one when the selected zone does not consume it.</returns>
|
|||
public static int GetDeltaY(int angle) |
|||
=> angle switch |
|||
{ |
|||
> 90 and < 180 => DirectionalIntraDerivative[angle - 90], |
|||
> 180 and < 270 => DirectionalIntraDerivative[270 - angle], |
|||
_ => 1, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Predicts one 8-bit zone 1 block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="above">The projected top reference.</param>
|
|||
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
|||
/// <param name="derivative">The Q8 projection derivative.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
private static void PredictZone1Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, bool upsample, int derivative, int width, int height) |
|||
{ |
|||
int upsampleShift = upsample ? 1 : 0; |
|||
int maximumBasis = (width + height - 1) << upsampleShift; |
|||
int fractionBits = 6 - upsampleShift; |
|||
int basisIncrement = 1 << upsampleShift; |
|||
int projection = derivative; |
|||
ref byte aboveBase = ref Unsafe.AsRef(in above[0]); |
|||
|
|||
for (int row = 0; row < height; row++, projection += derivative) |
|||
{ |
|||
int basis = projection >> fractionBits; |
|||
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
|||
ref byte destinationRow = ref destination[row * destinationStride]; |
|||
|
|||
for (int column = 0; column < width; column++, basis += basisIncrement) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = basis < maximumBasis |
|||
? (byte)(((Unsafe.Add(ref aboveBase, basis) * (32 - weight)) + (Unsafe.Add(ref aboveBase, basis + 1) * weight) + 16) >> 5) |
|||
: Unsafe.Add(ref aboveBase, maximumBasis); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts one high-bit-depth zone 1 block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="above">The projected top reference.</param>
|
|||
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
|||
/// <param name="derivative">The Q8 projection derivative.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
private static void PredictZone1Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, bool upsample, int derivative, int width, int height) |
|||
{ |
|||
int upsampleShift = upsample ? 1 : 0; |
|||
int maximumBasis = (width + height - 1) << upsampleShift; |
|||
int fractionBits = 6 - upsampleShift; |
|||
int basisIncrement = 1 << upsampleShift; |
|||
int projection = derivative; |
|||
ref short aboveBase = ref Unsafe.AsRef(in above[0]); |
|||
|
|||
for (int row = 0; row < height; row++, projection += derivative) |
|||
{ |
|||
int basis = projection >> fractionBits; |
|||
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
|||
ref short destinationRow = ref destination[row * destinationStride]; |
|||
|
|||
for (int column = 0; column < width; column++, basis += basisIncrement) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = basis < maximumBasis |
|||
? (short)(((Unsafe.Add(ref aboveBase, basis) * (32 - weight)) + (Unsafe.Add(ref aboveBase, basis + 1) * weight) + 16) >> 5) |
|||
: Unsafe.Add(ref aboveBase, maximumBasis); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts one 8-bit zone 2 block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="above">The projected top reference.</param>
|
|||
/// <param name="left">The projected left reference.</param>
|
|||
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
|
|||
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
|
|||
/// <param name="dx">The horizontal Q8 derivative.</param>
|
|||
/// <param name="dy">The vertical Q8 derivative.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
private static void PredictZone2Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height) |
|||
{ |
|||
int aboveShift = upsampleAbove ? 1 : 0; |
|||
int leftShift = upsampleLeft ? 1 : 0; |
|||
int minimumTopBasis = -(1 << aboveShift); |
|||
int topFractionBits = 6 - aboveShift; |
|||
int leftFractionBits = 6 - leftShift; |
|||
int topBasisIncrement = 1 << aboveShift; |
|||
int topProjection = -dx; |
|||
ref byte aboveBase = ref Unsafe.AsRef(in above[0]); |
|||
ref byte leftBase = ref Unsafe.AsRef(in left[0]); |
|||
|
|||
for (int row = 0; row < height; row++, topProjection -= dx) |
|||
{ |
|||
int topBasis = topProjection >> topFractionBits; |
|||
int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1; |
|||
int leftProjection = (row << 6) - dy; |
|||
ref byte destinationRow = ref destination[row * destinationStride]; |
|||
|
|||
for (int column = 0; column < width; column++, topBasis += topBasisIncrement, leftProjection -= dy) |
|||
{ |
|||
int prediction; |
|||
if (topBasis >= minimumTopBasis) |
|||
{ |
|||
prediction = (Unsafe.Add(ref aboveBase, topBasis) * (32 - topWeight)) + (Unsafe.Add(ref aboveBase, topBasis + 1) * topWeight); |
|||
} |
|||
else |
|||
{ |
|||
int leftBasis = leftProjection >> leftFractionBits; |
|||
int leftWeight = ((leftProjection << leftShift) & 0x3F) >> 1; |
|||
prediction = (Unsafe.Add(ref leftBase, leftBasis) * (32 - leftWeight)) + (Unsafe.Add(ref leftBase, leftBasis + 1) * leftWeight); |
|||
} |
|||
|
|||
Unsafe.Add(ref destinationRow, column) = (byte)((prediction + 16) >> 5); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts one high-bit-depth zone 2 block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="above">The projected top reference.</param>
|
|||
/// <param name="left">The projected left reference.</param>
|
|||
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
|
|||
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
|
|||
/// <param name="dx">The horizontal Q8 derivative.</param>
|
|||
/// <param name="dy">The vertical Q8 derivative.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
private static void PredictZone2Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height) |
|||
{ |
|||
int aboveShift = upsampleAbove ? 1 : 0; |
|||
int leftShift = upsampleLeft ? 1 : 0; |
|||
int minimumTopBasis = -(1 << aboveShift); |
|||
int topFractionBits = 6 - aboveShift; |
|||
int leftFractionBits = 6 - leftShift; |
|||
int topBasisIncrement = 1 << aboveShift; |
|||
int topProjection = -dx; |
|||
ref short aboveBase = ref Unsafe.AsRef(in above[0]); |
|||
ref short leftBase = ref Unsafe.AsRef(in left[0]); |
|||
|
|||
for (int row = 0; row < height; row++, topProjection -= dx) |
|||
{ |
|||
int topBasis = topProjection >> topFractionBits; |
|||
int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1; |
|||
int leftProjection = (row << 6) - dy; |
|||
ref short destinationRow = ref destination[row * destinationStride]; |
|||
|
|||
for (int column = 0; column < width; column++, topBasis += topBasisIncrement, leftProjection -= dy) |
|||
{ |
|||
int prediction; |
|||
if (topBasis >= minimumTopBasis) |
|||
{ |
|||
prediction = (Unsafe.Add(ref aboveBase, topBasis) * (32 - topWeight)) + (Unsafe.Add(ref aboveBase, topBasis + 1) * topWeight); |
|||
} |
|||
else |
|||
{ |
|||
int leftBasis = leftProjection >> leftFractionBits; |
|||
int leftWeight = ((leftProjection << leftShift) & 0x3F) >> 1; |
|||
prediction = (Unsafe.Add(ref leftBase, leftBasis) * (32 - leftWeight)) + (Unsafe.Add(ref leftBase, leftBasis + 1) * leftWeight); |
|||
} |
|||
|
|||
Unsafe.Add(ref destinationRow, column) = (short)((prediction + 16) >> 5); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts one 8-bit zone 3 block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="left">The projected left reference.</param>
|
|||
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
|||
/// <param name="derivative">The Q8 projection derivative.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
private static void PredictZone3Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> left, bool upsample, int derivative, int width, int height) |
|||
{ |
|||
int upsampleShift = upsample ? 1 : 0; |
|||
int maximumBasis = (width + height - 1) << upsampleShift; |
|||
int fractionBits = 6 - upsampleShift; |
|||
int basisIncrement = 1 << upsampleShift; |
|||
int projection = derivative; |
|||
ref byte leftBase = ref Unsafe.AsRef(in left[0]); |
|||
|
|||
for (int column = 0; column < width; column++, projection += derivative) |
|||
{ |
|||
int basis = projection >> fractionBits; |
|||
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
|||
for (int row = 0; row < height; row++, basis += basisIncrement) |
|||
{ |
|||
destination[(row * destinationStride) + column] = basis < maximumBasis |
|||
? (byte)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5) |
|||
: Unsafe.Add(ref leftBase, maximumBasis); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Predicts one high-bit-depth zone 3 block without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="left">The projected left reference.</param>
|
|||
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
|||
/// <param name="derivative">The Q8 projection derivative.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
private static void PredictZone3Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> left, bool upsample, int derivative, int width, int height) |
|||
{ |
|||
int upsampleShift = upsample ? 1 : 0; |
|||
int maximumBasis = (width + height - 1) << upsampleShift; |
|||
int fractionBits = 6 - upsampleShift; |
|||
int basisIncrement = 1 << upsampleShift; |
|||
int projection = derivative; |
|||
ref short leftBase = ref Unsafe.AsRef(in left[0]); |
|||
|
|||
for (int column = 0; column < width; column++, projection += derivative) |
|||
{ |
|||
int basis = projection >> fractionBits; |
|||
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
|||
for (int row = 0; row < height; row++, basis += basisIncrement) |
|||
{ |
|||
destination[(row * destinationStride) + column] = basis < maximumBasis |
|||
? (short)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5) |
|||
: Unsafe.Add(ref leftBase, maximumBasis); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,356 @@ |
|||
// 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.Av1.Prediction; |
|||
|
|||
/// <content>
|
|||
/// Defines the closed scalar/SIMD operator contract and traversal for AV1 palette prediction.
|
|||
/// </content>
|
|||
internal static class Av1PalettePredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Multiplies a palette index by two to select the low byte of a high-bit-depth entry.
|
|||
/// </summary>
|
|||
private const ushort PaletteByteOffsetMultiplier = 0x0202; |
|||
|
|||
/// <summary>
|
|||
/// Adds one to each odd control byte so each shuffled high-bit-depth sample retains both bytes.
|
|||
/// </summary>
|
|||
private const ushort PaletteHighByteOffset = 0x0100; |
|||
|
|||
/// <summary>
|
|||
/// Defines scalar and SIMD palette-index lookup.
|
|||
/// </summary>
|
|||
private interface IPaletteOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Predicts one 8-bit sample.
|
|||
/// </summary>
|
|||
/// <param name="palette">The first palette entry.</param>
|
|||
/// <param name="index">The palette index.</param>
|
|||
/// <returns>The selected sample.</returns>
|
|||
public static abstract byte Predict(ref byte palette, byte index); |
|||
|
|||
/// <summary>
|
|||
/// Predicts sixteen 8-bit samples.
|
|||
/// </summary>
|
|||
/// <param name="palette">The palette entries repeated in each 128-bit lane.</param>
|
|||
/// <param name="indices">The palette indices.</param>
|
|||
/// <returns>The selected samples.</returns>
|
|||
public static abstract Vector128<byte> Predict(Vector128<byte> palette, Vector128<byte> indices); |
|||
|
|||
/// <summary>
|
|||
/// Predicts thirty-two 8-bit samples.
|
|||
/// </summary>
|
|||
/// <param name="palette">The palette entries repeated in each 128-bit lane.</param>
|
|||
/// <param name="indices">The palette indices.</param>
|
|||
/// <returns>The selected samples.</returns>
|
|||
public static abstract Vector256<byte> Predict(Vector256<byte> palette, Vector256<byte> indices); |
|||
|
|||
/// <summary>
|
|||
/// Predicts sixty-four 8-bit samples.
|
|||
/// </summary>
|
|||
/// <param name="palette">The palette entries repeated in each 128-bit lane.</param>
|
|||
/// <param name="indices">The palette indices.</param>
|
|||
/// <returns>The selected samples.</returns>
|
|||
public static abstract Vector512<byte> Predict(Vector512<byte> palette, Vector512<byte> indices); |
|||
|
|||
/// <summary>
|
|||
/// Predicts one high-bit-depth sample.
|
|||
/// </summary>
|
|||
/// <param name="palette">The first palette entry.</param>
|
|||
/// <param name="index">The palette index.</param>
|
|||
/// <returns>The selected sample.</returns>
|
|||
public static abstract short Predict(ref ushort palette, byte index); |
|||
|
|||
/// <summary>
|
|||
/// Predicts eight high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="palette">The palette bytes repeated in each 128-bit lane.</param>
|
|||
/// <param name="indices">The palette indices.</param>
|
|||
/// <returns>The selected samples.</returns>
|
|||
public static abstract Vector128<short> Predict(Vector128<byte> palette, Vector128<ushort> indices); |
|||
|
|||
/// <summary>
|
|||
/// Predicts sixteen high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="palette">The palette bytes repeated in each 128-bit lane.</param>
|
|||
/// <param name="indices">The palette indices.</param>
|
|||
/// <returns>The selected samples.</returns>
|
|||
public static abstract Vector256<short> Predict(Vector256<byte> palette, Vector256<ushort> indices); |
|||
|
|||
/// <summary>
|
|||
/// Predicts thirty-two high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="palette">The palette bytes repeated in each 128-bit lane.</param>
|
|||
/// <param name="indices">The palette indices.</param>
|
|||
/// <returns>The selected samples.</returns>
|
|||
public static abstract Vector512<short> Predict(Vector512<byte> palette, Vector512<ushort> indices); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit palette-predicted block.
|
|||
/// </summary>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> paletteColors, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int colorIndexMapStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> Predictor<PaletteOperator>.Predict(paletteColors, colorIndexMap, colorIndexMapStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth palette-predicted block.
|
|||
/// </summary>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> paletteColors, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int colorIndexMapStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> Predictor<PaletteOperator>.Predict(paletteColors, colorIndexMap, colorIndexMapStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Maps decoded palette indices to reconstructed samples.
|
|||
/// </summary>
|
|||
private readonly struct PaletteOperator : IPaletteOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Predict(ref byte palette, byte index) => Unsafe.Add(ref palette, index); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Predict(Vector128<byte> palette, Vector128<byte> indices) |
|||
=> Vector128.ShuffleNative(palette, indices); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Predict(Vector256<byte> palette, Vector256<byte> indices) |
|||
=> Vector256.ShuffleNative(palette, indices); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Predict(Vector512<byte> palette, Vector512<byte> indices) |
|||
=> Vector512.ShuffleNative(palette, indices); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static short Predict(ref ushort palette, byte index) => (short)Unsafe.Add(ref palette, index); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<short> Predict(Vector128<byte> palette, Vector128<ushort> indices) |
|||
{ |
|||
Vector128<ushort> controls = (indices * Vector128.Create(PaletteByteOffsetMultiplier)) + Vector128.Create(PaletteHighByteOffset); |
|||
|
|||
return Vector128.ShuffleNative(palette, controls.AsByte()).AsInt16(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<short> Predict(Vector256<byte> palette, Vector256<ushort> indices) |
|||
{ |
|||
Vector256<ushort> controls = (indices * Vector256.Create(PaletteByteOffsetMultiplier)) + Vector256.Create(PaletteHighByteOffset); |
|||
|
|||
return Vector256.ShuffleNative(palette, controls.AsByte()).AsInt16(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<short> Predict(Vector512<byte> palette, Vector512<ushort> indices) |
|||
{ |
|||
Vector512<ushort> controls = (indices * Vector512.Create(PaletteByteOffsetMultiplier)) + Vector512.Create(PaletteHighByteOffset); |
|||
|
|||
return Vector512.ShuffleNative(palette, controls.AsByte()).AsInt16(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Traverses palette blocks through one closed lookup operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The palette lookup arithmetic.</typeparam>
|
|||
private static class Predictor<TOperator> |
|||
where TOperator : struct, IPaletteOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit palette block.
|
|||
/// </summary>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> paletteColors, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int colorIndexMapStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
ref byte mapBase = ref MemoryMarshal.GetReference(colorIndexMap); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
// AV1 palettes contain at most eight colors. Repeating all eight entries in every 128-bit lane keeps native
|
|||
// table lookup lane-local at every SIMD width and removes palette bounds work from the reconstruction loop.
|
|||
ulong packedPalette = 0; |
|||
for (int index = 0; index < paletteColors.Length; index++) |
|||
{ |
|||
packedPalette |= (ulong)(byte)paletteColors[index] << (index * 8); |
|||
} |
|||
|
|||
ref byte paletteBase = ref Unsafe.As<ulong, byte>(ref packedPalette); |
|||
Vector128<byte> palette128 = Vector128.Create(packedPalette, packedPalette).AsByte(); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
Vector512<byte> palette512 = Vector512.Create(palette256, palette256); |
|||
int oneVectorFromEnd = width - Vector512<byte>.Count; |
|||
|
|||
for (; column <= oneVectorFromEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> indices = Vector512.LoadUnsafe(ref mapRow, (nuint)column); |
|||
TOperator.Predict(palette512, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
int oneVectorFromEnd = width - Vector256<byte>.Count; |
|||
|
|||
for (; column <= oneVectorFromEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> indices = Vector256.LoadUnsafe(ref mapRow, (nuint)column); |
|||
TOperator.Predict(palette256, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector128<byte>.Count; |
|||
for (; column <= oneVectorFromEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> indices = Vector128.LoadUnsafe(ref mapRow, (nuint)column); |
|||
TOperator.Predict(palette128, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
|
|||
int remaining = width - column; |
|||
if (remaining >= 8) |
|||
{ |
|||
ulong packedIndices = Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref mapRow, column)); |
|||
Vector128<byte> prediction = TOperator.Predict(palette128, Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationRow, column), prediction.AsUInt64().ToScalar()); |
|||
column += 8; |
|||
remaining -= 8; |
|||
} |
|||
|
|||
if (remaining >= 4) |
|||
{ |
|||
uint packedIndices = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref mapRow, column)); |
|||
Vector128<byte> prediction = TOperator.Predict(palette128, Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationRow, column), prediction.AsUInt32().ToScalar()); |
|||
column += 4; |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(ref paletteBase, Unsafe.Add(ref mapRow, column)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth palette block.
|
|||
/// </summary>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> paletteColors, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int colorIndexMapStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
ref byte mapBase = ref MemoryMarshal.GetReference(colorIndexMap); |
|||
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
InlineArray8<ushort> paletteStorage = default; |
|||
paletteColors.CopyTo(paletteStorage); |
|||
|
|||
ref ushort paletteBase = ref paletteStorage[0]; |
|||
Vector128<byte> palette128 = Vector128.LoadUnsafe(ref paletteBase).AsByte(); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
Vector512<byte> palette512 = Vector512.Create(palette256, palette256); |
|||
int oneVectorFromEnd = width - Vector512<short>.Count; |
|||
|
|||
for (; column <= oneVectorFromEnd; column += Vector512<short>.Count) |
|||
{ |
|||
(Vector256<ushort> lower, Vector256<ushort> upper) = Vector256.Widen(Vector256.LoadUnsafe(ref mapRow, (nuint)column)); |
|||
Vector512<ushort> indices = Vector512.Create(lower, upper); |
|||
TOperator.Predict(palette512, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
int oneVectorFromEnd = width - Vector256<short>.Count; |
|||
|
|||
for (; column <= oneVectorFromEnd; column += Vector256<short>.Count) |
|||
{ |
|||
(Vector128<ushort> lower, Vector128<ushort> upper) = Vector128.Widen(Vector128.LoadUnsafe(ref mapRow, (nuint)column)); |
|||
Vector256<ushort> indices = Vector256.Create(lower, upper); |
|||
TOperator.Predict(palette256, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector128<short>.Count; |
|||
for (; column <= oneVectorFromEnd; column += Vector128<short>.Count) |
|||
{ |
|||
ulong packedIndices = Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref mapRow, column)); |
|||
Vector128<ushort> indices = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
TOperator.Predict(palette128, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
|
|||
if (width - column >= 4) |
|||
{ |
|||
uint packedIndices = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref mapRow, column)); |
|||
Vector128<ushort> indices = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
Vector128<short> prediction = TOperator.Predict(palette128, indices); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<short, byte>(ref Unsafe.Add(ref destinationRow, column)), prediction.AsUInt64().ToScalar()); |
|||
column += 4; |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(ref paletteBase, Unsafe.Add(ref mapRow, column)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,274 +0,0 @@ |
|||
// 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.Av1.Prediction; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs AV1 palette-predicted sample blocks from decoded color-index maps.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// AV1 palettes contain at most eight entries, so a complete 8-bit palette fits in each native shuffle lane and a
|
|||
/// complete high-bit-depth palette fits as sixteen bytes. Color indices become byte-shuffle controls; replicating the
|
|||
/// table per 128-bit lane keeps every lookup lane-local at 128, 256, and 512 bits. Exact-width tail loads and stores
|
|||
/// avoid requiring writable padding around small transform blocks.
|
|||
/// </remarks>
|
|||
internal static class Av1PalettePredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The repeated byte offsets that select both bytes of eight 16-bit palette entries.
|
|||
/// </summary>
|
|||
private const ushort PaletteByteOffsetMultiplier = 0x0202; |
|||
|
|||
/// <summary>
|
|||
/// The high-byte increment that selects the second byte of each 16-bit palette entry.
|
|||
/// </summary>
|
|||
private const ushort PaletteHighByteOffset = 0x0100; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit palette-predicted block.
|
|||
/// </summary>
|
|||
/// <param name="paletteColors">The decoded palette colors in prediction-index order.</param>
|
|||
/// <param name="colorIndexMap">The color-index map beginning at the prediction block origin.</param>
|
|||
/// <param name="colorIndexMapStride">The distance, in indices, between map rows.</param>
|
|||
/// <param name="destination">The destination beginning at the prediction block origin.</param>
|
|||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|||
/// <param name="width">The prediction width in samples.</param>
|
|||
/// <param name="height">The prediction height in samples.</param>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> paletteColors, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int colorIndexMapStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
ref byte mapBase = ref MemoryMarshal.GetReference(colorIndexMap); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
// An AV1 palette contains at most eight colors. Packing it once into the low 64 bits and repeating it in
|
|||
// every 128-bit lane turns reconstruction into the lane-local table lookup implemented by ShuffleNative.
|
|||
ulong packedPalette = 0; |
|||
for (int index = 0; index < paletteColors.Length; index++) |
|||
{ |
|||
packedPalette |= (ulong)(byte)paletteColors[index] << (index * 8); |
|||
} |
|||
|
|||
Vector128<byte> palette128 = Vector128.Create(packedPalette, packedPalette).AsByte(); |
|||
|
|||
if (Vector512.IsHardwareAccelerated && width >= Vector512<byte>.Count) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
Vector512<byte> palette512 = Vector512.Create(palette256, palette256); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < width; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> indices = Vector512.LoadUnsafe(ref mapRow, (nuint)column); |
|||
Vector512.ShuffleNative(palette512, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated && width >= Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < width; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> indices = Vector256.LoadUnsafe(ref mapRow, (nuint)column); |
|||
Vector256.ShuffleNative(palette256, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
|
|||
for (; column <= width - Vector128<byte>.Count; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> indices = Vector128.LoadUnsafe(ref mapRow, (nuint)column); |
|||
Vector128.ShuffleNative(palette128, indices).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
|
|||
if (column < width) |
|||
{ |
|||
// Transform widths are powers of two. After complete vector chunks, only a four- or eight-byte
|
|||
// row tail remains, so an exact-width load and store keeps neighboring transforms untouched.
|
|||
int remaining = width - column; |
|||
ulong packedIndices = remaining == 4 |
|||
? Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref mapRow, column)) |
|||
: Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref mapRow, column)); |
|||
|
|||
Vector128<byte> result = Vector128.ShuffleNative(palette128, Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
|
|||
if (remaining == 4) |
|||
{ |
|||
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationRow, column), result.AsUInt32().ToScalar()); |
|||
} |
|||
else |
|||
{ |
|||
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationRow, column), result.AsUInt64().ToScalar()); |
|||
} |
|||
|
|||
column = width; |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = (byte)paletteColors[Unsafe.Add(ref mapRow, column)]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth palette-predicted block.
|
|||
/// </summary>
|
|||
/// <param name="paletteColors">The decoded palette colors in prediction-index order.</param>
|
|||
/// <param name="colorIndexMap">The color-index map beginning at the prediction block origin.</param>
|
|||
/// <param name="colorIndexMapStride">The distance, in indices, between map rows.</param>
|
|||
/// <param name="destination">The destination beginning at the prediction block origin.</param>
|
|||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|||
/// <param name="width">The prediction width in samples.</param>
|
|||
/// <param name="height">The prediction height in samples.</param>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> paletteColors, |
|||
ReadOnlySpan<byte> colorIndexMap, |
|||
int colorIndexMapStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
ref byte mapBase = ref MemoryMarshal.GetReference(colorIndexMap); |
|||
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
InlineArray8<ushort> paletteStorage = default; |
|||
|
|||
paletteColors.CopyTo(paletteStorage); |
|||
|
|||
// Each index is expanded to the byte offsets 2n and 2n+1. Repeating the complete 16-byte palette in every
|
|||
// 128-bit lane then permits the same native byte-table shuffle on x86, Arm, and WebAssembly.
|
|||
Vector128<byte> palette128 = Vector128.LoadUnsafe(ref paletteStorage[0]).AsByte(); |
|||
|
|||
if (Vector512.IsHardwareAccelerated && width >= Vector512<ushort>.Count) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
Vector512<byte> palette512 = Vector512.Create(palette256, palette256); |
|||
Vector512<ushort> multiplier = Vector512.Create(PaletteByteOffsetMultiplier); |
|||
Vector512<ushort> increment = Vector512.Create(PaletteHighByteOffset); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < width; column += Vector512<ushort>.Count) |
|||
{ |
|||
(Vector256<ushort> lower, Vector256<ushort> upper) = Vector256.Widen(Vector256.LoadUnsafe(ref mapRow, (nuint)column)); |
|||
|
|||
Vector512<ushort> indices = Vector512.Create(lower, upper); |
|||
Vector512<byte> controls = ((indices * multiplier) + increment).AsByte(); |
|||
Vector512.ShuffleNative(palette512, controls).AsInt16().StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated && width >= Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<byte> palette256 = Vector256.Create(palette128, palette128); |
|||
Vector256<ushort> multiplier = Vector256.Create(PaletteByteOffsetMultiplier); |
|||
Vector256<ushort> increment = Vector256.Create(PaletteHighByteOffset); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < width; column += Vector256<ushort>.Count) |
|||
{ |
|||
(Vector128<ushort> lower, Vector128<ushort> upper) = Vector128.Widen(Vector128.LoadUnsafe(ref mapRow, (nuint)column)); |
|||
|
|||
Vector256<ushort> indices = Vector256.Create(lower, upper); |
|||
Vector256<byte> controls = ((indices * multiplier) + increment).AsByte(); |
|||
Vector256.ShuffleNative(palette256, controls).AsInt16().StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<ushort> multiplier = Vector128.Create(PaletteByteOffsetMultiplier); |
|||
Vector128<ushort> increment = Vector128.Create(PaletteHighByteOffset); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
|
|||
for (; column <= width - Vector128<ushort>.Count; column += Vector128<ushort>.Count) |
|||
{ |
|||
ulong packedIndices = Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref mapRow, column)); |
|||
Vector128<ushort> indices = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
Vector128<byte> controls = ((indices * multiplier) + increment).AsByte(); |
|||
Vector128.ShuffleNative(palette128, controls).AsInt16().StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
|
|||
if (column < width) |
|||
{ |
|||
uint packedIndices = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref mapRow, column)); |
|||
Vector128<ushort> indices = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packedIndices).AsByte()); |
|||
Vector128<byte> controls = ((indices * multiplier) + increment).AsByte(); |
|||
Vector128<short> result = Vector128.ShuffleNative(palette128, controls).AsInt16(); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<short, byte>(ref Unsafe.Add(ref destinationRow, column)), result.AsUInt64().ToScalar()); |
|||
column += 4; |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte mapRow = ref Unsafe.Add(ref mapBase, row * colorIndexMapStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = (short)paletteColors[Unsafe.Add(ref mapRow, column)]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,324 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.Arm; |
|||
using System.Runtime.Intrinsics.X86; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; |
|||
|
|||
/// <content>
|
|||
/// Defines the closed scalar/SIMD operator contract and traversal for AV1 chroma-from-luma prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1ChromaFromLumaPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The fixed row stride of the AV1 chroma-from-luma scratch buffer.
|
|||
/// </summary>
|
|||
private const int BufferLine = 32; |
|||
|
|||
/// <summary>
|
|||
/// Defines scalar and SIMD signed Q3 arithmetic for AV1 chroma-from-luma prediction.
|
|||
/// </summary>
|
|||
private interface IChromaFromLumaOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Predicts one chroma sample.
|
|||
/// </summary>
|
|||
/// <param name="lumaQ3">The zero-mean Q3 luma sample.</param>
|
|||
/// <param name="dc">The chroma DC prediction.</param>
|
|||
/// <param name="alphaQ3">The signed Q3 chroma scaling factor.</param>
|
|||
/// <param name="maximum">The maximum sample value.</param>
|
|||
/// <returns>The predicted chroma sample.</returns>
|
|||
public static abstract short Predict(short lumaQ3, short dc, int alphaQ3, short maximum); |
|||
|
|||
/// <summary>
|
|||
/// Predicts eight chroma samples.
|
|||
/// </summary>
|
|||
/// <param name="lumaQ3">The zero-mean Q3 luma samples.</param>
|
|||
/// <param name="dc">The chroma DC prediction.</param>
|
|||
/// <param name="alphaQ3">The signed Q3 chroma scaling factor.</param>
|
|||
/// <param name="maximum">The maximum sample value.</param>
|
|||
/// <returns>The predicted chroma samples.</returns>
|
|||
public static abstract Vector128<short> Predict(Vector128<short> lumaQ3, short dc, int alphaQ3, short maximum); |
|||
|
|||
/// <summary>
|
|||
/// Predicts sixteen chroma samples.
|
|||
/// </summary>
|
|||
/// <param name="lumaQ3">The zero-mean Q3 luma samples.</param>
|
|||
/// <param name="dc">The chroma DC prediction.</param>
|
|||
/// <param name="alphaQ3">The signed Q3 chroma scaling factor.</param>
|
|||
/// <param name="maximum">The maximum sample value.</param>
|
|||
/// <returns>The predicted chroma samples.</returns>
|
|||
public static abstract Vector256<short> Predict(Vector256<short> lumaQ3, short dc, int alphaQ3, short maximum); |
|||
|
|||
/// <summary>
|
|||
/// Predicts thirty-two chroma samples.
|
|||
/// </summary>
|
|||
/// <param name="lumaQ3">The zero-mean Q3 luma samples.</param>
|
|||
/// <param name="dc">The chroma DC prediction.</param>
|
|||
/// <param name="alphaQ3">The signed Q3 chroma scaling factor.</param>
|
|||
/// <param name="maximum">The maximum sample value.</param>
|
|||
/// <returns>The predicted chroma samples.</returns>
|
|||
public static abstract Vector512<short> Predict(Vector512<short> lumaQ3, short dc, int alphaQ3, short maximum); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies chroma-from-luma prediction to an 8-bit chroma block.
|
|||
/// </summary>
|
|||
/// <param name="lumaQ3">The zero-mean Q3 luma surface.</param>
|
|||
/// <param name="destination">The DC-predicted chroma block that receives the luma adjustment.</param>
|
|||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|||
/// <param name="alphaQ3">The signed Q3 chroma scaling factor.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
public static void Predict(ReadOnlySpan<short> lumaQ3, Span<byte> destination, int destinationStride, int alphaQ3, int width, int height) |
|||
=> Predictor<ChromaFromLumaOperator>.Predict(lumaQ3, destination, destinationStride, alphaQ3, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Applies chroma-from-luma prediction to a high-bit-depth chroma block.
|
|||
/// </summary>
|
|||
/// <param name="lumaQ3">The zero-mean Q3 luma surface.</param>
|
|||
/// <param name="destination">The DC-predicted chroma block that receives the luma adjustment.</param>
|
|||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|||
/// <param name="alphaQ3">The signed Q3 chroma scaling factor.</param>
|
|||
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
public static void Predict(ReadOnlySpan<short> lumaQ3, Span<short> destination, int destinationStride, int alphaQ3, int bitDepth, int width, int height) |
|||
=> Predictor<ChromaFromLumaOperator>.Predict(lumaQ3, destination, destinationStride, alphaQ3, bitDepth, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Applies the decoded chroma scaling factor to zero-mean luma samples.
|
|||
/// </summary>
|
|||
private readonly struct ChromaFromLumaOperator : IChromaFromLumaOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static short Predict(short lumaQ3, short dc, int alphaQ3, short maximum) |
|||
{ |
|||
int scaledLumaQ0 = Av1Math.RoundPowerOf2Signed(alphaQ3 * lumaQ3, 6); |
|||
|
|||
return (short)Math.Clamp(dc + scaledLumaQ0, (short)0, maximum); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<short> Predict(Vector128<short> lumaQ3, short dc, int alphaQ3, short maximum) |
|||
{ |
|||
Vector128<short> dcVector = Vector128.Create(dc); |
|||
Vector128<short> scaledLumaQ0; |
|||
|
|||
if (Ssse3.IsSupported) |
|||
{ |
|||
Vector128<short> alphaSign = Vector128.Create((short)alphaQ3); |
|||
Vector128<short> alphaQ12 = Vector128.Create((short)(Math.Abs(alphaQ3) << 9)); |
|||
scaledLumaQ0 = Ssse3.MultiplyHighRoundScale(Ssse3.Abs(lumaQ3).AsInt16(), alphaQ12); |
|||
Vector128<short> signMask = (lumaQ3 ^ alphaSign) >> 15; |
|||
scaledLumaQ0 = (scaledLumaQ0 ^ signMask) - signMask; |
|||
} |
|||
else if (AdvSimd.IsSupported) |
|||
{ |
|||
Vector128<short> alphaSign = Vector128.Create((short)alphaQ3); |
|||
Vector128<short> alphaQ12 = Vector128.Create((short)(Math.Abs(alphaQ3) << 9)); |
|||
scaledLumaQ0 = AdvSimd.MultiplyRoundedDoublingSaturateHigh(Vector128.Abs(lumaQ3), alphaQ12); |
|||
Vector128<short> signMask = (lumaQ3 ^ alphaSign) >> 15; |
|||
scaledLumaQ0 = (scaledLumaQ0 ^ signMask) - signMask; |
|||
} |
|||
else |
|||
{ |
|||
// WebAssembly and other Vector128 targets do not expose packed rounded-high multiply. Widening keeps
|
|||
// the same signed rounding rule without introducing a second scalar traversal.
|
|||
(Vector128<int> lower, Vector128<int> upper) = Vector128.Widen(lumaQ3); |
|||
Vector128<int> alpha = Vector128.Create(alphaQ3); |
|||
lower *= alpha; |
|||
upper *= alpha; |
|||
lower = (lower + Vector128.Create(32) + (lower >> 31)) >> 6; |
|||
upper = (upper + Vector128.Create(32) + (upper >> 31)) >> 6; |
|||
scaledLumaQ0 = Vector128.Narrow(lower, upper); |
|||
} |
|||
|
|||
return Vector128.Clamp(scaledLumaQ0 + dcVector, Vector128<short>.Zero, Vector128.Create(maximum)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<short> Predict(Vector256<short> lumaQ3, short dc, int alphaQ3, short maximum) |
|||
{ |
|||
Vector256<short> dcVector = Vector256.Create(dc); |
|||
Vector256<short> scaledLumaQ0; |
|||
|
|||
if (Avx2.IsSupported) |
|||
{ |
|||
Vector256<short> alphaSign = Vector256.Create((short)alphaQ3); |
|||
Vector256<short> alphaQ12 = Vector256.Create((short)(Math.Abs(alphaQ3) << 9)); |
|||
scaledLumaQ0 = Avx2.MultiplyHighRoundScale(Avx2.Abs(lumaQ3).AsInt16(), alphaQ12); |
|||
Vector256<short> signMask = (lumaQ3 ^ alphaSign) >> 15; |
|||
scaledLumaQ0 = (scaledLumaQ0 ^ signMask) - signMask; |
|||
} |
|||
else |
|||
{ |
|||
(Vector256<int> lower, Vector256<int> upper) = Vector256.Widen(lumaQ3); |
|||
Vector256<int> alpha = Vector256.Create(alphaQ3); |
|||
lower *= alpha; |
|||
upper *= alpha; |
|||
lower = (lower + Vector256.Create(32) + (lower >> 31)) >> 6; |
|||
upper = (upper + Vector256.Create(32) + (upper >> 31)) >> 6; |
|||
scaledLumaQ0 = Vector256.Narrow(lower, upper); |
|||
} |
|||
|
|||
return Vector256.Clamp(scaledLumaQ0 + dcVector, Vector256<short>.Zero, Vector256.Create(maximum)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<short> Predict(Vector512<short> lumaQ3, short dc, int alphaQ3, short maximum) |
|||
{ |
|||
(Vector512<int> lower, Vector512<int> upper) = Vector512.Widen(lumaQ3); |
|||
Vector512<int> alpha = Vector512.Create(alphaQ3); |
|||
lower *= alpha; |
|||
upper *= alpha; |
|||
lower = (lower + Vector512.Create(32) + (lower >> 31)) >> 6; |
|||
upper = (upper + Vector512.Create(32) + (upper >> 31)) >> 6; |
|||
Vector512<short> scaledLumaQ0 = Vector512.Narrow(lower, upper); |
|||
|
|||
return Vector512.Clamp(scaledLumaQ0 + Vector512.Create(dc), Vector512<short>.Zero, Vector512.Create(maximum)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Traverses a chroma block through one closed prediction operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The signed Q3 prediction arithmetic.</typeparam>
|
|||
private static class Predictor<TOperator> |
|||
where TOperator : struct, IChromaFromLumaOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies chroma-from-luma prediction to an 8-bit block.
|
|||
/// </summary>
|
|||
public static void Predict(ReadOnlySpan<short> lumaQ3, Span<byte> destination, int destinationStride, int alphaQ3, int width, int height) |
|||
{ |
|||
ref short lumaBase = ref MemoryMarshal.GetReference(lumaQ3); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
short dc = destinationBase; |
|||
|
|||
// CfL follows DC prediction, so one sample supplies the base value for the complete block. The fixed
|
|||
// scratch stride also makes exact-width Vector128 loads safe for the four-sample AV1 tail.
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = row * BufferLine; |
|||
int destinationRowOffset = row * destinationStride; |
|||
ref short lumaRow = ref Unsafe.Add(ref lumaBase, lumaRowOffset); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, destinationRowOffset); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector512<short>.Count; |
|||
for (; column <= oneVectorFromEnd; column += Vector512<short>.Count) |
|||
{ |
|||
Vector512<short> prediction = TOperator.Predict(Vector512.LoadUnsafe(ref lumaRow, (nuint)column), dc, alphaQ3, byte.MaxValue); |
|||
Vector256<byte> packed = Vector512.Narrow(prediction.AsUInt16(), Vector512<ushort>.Zero).GetLower(); |
|||
packed.StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector256<short>.Count; |
|||
for (; column <= oneVectorFromEnd; column += Vector256<short>.Count) |
|||
{ |
|||
Vector256<short> prediction = TOperator.Predict(Vector256.LoadUnsafe(ref lumaRow, (nuint)column), dc, alphaQ3, byte.MaxValue); |
|||
Vector128<byte> packed = Vector256.Narrow(prediction.AsUInt16(), Vector256<ushort>.Zero).GetLower(); |
|||
packed.StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector128<short>.Count; |
|||
for (; column <= oneVectorFromEnd; column += Vector128<short>.Count) |
|||
{ |
|||
Vector128<short> prediction = TOperator.Predict(Vector128.LoadUnsafe(ref lumaRow, (nuint)column), dc, alphaQ3, byte.MaxValue); |
|||
Vector64<byte> packed = Vector128.Narrow(prediction.AsUInt16(), Vector128<ushort>.Zero).GetLower(); |
|||
packed.StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
|
|||
if (width - column >= 4) |
|||
{ |
|||
Vector128<short> prediction = TOperator.Predict(Vector128.LoadUnsafe(ref lumaRow, (nuint)column), dc, alphaQ3, byte.MaxValue); |
|||
Vector64<byte> packed = Vector128.Narrow(prediction.AsUInt16(), Vector128<ushort>.Zero).GetLower(); |
|||
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationRow, column), packed.AsUInt32().ToScalar()); |
|||
column += 4; |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = (byte)TOperator.Predict(Unsafe.Add(ref lumaRow, column), dc, alphaQ3, byte.MaxValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies chroma-from-luma prediction to a high-bit-depth block.
|
|||
/// </summary>
|
|||
public static void Predict(ReadOnlySpan<short> lumaQ3, Span<short> destination, int destinationStride, int alphaQ3, int bitDepth, int width, int height) |
|||
{ |
|||
ref short lumaBase = ref MemoryMarshal.GetReference(lumaQ3); |
|||
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
short dc = destinationBase; |
|||
short maximum = (short)((1 << bitDepth) - 1); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = row * BufferLine; |
|||
int destinationRowOffset = row * destinationStride; |
|||
ref short lumaRow = ref Unsafe.Add(ref lumaBase, lumaRowOffset); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, destinationRowOffset); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector512<short>.Count; |
|||
for (; column <= oneVectorFromEnd; column += Vector512<short>.Count) |
|||
{ |
|||
TOperator.Predict(Vector512.LoadUnsafe(ref lumaRow, (nuint)column), dc, alphaQ3, maximum).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector256<short>.Count; |
|||
for (; column <= oneVectorFromEnd; column += Vector256<short>.Count) |
|||
{ |
|||
TOperator.Predict(Vector256.LoadUnsafe(ref lumaRow, (nuint)column), dc, alphaQ3, maximum).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector128<short>.Count; |
|||
for (; column <= oneVectorFromEnd; column += Vector128<short>.Count) |
|||
{ |
|||
TOperator.Predict(Vector128.LoadUnsafe(ref lumaRow, (nuint)column), dc, alphaQ3, maximum).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
|
|||
if (width - column >= 4) |
|||
{ |
|||
Vector128<short> prediction = TOperator.Predict(Vector128.LoadUnsafe(ref lumaRow, (nuint)column), dc, alphaQ3, maximum); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<short, byte>(ref Unsafe.Add(ref destinationRow, column)), prediction.AsUInt64().ToScalar()); |
|||
column += 4; |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(Unsafe.Add(ref lumaRow, column), dc, alphaQ3, maximum); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,237 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.Arm; |
|||
using System.Runtime.Intrinsics.X86; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; |
|||
|
|||
/// <summary>
|
|||
/// Applies an AV1 chroma-from-luma residual to a DC-predicted chroma block.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Each signed 16-bit lane contains one zero-mean Q3 luma value. Packed rounded-high multiplication converts the product
|
|||
/// with the Q3 alpha parameter directly to a signed integer adjustment; the alpha/luma sign mask restores the product
|
|||
/// sign after the magnitude operation. The common DC prediction is broadcast, then results are clipped and narrowed to
|
|||
/// the destination sample representation.
|
|||
/// </remarks>
|
|||
internal static class Av1ChromaFromLumaPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The fixed row stride of the AV1 chroma-from-luma scratch buffer.
|
|||
/// </summary>
|
|||
private const int BufferLine = 32; |
|||
|
|||
/// <summary>
|
|||
/// Applies chroma-from-luma prediction to an 8-bit chroma block.
|
|||
/// </summary>
|
|||
/// <param name="lumaQ3">The zero-mean Q3 luma surface.</param>
|
|||
/// <param name="destination">The DC-predicted chroma block that receives the luma adjustment.</param>
|
|||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|||
/// <param name="alphaQ3">The signed Q3 chroma scaling factor.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
public static void Predict(ReadOnlySpan<short> lumaQ3, Span<byte> destination, int destinationStride, int alphaQ3, int width, int height) |
|||
{ |
|||
ref short lumaBase = ref MemoryMarshal.GetReference(lumaQ3); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
short dc = destinationBase; |
|||
|
|||
// CfL always follows DC prediction, so the first sample is the common base value for every lane. This
|
|||
// mirrors libaom and avoids loading a block that is known to contain a single repeated prediction value.
|
|||
if (Avx2.IsSupported && width >= Vector256<short>.Count) |
|||
{ |
|||
Vector256<short> alphaSign = Vector256.Create((short)alphaQ3); |
|||
Vector256<short> alphaQ12 = Vector256.Create((short)(Math.Abs(alphaQ3) << 9)); |
|||
Vector256<short> dcVector = Vector256.Create(dc); |
|||
Vector256<short> maximum = Vector256.Create((short)byte.MaxValue); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = row * BufferLine; |
|||
int destinationRowOffset = row * destinationStride; |
|||
|
|||
for (int column = 0; column < width; column += Vector256<short>.Count) |
|||
{ |
|||
Vector256<short> prediction = Predict(Vector256.LoadUnsafe(ref lumaBase, (nuint)(lumaRowOffset + column)), alphaSign, alphaQ12, dcVector); |
|||
prediction = Vector256.Clamp(prediction, Vector256<short>.Zero, maximum); |
|||
Vector256.Narrow(prediction.AsUInt16(), Vector256<ushort>.Zero).GetLower().StoreUnsafe(ref destinationBase, (nuint)(destinationRowOffset + column)); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<short> alphaSign = Vector128.Create((short)alphaQ3); |
|||
Vector128<short> alphaQ12 = Vector128.Create((short)(Math.Abs(alphaQ3) << 9)); |
|||
Vector128<short> dcVector = Vector128.Create(dc); |
|||
Vector128<short> maximum = Vector128.Create((short)byte.MaxValue); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = row * BufferLine; |
|||
int destinationRowOffset = row * destinationStride; |
|||
int column = 0; |
|||
|
|||
for (; column <= width - Vector128<short>.Count; column += Vector128<short>.Count) |
|||
{ |
|||
Vector128<short> prediction = Predict(Vector128.LoadUnsafe(ref lumaBase, (nuint)(lumaRowOffset + column)), alphaSign, alphaQ12, dcVector, alphaQ3); |
|||
prediction = Vector128.Clamp(prediction, Vector128<short>.Zero, maximum); |
|||
Vector128.Narrow(prediction.AsUInt16(), Vector128<ushort>.Zero).GetLower().StoreUnsafe(ref destinationBase, (nuint)(destinationRowOffset + column)); |
|||
} |
|||
|
|||
// Four-wide CfL blocks still have a complete padded scratch row, so reading eight residuals is
|
|||
// valid. Only the four active predictions are stored to the image buffer.
|
|||
if (column < width) |
|||
{ |
|||
Vector128<short> prediction = Predict(Vector128.LoadUnsafe(ref lumaBase, (nuint)(lumaRowOffset + column)), alphaSign, alphaQ12, dcVector, alphaQ3); |
|||
prediction = Vector128.Clamp(prediction, Vector128<short>.Zero, maximum); |
|||
Vector64<byte> packed = Vector128.Narrow(prediction.AsUInt16(), Vector128<ushort>.Zero).GetLower(); |
|||
Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationBase, destinationRowOffset + column), packed.AsUInt32().ToScalar()); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = row * BufferLine; |
|||
int destinationRowOffset = row * destinationStride; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int scaledLumaQ0 = Av1Math.RoundPowerOf2Signed(alphaQ3 * Unsafe.Add(ref lumaBase, lumaRowOffset + column), 6); |
|||
Unsafe.Add(ref destinationBase, destinationRowOffset + column) = (byte)Math.Clamp(dc + scaledLumaQ0, byte.MinValue, byte.MaxValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies chroma-from-luma prediction to a high-bit-depth chroma block.
|
|||
/// </summary>
|
|||
/// <param name="lumaQ3">The zero-mean Q3 luma surface.</param>
|
|||
/// <param name="destination">The DC-predicted chroma block that receives the luma adjustment.</param>
|
|||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|||
/// <param name="alphaQ3">The signed Q3 chroma scaling factor.</param>
|
|||
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
public static void Predict(ReadOnlySpan<short> lumaQ3, Span<short> destination, int destinationStride, int alphaQ3, int bitDepth, int width, int height) |
|||
{ |
|||
ref short lumaBase = ref MemoryMarshal.GetReference(lumaQ3); |
|||
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
short dc = destinationBase; |
|||
short maximum = (short)((1 << bitDepth) - 1); |
|||
|
|||
if (Avx2.IsSupported && width >= Vector256<short>.Count) |
|||
{ |
|||
Vector256<short> alphaSign = Vector256.Create((short)alphaQ3); |
|||
Vector256<short> alphaQ12 = Vector256.Create((short)(Math.Abs(alphaQ3) << 9)); |
|||
Vector256<short> dcVector = Vector256.Create(dc); |
|||
Vector256<short> maximumVector = Vector256.Create(maximum); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = row * BufferLine; |
|||
int destinationRowOffset = row * destinationStride; |
|||
|
|||
for (int column = 0; column < width; column += Vector256<short>.Count) |
|||
{ |
|||
Vector256<short> prediction = Predict(Vector256.LoadUnsafe(ref lumaBase, (nuint)(lumaRowOffset + column)), alphaSign, alphaQ12, dcVector); |
|||
Vector256.Clamp(prediction, Vector256<short>.Zero, maximumVector).StoreUnsafe(ref destinationBase, (nuint)(destinationRowOffset + column)); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<short> alphaSign = Vector128.Create((short)alphaQ3); |
|||
Vector128<short> alphaQ12 = Vector128.Create((short)(Math.Abs(alphaQ3) << 9)); |
|||
Vector128<short> dcVector = Vector128.Create(dc); |
|||
Vector128<short> maximumVector = Vector128.Create(maximum); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = row * BufferLine; |
|||
int destinationRowOffset = row * destinationStride; |
|||
int column = 0; |
|||
|
|||
for (; column <= width - Vector128<short>.Count; column += Vector128<short>.Count) |
|||
{ |
|||
Vector128<short> prediction = Predict(Vector128.LoadUnsafe(ref lumaBase, (nuint)(lumaRowOffset + column)), alphaSign, alphaQ12, dcVector, alphaQ3); |
|||
Vector128.Clamp(prediction, Vector128<short>.Zero, maximumVector).StoreUnsafe(ref destinationBase, (nuint)(destinationRowOffset + column)); |
|||
} |
|||
|
|||
if (column < width) |
|||
{ |
|||
Vector128<short> prediction = Predict(Vector128.LoadUnsafe(ref lumaBase, (nuint)(lumaRowOffset + column)), alphaSign, alphaQ12, dcVector, alphaQ3); |
|||
prediction = Vector128.Clamp(prediction, Vector128<short>.Zero, maximumVector); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<short, byte>(ref Unsafe.Add(ref destinationBase, destinationRowOffset + column)), prediction.AsUInt64().ToScalar()); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = row * BufferLine; |
|||
int destinationRowOffset = row * destinationStride; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int scaledLumaQ0 = Av1Math.RoundPowerOf2Signed(alphaQ3 * Unsafe.Add(ref lumaBase, lumaRowOffset + column), 6); |
|||
Unsafe.Add(ref destinationBase, destinationRowOffset + column) = (short)Math.Clamp(dc + scaledLumaQ0, 0, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates sixteen chroma predictions using the packed Q3 arithmetic defined by AV1.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<short> Predict(Vector256<short> lumaQ3, Vector256<short> alphaSign, Vector256<short> alphaQ12, Vector256<short> dc) |
|||
{ |
|||
Vector256<short> scaledLumaQ0 = Avx2.MultiplyHighRoundScale(Avx2.Abs(lumaQ3).AsInt16(), alphaQ12); |
|||
Vector256<short> signMask = (lumaQ3 ^ alphaSign) >> 15; |
|||
return ((scaledLumaQ0 ^ signMask) - signMask) + dc; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates eight chroma predictions using the packed Q3 arithmetic defined by AV1.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<short> Predict(Vector128<short> lumaQ3, Vector128<short> alphaSign, Vector128<short> alphaQ12, Vector128<short> dc, int alphaQ3) |
|||
{ |
|||
Vector128<short> scaledLumaQ0; |
|||
if (Ssse3.IsSupported) |
|||
{ |
|||
scaledLumaQ0 = Ssse3.MultiplyHighRoundScale(Ssse3.Abs(lumaQ3).AsInt16(), alphaQ12); |
|||
} |
|||
else if (AdvSimd.IsSupported) |
|||
{ |
|||
scaledLumaQ0 = AdvSimd.MultiplyRoundedDoublingSaturateHigh(Vector128.Abs(lumaQ3), alphaQ12); |
|||
} |
|||
else |
|||
{ |
|||
// WebAssembly and other Vector128 targets do not expose packed rounded-high multiply. Widening retains
|
|||
// SIMD traversal while reproducing the same signed nearest-integer result in ordinary integer lanes.
|
|||
(Vector128<int> lower, Vector128<int> upper) = Vector128.Widen(lumaQ3); |
|||
Vector128<int> alpha = Vector128.Create(alphaQ3); |
|||
lower *= alpha; |
|||
upper *= alpha; |
|||
lower = (lower + Vector128.Create(32) + (lower >> 31)) >> 6; |
|||
upper = (upper + Vector128.Create(32) + (upper >> 31)) >> 6; |
|||
return Vector128.Narrow(lower, upper) + dc; |
|||
} |
|||
|
|||
Vector128<short> signMask = (lumaQ3 ^ alphaSign) >> 15; |
|||
return ((scaledLumaQ0 ^ signMask) - signMask) + dc; |
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines equal-weight compound prediction arithmetic.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundAveragePredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines equal-weight compound averaging for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1CompoundAverageOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Averages two 8-bit samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first sample.</param>
|
|||
/// <param name="second">The second sample.</param>
|
|||
/// <returns>The rounded average.</returns>
|
|||
public static abstract byte Blend(byte first, byte second); |
|||
|
|||
/// <summary>
|
|||
/// Averages two high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first sample.</param>
|
|||
/// <param name="second">The second sample.</param>
|
|||
/// <returns>The rounded average.</returns>
|
|||
public static abstract ushort Blend(ushort first, ushort second); |
|||
|
|||
/// <summary>
|
|||
/// Averages two 128-bit vectors of 8-bit samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <returns>The rounded averages.</returns>
|
|||
public static abstract Vector128<byte> Blend(Vector128<byte> first, Vector128<byte> second); |
|||
|
|||
/// <summary>
|
|||
/// Averages two 256-bit vectors of 8-bit samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <returns>The rounded averages.</returns>
|
|||
public static abstract Vector256<byte> Blend(Vector256<byte> first, Vector256<byte> second); |
|||
|
|||
/// <summary>
|
|||
/// Averages two 512-bit vectors of 8-bit samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <returns>The rounded averages.</returns>
|
|||
public static abstract Vector512<byte> Blend(Vector512<byte> first, Vector512<byte> second); |
|||
|
|||
/// <summary>
|
|||
/// Averages two 128-bit vectors of high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <returns>The rounded averages.</returns>
|
|||
public static abstract Vector128<ushort> Blend(Vector128<ushort> first, Vector128<ushort> second); |
|||
|
|||
/// <summary>
|
|||
/// Averages two 256-bit vectors of high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <returns>The rounded averages.</returns>
|
|||
public static abstract Vector256<ushort> Blend(Vector256<ushort> first, Vector256<ushort> second); |
|||
|
|||
/// <summary>
|
|||
/// Averages two 512-bit vectors of high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <returns>The rounded averages.</returns>
|
|||
public static abstract Vector512<ushort> Blend(Vector512<ushort> first, Vector512<ushort> second); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements equal-weight rounded averaging for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct CompoundAverageOperator : IAv1CompoundAverageOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Blend(byte first, byte second) => (byte)((first + second + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static ushort Blend(ushort first, ushort second) => (ushort)((first + second + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Blend(Vector128<byte> first, Vector128<byte> second) |
|||
=> (first | second) - ((first ^ second) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Blend(Vector256<byte> first, Vector256<byte> second) |
|||
=> (first | second) - ((first ^ second) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Blend(Vector512<byte> first, Vector512<byte> second) |
|||
{ |
|||
// This identity is exactly (a + b + 1) >> 1 but cannot overflow unsigned lanes at any SIMD width.
|
|||
return (first | second) - ((first ^ second) >> 1); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<ushort> Blend(Vector128<ushort> first, Vector128<ushort> second) |
|||
=> (first | second) - ((first ^ second) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<ushort> Blend(Vector256<ushort> first, Vector256<ushort> second) |
|||
=> (first | second) - ((first ^ second) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<ushort> Blend(Vector512<ushort> first, Vector512<ushort> second) |
|||
=> (first | second) - ((first ^ second) >> 1); |
|||
} |
|||
} |
|||
@ -0,0 +1,281 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Combines two AV1 inter predictors with equal-weight rounded averaging.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundAveragePredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Averages an 8-bit predictor into an existing prediction block.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first predictor and combined output.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows in samples.</param>
|
|||
/// <param name="second">The second predictor.</param>
|
|||
/// <param name="secondStride">The distance between second-predictor rows in samples.</param>
|
|||
/// <param name="width">The active block width.</param>
|
|||
/// <param name="height">The active block height.</param>
|
|||
public static void Average( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height) |
|||
=> Average<CompoundAverageOperator>(destination, destinationStride, second, secondStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Averages an 8-bit predictor through one closed compound operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The equal-weight averaging arithmetic.</typeparam>
|
|||
/// <param name="destination">The first predictor and combined output.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="second">The second predictor.</param>
|
|||
/// <param name="secondStride">The distance between second-predictor rows.</param>
|
|||
/// <param name="width">The active block width.</param>
|
|||
/// <param name="height">The active block height.</param>
|
|||
private static void Average<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1CompoundAverageOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Averages a high-bit-depth predictor into an existing prediction block.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first predictor and combined output.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows in samples.</param>
|
|||
/// <param name="second">The second predictor.</param>
|
|||
/// <param name="secondStride">The distance between second-predictor rows in samples.</param>
|
|||
/// <param name="width">The active block width.</param>
|
|||
/// <param name="height">The active block height.</param>
|
|||
public static void Average( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height) |
|||
=> Average<CompoundAverageOperator>(destination, destinationStride, second, secondStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Averages a high-bit-depth predictor through one closed compound operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The equal-weight averaging arithmetic.</typeparam>
|
|||
/// <param name="destination">The first predictor and combined output.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="second">The second predictor.</param>
|
|||
/// <param name="secondStride">The distance between second-predictor rows.</param>
|
|||
/// <param name="width">The active block width.</param>
|
|||
/// <param name="height">The active block height.</param>
|
|||
private static void Average<TOperator>( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1CompoundAverageOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<ushort> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<ushort> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<ushort> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Averages an 8-bit predictor without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first predictor and combined output.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows in samples.</param>
|
|||
/// <param name="second">The second predictor.</param>
|
|||
/// <param name="secondStride">The distance between second-predictor rows in samples.</param>
|
|||
/// <param name="width">The active block width.</param>
|
|||
/// <param name="height">The active block height.</param>
|
|||
public static void AverageScalar( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height) |
|||
=> AverageScalar<CompoundAverageOperator>(destination, destinationStride, second, secondStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Averages an 8-bit predictor through one closed scalar compound operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The equal-weight averaging arithmetic.</typeparam>
|
|||
/// <param name="destination">The first predictor and combined output.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="second">The second predictor.</param>
|
|||
/// <param name="secondStride">The distance between second-predictor rows.</param>
|
|||
/// <param name="width">The active block width.</param>
|
|||
/// <param name="height">The active block height.</param>
|
|||
private static void AverageScalar<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1CompoundAverageOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Averages a high-bit-depth predictor without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first predictor and combined output.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows in samples.</param>
|
|||
/// <param name="second">The second predictor.</param>
|
|||
/// <param name="secondStride">The distance between second-predictor rows in samples.</param>
|
|||
/// <param name="width">The active block width.</param>
|
|||
/// <param name="height">The active block height.</param>
|
|||
public static void AverageScalar( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height) |
|||
=> AverageScalar<CompoundAverageOperator>(destination, destinationStride, second, secondStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Averages a high-bit-depth predictor through one closed scalar compound operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The equal-weight averaging arithmetic.</typeparam>
|
|||
/// <param name="destination">The first predictor and combined output.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="second">The second predictor.</param>
|
|||
/// <param name="secondStride">The distance between second-predictor rows.</param>
|
|||
/// <param name="width">The active block width.</param>
|
|||
/// <param name="height">The active block height.</param>
|
|||
private static void AverageScalar<TOperator>( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1CompoundAverageOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,211 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines distance-weighted compound prediction arithmetic.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundDistanceWeightedPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines distance-weighted compound blending for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1CompoundDistanceWeightedOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Blends two 8-bit samples with display-distance weights.
|
|||
/// </summary>
|
|||
/// <param name="first">The first sample.</param>
|
|||
/// <param name="second">The second sample.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <returns>The weighted sample.</returns>
|
|||
public static abstract byte Blend(byte first, byte second, int firstWeight, int secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Blends two high-bit-depth samples with display-distance weights.
|
|||
/// </summary>
|
|||
/// <param name="first">The first sample.</param>
|
|||
/// <param name="second">The second sample.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <returns>The weighted sample.</returns>
|
|||
public static abstract ushort Blend(ushort first, ushort second, int firstWeight, int secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Blends 128-bit vectors of 8-bit samples with display-distance weights.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <returns>The weighted samples.</returns>
|
|||
public static abstract Vector128<byte> Blend(Vector128<byte> first, Vector128<byte> second, int firstWeight, int secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Blends 256-bit vectors of 8-bit samples with display-distance weights.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <returns>The weighted samples.</returns>
|
|||
public static abstract Vector256<byte> Blend(Vector256<byte> first, Vector256<byte> second, int firstWeight, int secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Blends 512-bit vectors of 8-bit samples with display-distance weights.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <returns>The weighted samples.</returns>
|
|||
public static abstract Vector512<byte> Blend(Vector512<byte> first, Vector512<byte> second, int firstWeight, int secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Blends 128-bit vectors of high-bit-depth samples with display-distance weights.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <returns>The weighted samples.</returns>
|
|||
public static abstract Vector128<ushort> Blend(Vector128<ushort> first, Vector128<ushort> second, int firstWeight, int secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Blends 256-bit vectors of high-bit-depth samples with display-distance weights.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <returns>The weighted samples.</returns>
|
|||
public static abstract Vector256<ushort> Blend(Vector256<ushort> first, Vector256<ushort> second, int firstWeight, int secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Blends 512-bit vectors of high-bit-depth samples with display-distance weights.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <returns>The weighted samples.</returns>
|
|||
public static abstract Vector512<ushort> Blend(Vector512<ushort> first, Vector512<ushort> second, int firstWeight, int secondWeight); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements display-distance-weighted blending for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct CompoundDistanceWeightedOperator : IAv1CompoundDistanceWeightedOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Blend(byte first, byte second, int firstWeight, int secondWeight) |
|||
=> (byte)(((first * firstWeight) + (second * secondWeight) + 8) >> DistanceWeightBits); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static ushort Blend(ushort first, ushort second, int firstWeight, int secondWeight) |
|||
=> (ushort)(((first * firstWeight) + (second * secondWeight) + 8) >> DistanceWeightBits); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Blend(Vector128<byte> first, Vector128<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector128<int> first0, out Vector128<int> first1, out Vector128<int> first2, out Vector128<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector128<int> second0, out Vector128<int> second1, out Vector128<int> second2, out Vector128<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, firstWeight, secondWeight), |
|||
Blend(first1, second1, firstWeight, secondWeight), |
|||
Blend(first2, second2, firstWeight, secondWeight), |
|||
Blend(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Blend(Vector256<byte> first, Vector256<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector256<int> first0, out Vector256<int> first1, out Vector256<int> first2, out Vector256<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector256<int> second0, out Vector256<int> second1, out Vector256<int> second2, out Vector256<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, firstWeight, secondWeight), |
|||
Blend(first1, second1, firstWeight, secondWeight), |
|||
Blend(first2, second2, firstWeight, secondWeight), |
|||
Blend(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Blend(Vector512<byte> first, Vector512<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector512<int> first0, out Vector512<int> first1, out Vector512<int> first2, out Vector512<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector512<int> second0, out Vector512<int> second1, out Vector512<int> second2, out Vector512<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, firstWeight, secondWeight), |
|||
Blend(first1, second1, firstWeight, secondWeight), |
|||
Blend(first2, second2, firstWeight, secondWeight), |
|||
Blend(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<ushort> Blend(Vector128<ushort> first, Vector128<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector128<int> first0, out Vector128<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector128<int> second0, out Vector128<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, firstWeight, secondWeight), |
|||
Blend(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<ushort> Blend(Vector256<ushort> first, Vector256<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector256<int> first0, out Vector256<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector256<int> second0, out Vector256<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, firstWeight, secondWeight), |
|||
Blend(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<ushort> Blend(Vector512<ushort> first, Vector512<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector512<int> first0, out Vector512<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector512<int> second0, out Vector512<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, firstWeight, secondWeight), |
|||
Blend(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies display-distance weighting to 128-bit vectors of widened samples.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> Blend(Vector128<int> first, Vector128<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector128.Create(firstWeight)) + (second * Vector128.Create(secondWeight)) + Vector128.Create(8)) >> DistanceWeightBits; |
|||
|
|||
/// <summary>
|
|||
/// Applies display-distance weighting to 256-bit vectors of widened samples.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> Blend(Vector256<int> first, Vector256<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector256.Create(firstWeight)) + (second * Vector256.Create(secondWeight)) + Vector256.Create(8)) >> DistanceWeightBits; |
|||
|
|||
/// <summary>
|
|||
/// Applies display-distance weighting to 512-bit vectors of widened samples.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> Blend(Vector512<int> first, Vector512<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector512.Create(firstWeight)) + (second * Vector512.Create(secondWeight)) + Vector512.Create(8)) >> DistanceWeightBits; |
|||
} |
|||
} |
|||
@ -0,0 +1,235 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Reconstructs display-distance-weighted compound prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundDistanceWeightedPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Combines two 8-bit predictors with AV1 display-distance weights.
|
|||
/// </summary>
|
|||
public static void DistanceWeighted( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
=> DistanceWeighted<CompoundDistanceWeightedOperator>( |
|||
destination, |
|||
destinationStride, |
|||
second, |
|||
secondStride, |
|||
width, |
|||
height, |
|||
firstWeight, |
|||
secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed 8-bit distance-weighted compound operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound arithmetic operator.</typeparam>
|
|||
private static void DistanceWeighted<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
where TOperator : struct, IAv1CompoundDistanceWeightedOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column], firstWeight, secondWeight); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines two high-bit-depth predictors with AV1 display-distance weights.
|
|||
/// </summary>
|
|||
public static void DistanceWeighted( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
=> DistanceWeighted<CompoundDistanceWeightedOperator>( |
|||
destination, |
|||
destinationStride, |
|||
second, |
|||
secondStride, |
|||
width, |
|||
height, |
|||
firstWeight, |
|||
secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed high-bit-depth distance-weighted compound operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound arithmetic operator.</typeparam>
|
|||
private static void DistanceWeighted<TOperator>( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
where TOperator : struct, IAv1CompoundDistanceWeightedOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<ushort> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<ushort> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<ushort> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column], firstWeight, secondWeight); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines two 8-bit predictors with display-distance weights without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void DistanceWeightedScalar( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
=> DistanceWeightedScalar<CompoundDistanceWeightedOperator>( |
|||
destination, |
|||
destinationStride, |
|||
second, |
|||
secondStride, |
|||
width, |
|||
height, |
|||
firstWeight, |
|||
secondWeight); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed 8-bit distance-weighted compound operator without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound arithmetic operator.</typeparam>
|
|||
private static void DistanceWeightedScalar<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
where TOperator : struct, IAv1CompoundDistanceWeightedOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column], firstWeight, secondWeight); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Provides shared final-rounding arithmetic for compound intermediate reconstruction.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundInterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Derives the bias and remaining fractional precision of a compound intermediate.
|
|||
/// </summary>
|
|||
public static void GetIntermediateRounding(int bitDepth, out int roundBits, out int roundOffset) |
|||
{ |
|||
int intermediateRange = bitDepth + 7 - 3 + 2; |
|||
int round0 = 3 + Math.Max(intermediateRange - 16, 0); |
|||
int offsetBits = bitDepth + 14 - round0; |
|||
roundBits = 14 - round0 - CompoundRound1Bits; |
|||
roundOffset = (1 << (offsetBits - CompoundRound1Bits)) + |
|||
(1 << (offsetBits - CompoundRound1Bits - 1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the compound bias and final fractional precision from 128-bit unsigned lanes.
|
|||
/// </summary>
|
|||
public static Vector128<ushort> FinalizeIntermediate(Vector128<ushort> value, int roundBits, int roundOffset) |
|||
{ |
|||
Vector128<short> result = (value - Vector128.Create((ushort)roundOffset)).AsInt16(); |
|||
if (roundBits != 0) |
|||
{ |
|||
result = (result + Vector128.Create((short)(1 << (roundBits - 1)))) >> roundBits; |
|||
} |
|||
|
|||
result = Vector128.Max(Vector128<short>.Zero, Vector128.Min(Vector128.Create((short)byte.MaxValue), result)); |
|||
return result.AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the compound bias and final fractional precision from 256-bit unsigned lanes.
|
|||
/// </summary>
|
|||
public static Vector256<ushort> FinalizeIntermediate(Vector256<ushort> value, int roundBits, int roundOffset) |
|||
{ |
|||
Vector256<short> result = (value - Vector256.Create((ushort)roundOffset)).AsInt16(); |
|||
if (roundBits != 0) |
|||
{ |
|||
result = (result + Vector256.Create((short)(1 << (roundBits - 1)))) >> roundBits; |
|||
} |
|||
|
|||
result = Vector256.Max(Vector256<short>.Zero, Vector256.Min(Vector256.Create((short)byte.MaxValue), result)); |
|||
return result.AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the compound bias and final fractional precision from 512-bit unsigned lanes.
|
|||
/// </summary>
|
|||
public static Vector512<ushort> FinalizeIntermediate(Vector512<ushort> value, int roundBits, int roundOffset) |
|||
{ |
|||
Vector512<short> result = (value - Vector512.Create((ushort)roundOffset)).AsInt16(); |
|||
if (roundBits != 0) |
|||
{ |
|||
result = (result + Vector512.Create((short)(1 << (roundBits - 1)))) >> roundBits; |
|||
} |
|||
|
|||
result = Vector512.Max(Vector512<short>.Zero, Vector512.Min(Vector512.Create((short)byte.MaxValue), result)); |
|||
return result.AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the compound bias and final fractional precision from 128-bit widened lanes.
|
|||
/// </summary>
|
|||
public static Vector128<int> FinalizeIntermediate(Vector128<int> value, int roundBits, int roundOffset) |
|||
{ |
|||
Vector128<int> result = value - Vector128.Create(roundOffset); |
|||
if (roundBits != 0) |
|||
{ |
|||
result = (result + Vector128.Create(1 << (roundBits - 1))) >> roundBits; |
|||
} |
|||
|
|||
return Vector128.Max(Vector128<int>.Zero, Vector128.Min(Vector128.Create((int)byte.MaxValue), result)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the compound bias and final fractional precision from 256-bit widened lanes.
|
|||
/// </summary>
|
|||
public static Vector256<int> FinalizeIntermediate(Vector256<int> value, int roundBits, int roundOffset) |
|||
{ |
|||
Vector256<int> result = value - Vector256.Create(roundOffset); |
|||
if (roundBits != 0) |
|||
{ |
|||
result = (result + Vector256.Create(1 << (roundBits - 1))) >> roundBits; |
|||
} |
|||
|
|||
return Vector256.Max(Vector256<int>.Zero, Vector256.Min(Vector256.Create((int)byte.MaxValue), result)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the compound bias and final fractional precision from 512-bit widened lanes.
|
|||
/// </summary>
|
|||
public static Vector512<int> FinalizeIntermediate(Vector512<int> value, int roundBits, int roundOffset) |
|||
{ |
|||
Vector512<int> result = value - Vector512.Create(roundOffset); |
|||
if (roundBits != 0) |
|||
{ |
|||
result = (result + Vector512.Create(1 << (roundBits - 1))) >> roundBits; |
|||
} |
|||
|
|||
return Vector512.Max(Vector512<int>.Zero, Vector512.Min(Vector512.Create((int)byte.MaxValue), result)); |
|||
} |
|||
} |
|||
@ -1,501 +0,0 @@ |
|||
// 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.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Provides distance-weighted and per-sample masked compound blending.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundInterPredictor |
|||
{ |
|||
private const int DistanceWeightBits = 4; |
|||
private const int MaskWeightBits = 6; |
|||
private const int MaximumMaskAlpha = 1 << MaskWeightBits; |
|||
|
|||
/// <summary>
|
|||
/// Combines two 8-bit predictors with AV1 display-distance weights.
|
|||
/// </summary>
|
|||
public static void DistanceWeighted( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = (byte)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines two high-bit-depth predictors with AV1 display-distance weights.
|
|||
/// </summary>
|
|||
public static void DistanceWeighted( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<ushort> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<ushort> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<ushort> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = (ushort)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two 8-bit predictors through a contiguous AV1 alpha mask.
|
|||
/// </summary>
|
|||
public static void Blend( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<byte> maskVector = Vector512.LoadUnsafe(ref maskReference, (nuint)column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<byte> maskVector = Vector256.LoadUnsafe(ref maskReference, (nuint)column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<byte> maskVector = Vector128.LoadUnsafe(ref maskReference, (nuint)column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int alpha = maskRow[column]; |
|||
destinationRow[column] = (byte)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two high-bit-depth predictors through a contiguous AV1 alpha mask.
|
|||
/// </summary>
|
|||
public static void Blend( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<ushort> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> maskVector = LoadMask512(ref maskReference, column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<ushort> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> maskVector = LoadMask256(ref maskReference, column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<ushort> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> maskVector = LoadMask128(ref maskReference, column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int alpha = maskRow[column]; |
|||
destinationRow[column] = (ushort)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines two 8-bit predictors with display-distance weights without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void DistanceWeightedScalar( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destinationRow[column] = (byte)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two 8-bit predictors through an alpha mask without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void BlendScalar( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int alpha = maskRow[column]; |
|||
destinationRow[column] = (byte)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<byte> DistanceWeighted(Vector128<byte> first, Vector128<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector128<int> first0, out Vector128<int> first1, out Vector128<int> first2, out Vector128<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector128<int> second0, out Vector128<int> second1, out Vector128<int> second2, out Vector128<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight), |
|||
DistanceWeighted(first2, second2, firstWeight, secondWeight), |
|||
DistanceWeighted(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<byte> DistanceWeighted(Vector256<byte> first, Vector256<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector256<int> first0, out Vector256<int> first1, out Vector256<int> first2, out Vector256<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector256<int> second0, out Vector256<int> second1, out Vector256<int> second2, out Vector256<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight), |
|||
DistanceWeighted(first2, second2, firstWeight, secondWeight), |
|||
DistanceWeighted(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<byte> DistanceWeighted(Vector512<byte> first, Vector512<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector512<int> first0, out Vector512<int> first1, out Vector512<int> first2, out Vector512<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector512<int> second0, out Vector512<int> second1, out Vector512<int> second2, out Vector512<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight), |
|||
DistanceWeighted(first2, second2, firstWeight, secondWeight), |
|||
DistanceWeighted(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> DistanceWeighted(Vector128<ushort> first, Vector128<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector128<int> first0, out Vector128<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector128<int> second0, out Vector128<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> DistanceWeighted(Vector256<ushort> first, Vector256<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector256<int> first0, out Vector256<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector256<int> second0, out Vector256<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> DistanceWeighted(Vector512<ushort> first, Vector512<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector512<int> first0, out Vector512<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector512<int> second0, out Vector512<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> DistanceWeighted(Vector128<int> first, Vector128<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector128.Create(firstWeight)) + (second * Vector128.Create(secondWeight)) + Vector128.Create(8)) >> DistanceWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> DistanceWeighted(Vector256<int> first, Vector256<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector256.Create(firstWeight)) + (second * Vector256.Create(secondWeight)) + Vector256.Create(8)) >> DistanceWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> DistanceWeighted(Vector512<int> first, Vector512<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector512.Create(firstWeight)) + (second * Vector512.Create(secondWeight)) + Vector512.Create(8)) >> DistanceWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<byte> Blend(Vector128<byte> first, Vector128<byte> second, Vector128<byte> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector128<int> first0, out Vector128<int> first1, out Vector128<int> first2, out Vector128<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector128<int> second0, out Vector128<int> second1, out Vector128<int> second2, out Vector128<int> second3); |
|||
Av1IntraPredictorBase.Widen(mask, out Vector128<int> mask0, out Vector128<int> mask1, out Vector128<int> mask2, out Vector128<int> mask3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, mask0), |
|||
Blend(first1, second1, mask1), |
|||
Blend(first2, second2, mask2), |
|||
Blend(first3, second3, mask3)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<byte> Blend(Vector256<byte> first, Vector256<byte> second, Vector256<byte> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector256<int> first0, out Vector256<int> first1, out Vector256<int> first2, out Vector256<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector256<int> second0, out Vector256<int> second1, out Vector256<int> second2, out Vector256<int> second3); |
|||
Av1IntraPredictorBase.Widen(mask, out Vector256<int> mask0, out Vector256<int> mask1, out Vector256<int> mask2, out Vector256<int> mask3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, mask0), |
|||
Blend(first1, second1, mask1), |
|||
Blend(first2, second2, mask2), |
|||
Blend(first3, second3, mask3)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<byte> Blend(Vector512<byte> first, Vector512<byte> second, Vector512<byte> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector512<int> first0, out Vector512<int> first1, out Vector512<int> first2, out Vector512<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector512<int> second0, out Vector512<int> second1, out Vector512<int> second2, out Vector512<int> second3); |
|||
Av1IntraPredictorBase.Widen(mask, out Vector512<int> mask0, out Vector512<int> mask1, out Vector512<int> mask2, out Vector512<int> mask3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, mask0), |
|||
Blend(first1, second1, mask1), |
|||
Blend(first2, second2, mask2), |
|||
Blend(first3, second3, mask3)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> Blend(Vector128<ushort> first, Vector128<ushort> second, Vector128<ushort> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector128<int> first0, out Vector128<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector128<int> second0, out Vector128<int> second1); |
|||
Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector128<int> mask0, out Vector128<int> mask1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> Blend(Vector256<ushort> first, Vector256<ushort> second, Vector256<ushort> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector256<int> first0, out Vector256<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector256<int> second0, out Vector256<int> second1); |
|||
Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector256<int> mask0, out Vector256<int> mask1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> Blend(Vector512<ushort> first, Vector512<ushort> second, Vector512<ushort> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector512<int> first0, out Vector512<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector512<int> second0, out Vector512<int> second1); |
|||
Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector512<int> mask0, out Vector512<int> mask1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> Blend(Vector128<int> first, Vector128<int> second, Vector128<int> mask) |
|||
=> ((mask * first) + ((Vector128.Create(MaximumMaskAlpha) - mask) * second) + Vector128.Create(32)) >> MaskWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> Blend(Vector256<int> first, Vector256<int> second, Vector256<int> mask) |
|||
=> ((mask * first) + ((Vector256.Create(MaximumMaskAlpha) - mask) * second) + Vector256.Create(32)) >> MaskWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> Blend(Vector512<int> first, Vector512<int> second, Vector512<int> mask) |
|||
=> ((mask * first) + ((Vector512.Create(MaximumMaskAlpha) - mask) * second) + Vector512.Create(32)) >> MaskWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> LoadMask128(ref byte source, int offset) |
|||
{ |
|||
Vector64<byte> packed = Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref source, offset)); |
|||
return Vector128.WidenLower(Vector128.Create(packed, Vector64<byte>.Zero)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> LoadMask256(ref byte source, int offset) |
|||
{ |
|||
Vector128<byte> packed = Vector128.LoadUnsafe(ref source, (nuint)offset); |
|||
return Vector256.WidenLower(Vector256.Create(packed, Vector128<byte>.Zero)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> LoadMask512(ref byte source, int offset) |
|||
{ |
|||
Vector256<byte> packed = Vector256.LoadUnsafe(ref source, (nuint)offset); |
|||
return Vector512.WidenLower(Vector512.Create(packed, Vector256<byte>.Zero)); |
|||
} |
|||
} |
|||
@ -1,529 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Combines high-precision compound convolution intermediates into reconstructed samples.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundInterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Combines two compound intermediates by equal averaging.
|
|||
/// </summary>
|
|||
public static void AverageIntermediate( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth) |
|||
{ |
|||
GetIntermediateRounding(bitDepth, out int roundBits, out int roundOffset); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe( |
|||
ref firstReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe( |
|||
ref secondReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
AverageIntermediate(first0, first1, second0, second1, roundBits, roundOffset).StoreUnsafe( |
|||
ref destinationReference, |
|||
(nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
// The reference average deliberately truncates here. The sole rounding step follows bias removal,
|
|||
// preventing the double rounding that occurs when each reference is first converted to pixels.
|
|||
int result = ((firstRow[column] + secondRow[column]) >> 1) - roundOffset; |
|||
destinationRow[column] = (byte)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, byte.MaxValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines two compound intermediates using the decoded temporal-distance weights.
|
|||
/// </summary>
|
|||
public static void DistanceWeightedIntermediate( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int bitDepth) |
|||
{ |
|||
GetIntermediateRounding(bitDepth, out int roundBits, out int roundOffset); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe( |
|||
ref firstReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe( |
|||
ref secondReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
DistanceWeightedIntermediate( |
|||
first0, |
|||
first1, |
|||
second0, |
|||
second1, |
|||
firstWeight, |
|||
secondWeight, |
|||
roundBits, |
|||
roundOffset).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int result = ((firstRow[column] * firstWeight) + (secondRow[column] * secondWeight)) >> DistanceWeightBits; |
|||
result -= roundOffset; |
|||
destinationRow[column] = (byte)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, byte.MaxValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills a luma-resolution difference-weighted mask from compound intermediates.
|
|||
/// </summary>
|
|||
public static void FillDifferenceWeightedIntermediateMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
{ |
|||
bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; |
|||
GetIntermediateRounding(bitDepth, out int roundBits, out _); |
|||
int differenceRound = roundBits + bitDepth - 8; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe( |
|||
ref firstReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe( |
|||
ref secondReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
DifferenceWeightedIntermediate( |
|||
first0, |
|||
first1, |
|||
second0, |
|||
second1, |
|||
differenceRound, |
|||
invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int difference = Math.Abs(firstRow[column] - secondRow[column]); |
|||
difference = RoundPowerOfTwo(difference, differenceRound); |
|||
int alpha = Math.Min(MaximumMaskAlpha, 38 + (difference >> 4)); |
|||
maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two compound intermediates through a luma-resolution mask.
|
|||
/// </summary>
|
|||
public static void BlendIntermediate( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height, |
|||
int subX, |
|||
int subY, |
|||
int bitDepth) |
|||
{ |
|||
GetIntermediateRounding(bitDepth, out int roundBits, out int roundOffset); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector128.IsHardwareAccelerated && subX == 0 && subY == 0) |
|||
{ |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(mask); |
|||
int maskRowOffset = row * maskStride; |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe( |
|||
ref firstReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe( |
|||
ref secondReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<byte> alpha = Vector128.LoadUnsafe( |
|||
ref maskReference, |
|||
(nuint)(maskRowOffset + column)); |
|||
|
|||
BlendIntermediate( |
|||
first0, |
|||
first1, |
|||
second0, |
|||
second1, |
|||
alpha, |
|||
roundBits, |
|||
roundOffset).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int alpha = GetSubsampledMaskAlpha(mask, maskStride, row, column, subX, subY); |
|||
|
|||
// Mask blending also truncates its Q6 result because final pixel rounding is still pending. Adding
|
|||
// a half-unit here would produce a second rounding step and diverge from pinned libaom.
|
|||
int result = ((alpha * firstRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column])) >> MaskWeightBits; |
|||
result -= roundOffset; |
|||
destinationRow[column] = (byte)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, byte.MaxValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Equal-averages sixteen compound lanes and converts them to final 8-bit samples.
|
|||
/// </summary>
|
|||
private static Vector128<byte> AverageIntermediate( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector128.Narrow( |
|||
FinalizeIntermediate( |
|||
(first0 & second0) + ((first0 ^ second0) >> 1), |
|||
roundBits, |
|||
roundOffset), |
|||
FinalizeIntermediate( |
|||
(first1 & second1) + ((first1 ^ second1) >> 1), |
|||
roundBits, |
|||
roundOffset)); |
|||
|
|||
/// <summary>
|
|||
/// Distance-weights sixteen compound lanes and converts them to final 8-bit samples.
|
|||
/// </summary>
|
|||
private static Vector128<byte> DistanceWeightedIntermediate( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector128.Narrow( |
|||
DistanceWeightedIntermediate(first0, second0, firstWeight, secondWeight, roundBits, roundOffset), |
|||
DistanceWeightedIntermediate(first1, second1, firstWeight, secondWeight, roundBits, roundOffset)); |
|||
|
|||
/// <summary>
|
|||
/// Distance-weights eight compound lanes without overflowing the unsigned intermediate range.
|
|||
/// </summary>
|
|||
private static Vector128<ushort> DistanceWeightedIntermediate( |
|||
Vector128<ushort> first, |
|||
Vector128<ushort> second, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector128<int> firstLower = Vector128.WidenLower(first).AsInt32(); |
|||
Vector128<int> firstUpper = Vector128.WidenUpper(first).AsInt32(); |
|||
Vector128<int> secondLower = Vector128.WidenLower(second).AsInt32(); |
|||
Vector128<int> secondUpper = Vector128.WidenUpper(second).AsInt32(); |
|||
Vector128<int> lower = |
|||
((firstLower * firstWeight) + (secondLower * secondWeight)) >> DistanceWeightBits; |
|||
|
|||
Vector128<int> upper = |
|||
((firstUpper * firstWeight) + (secondUpper * secondWeight)) >> DistanceWeightBits; |
|||
|
|||
return Vector128.Narrow( |
|||
FinalizeIntermediate(lower, roundBits, roundOffset), |
|||
FinalizeIntermediate(upper, roundBits, roundOffset)).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates sixteen difference-weighted mask values from compound intermediates.
|
|||
/// </summary>
|
|||
private static Vector128<byte> DifferenceWeightedIntermediate( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int differenceRound, |
|||
bool invert) |
|||
=> Vector128.Narrow( |
|||
DifferenceWeightedIntermediate(first0, second0, differenceRound, invert), |
|||
DifferenceWeightedIntermediate(first1, second1, differenceRound, invert)); |
|||
|
|||
/// <summary>
|
|||
/// Creates eight difference-weighted mask values without losing the required pre-alpha rounding.
|
|||
/// </summary>
|
|||
private static Vector128<ushort> DifferenceWeightedIntermediate( |
|||
Vector128<ushort> first, |
|||
Vector128<ushort> second, |
|||
int differenceRound, |
|||
bool invert) |
|||
{ |
|||
Vector128<ushort> difference = Vector128.Max(first, second) - Vector128.Min(first, second); |
|||
Vector128<int> lower = DifferenceWeightedIntermediate( |
|||
Vector128.WidenLower(difference).AsInt32(), |
|||
differenceRound, |
|||
invert); |
|||
|
|||
Vector128<int> upper = DifferenceWeightedIntermediate( |
|||
Vector128.WidenUpper(difference).AsInt32(), |
|||
differenceRound, |
|||
invert); |
|||
|
|||
return Vector128.Narrow(lower, upper).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts four intermediate differences to the decoded type-38 mask range.
|
|||
/// </summary>
|
|||
private static Vector128<int> DifferenceWeightedIntermediate( |
|||
Vector128<int> difference, |
|||
int differenceRound, |
|||
bool invert) |
|||
{ |
|||
if (differenceRound != 0) |
|||
{ |
|||
difference = (difference + Vector128.Create(1 << (differenceRound - 1))) >> differenceRound; |
|||
} |
|||
|
|||
Vector128<int> maximum = Vector128.Create(MaximumMaskAlpha); |
|||
Vector128<int> alpha = Vector128.Min(maximum, (difference >> 4) + Vector128.Create(38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Mask-blends sixteen compound lanes and converts them to final 8-bit samples.
|
|||
/// </summary>
|
|||
private static Vector128<byte> BlendIntermediate( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
Vector128<byte> alpha, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector128.Narrow( |
|||
BlendIntermediate( |
|||
first0, |
|||
second0, |
|||
Vector128.WidenLower(alpha), |
|||
roundBits, |
|||
roundOffset), |
|||
BlendIntermediate( |
|||
first1, |
|||
second1, |
|||
Vector128.WidenUpper(alpha), |
|||
roundBits, |
|||
roundOffset)); |
|||
|
|||
/// <summary>
|
|||
/// Mask-blends eight compound lanes after widening every product to signed 32-bit precision.
|
|||
/// </summary>
|
|||
private static Vector128<ushort> BlendIntermediate( |
|||
Vector128<ushort> first, |
|||
Vector128<ushort> second, |
|||
Vector128<ushort> alpha, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector128<int> firstLower = Vector128.WidenLower(first).AsInt32(); |
|||
Vector128<int> firstUpper = Vector128.WidenUpper(first).AsInt32(); |
|||
Vector128<int> secondLower = Vector128.WidenLower(second).AsInt32(); |
|||
Vector128<int> secondUpper = Vector128.WidenUpper(second).AsInt32(); |
|||
Vector128<int> alphaLower = Vector128.WidenLower(alpha).AsInt32(); |
|||
Vector128<int> alphaUpper = Vector128.WidenUpper(alpha).AsInt32(); |
|||
Vector128<int> maximum = Vector128.Create(MaximumMaskAlpha); |
|||
Vector128<int> lower = |
|||
((alphaLower * firstLower) + ((maximum - alphaLower) * secondLower)) >> MaskWeightBits; |
|||
|
|||
Vector128<int> upper = |
|||
((alphaUpper * firstUpper) + ((maximum - alphaUpper) * secondUpper)) >> MaskWeightBits; |
|||
|
|||
return Vector128.Narrow( |
|||
FinalizeIntermediate(lower, roundBits, roundOffset), |
|||
FinalizeIntermediate(upper, roundBits, roundOffset)).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the compound bias and final fractional precision from eight unsigned lanes.
|
|||
/// </summary>
|
|||
private static Vector128<ushort> FinalizeIntermediate( |
|||
Vector128<ushort> value, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector128<short> result = (value - Vector128.Create((ushort)roundOffset)).AsInt16(); |
|||
if (roundBits != 0) |
|||
{ |
|||
result = (result + Vector128.Create((short)(1 << (roundBits - 1)))) >> roundBits; |
|||
} |
|||
|
|||
result = Vector128.Max(Vector128<short>.Zero, Vector128.Min(Vector128.Create((short)byte.MaxValue), result)); |
|||
return result.AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the compound bias and final fractional precision from four widened lanes.
|
|||
/// </summary>
|
|||
private static Vector128<int> FinalizeIntermediate( |
|||
Vector128<int> value, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector128<int> result = value - Vector128.Create(roundOffset); |
|||
if (roundBits != 0) |
|||
{ |
|||
result = (result + Vector128.Create(1 << (roundBits - 1))) >> roundBits; |
|||
} |
|||
|
|||
return Vector128.Max(Vector128<int>.Zero, Vector128.Min(Vector128.Create((int)byte.MaxValue), result)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the mask alpha for one plane sample, averaging its two or four luma samples when required.
|
|||
/// </summary>
|
|||
private static int GetSubsampledMaskAlpha( |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int row, |
|||
int column, |
|||
int subX, |
|||
int subY) |
|||
{ |
|||
int maskRow = row << subY; |
|||
int maskColumn = column << subX; |
|||
int alpha = mask[(maskRow * maskStride) + maskColumn]; |
|||
if (subX != 0) |
|||
{ |
|||
alpha += mask[(maskRow * maskStride) + maskColumn + 1]; |
|||
} |
|||
|
|||
if (subY != 0) |
|||
{ |
|||
int lowerOffset = ((maskRow + 1) * maskStride) + maskColumn; |
|||
alpha += mask[lowerOffset]; |
|||
if (subX != 0) |
|||
{ |
|||
alpha += mask[lowerOffset + 1]; |
|||
} |
|||
} |
|||
|
|||
int sampleCountShift = subX + subY; |
|||
return sampleCountShift == 0 |
|||
? alpha |
|||
: RoundPowerOfTwo(alpha, sampleCountShift); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Derives the bias and remaining fractional precision of a compound intermediate.
|
|||
/// </summary>
|
|||
private static void GetIntermediateRounding(int bitDepth, out int roundBits, out int roundOffset) |
|||
{ |
|||
int intermediateRange = bitDepth + 7 - 3 + 2; |
|||
int round0 = 3 + Math.Max(intermediateRange - 16, 0); |
|||
int offsetBits = bitDepth + 14 - round0; |
|||
roundBits = 14 - round0 - Av1InterPredictor.CompoundRound1Bits; |
|||
roundOffset = (1 << (offsetBits - Av1InterPredictor.CompoundRound1Bits)) + |
|||
(1 << (offsetBits - Av1InterPredictor.CompoundRound1Bits - 1)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies AV1's positive power-of-two rounding rule.
|
|||
/// </summary>
|
|||
private static int RoundPowerOfTwo(int value, int bits) |
|||
=> bits == 0 ? value : (value + (1 << (bits - 1))) >> bits; |
|||
} |
|||
@ -1,294 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Produces the smooth inter-intra and predictor-difference masks used by compound blending.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundInterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Gets libaom's one-dimensional inter-intra alpha curve.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> InterIntraWeights => |
|||
[ |
|||
60, 58, 56, 54, 52, 50, 48, 47, 45, 44, 42, 41, 39, 38, 37, 35, |
|||
34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 22, 21, 20, |
|||
19, 19, 18, 18, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 12, |
|||
11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, |
|||
6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, |
|||
4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, |
|||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, |
|||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Fills a smooth inter-intra mask for one plane.
|
|||
/// </summary>
|
|||
public static void FillInterIntraMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height, |
|||
Av1InterIntraMode mode, |
|||
bool invert) |
|||
{ |
|||
int sizeScale = 128 / Math.Max(width, height); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int alpha = mode switch |
|||
{ |
|||
Av1InterIntraMode.Vertical => InterIntraWeights[row * sizeScale], |
|||
Av1InterIntraMode.Horizontal => InterIntraWeights[column * sizeScale], |
|||
Av1InterIntraMode.Smooth => InterIntraWeights[Math.Min(row, column) * sizeScale], |
|||
_ => 32, |
|||
}; |
|||
|
|||
maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills an 8-bit difference-weighted compound mask.
|
|||
/// </summary>
|
|||
public static void FillDifferenceWeightedMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<byte> first, |
|||
int firstStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
{ |
|||
bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ReadOnlySpan<byte> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
ref byte firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int difference = Math.Abs(firstRow[column] - secondRow[column]) >> 4; |
|||
int alpha = Math.Min(MaximumMaskAlpha, 38 + difference); |
|||
maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills a high-bit-depth difference-weighted compound mask.
|
|||
/// </summary>
|
|||
public static void FillDifferenceWeightedMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
{ |
|||
bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; |
|||
int differenceShift = bitDepth - 8 + 4; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
// Two input vectors narrow to one packed byte mask. This keeps mask construction contiguous and avoids
|
|||
// temporary buffers before the following vector blend consumes the complete plane block.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<ushort> first0 = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<ushort> first1 = Vector512.LoadUnsafe(ref firstReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
Vector512<ushort> second0 = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> second1 = Vector512.LoadUnsafe(ref secondReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<ushort> first0 = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<ushort> first1 = Vector256.LoadUnsafe(ref firstReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
Vector256<ushort> second0 = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> second1 = Vector256.LoadUnsafe(ref secondReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe(ref firstReference, (nuint)(column + Vector128<ushort>.Count)); |
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe(ref secondReference, (nuint)(column + Vector128<ushort>.Count)); |
|||
DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int difference = Math.Abs(firstRow[column] - secondRow[column]) >> differenceShift; |
|||
int alpha = Math.Min(MaximumMaskAlpha, 38 + difference); |
|||
maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<byte> DifferenceWeighted(Vector128<byte> first, Vector128<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector128<byte> difference = Vector128.Max(first, second) - Vector128.Min(first, second); |
|||
Vector128<ushort> lower = DifferenceWeightedAlpha(Vector128.WidenLower(difference), shift, invert); |
|||
Vector128<ushort> upper = DifferenceWeightedAlpha(Vector128.WidenUpper(difference), shift, invert); |
|||
return Vector128.Narrow(lower, upper); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<byte> DifferenceWeighted(Vector256<byte> first, Vector256<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector256<byte> difference = Vector256.Max(first, second) - Vector256.Min(first, second); |
|||
Vector256<ushort> lower = DifferenceWeightedAlpha(Vector256.WidenLower(difference), shift, invert); |
|||
Vector256<ushort> upper = DifferenceWeightedAlpha(Vector256.WidenUpper(difference), shift, invert); |
|||
return Vector256.Narrow(lower, upper); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<byte> DifferenceWeighted(Vector512<byte> first, Vector512<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector512<byte> difference = Vector512.Max(first, second) - Vector512.Min(first, second); |
|||
Vector512<ushort> lower = DifferenceWeightedAlpha(Vector512.WidenLower(difference), shift, invert); |
|||
Vector512<ushort> upper = DifferenceWeightedAlpha(Vector512.WidenUpper(difference), shift, invert); |
|||
return Vector512.Narrow(lower, upper); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<byte> DifferenceWeighted( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector128.Narrow( |
|||
DifferenceWeightedAlpha(Vector128.Max(first0, second0) - Vector128.Min(first0, second0), shift, invert), |
|||
DifferenceWeightedAlpha(Vector128.Max(first1, second1) - Vector128.Min(first1, second1), shift, invert)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<byte> DifferenceWeighted( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector256.Narrow( |
|||
DifferenceWeightedAlpha(Vector256.Max(first0, second0) - Vector256.Min(first0, second0), shift, invert), |
|||
DifferenceWeightedAlpha(Vector256.Max(first1, second1) - Vector256.Min(first1, second1), shift, invert)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<byte> DifferenceWeighted( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector512.Narrow( |
|||
DifferenceWeightedAlpha(Vector512.Max(first0, second0) - Vector512.Min(first0, second0), shift, invert), |
|||
DifferenceWeightedAlpha(Vector512.Max(first1, second1) - Vector512.Min(first1, second1), shift, invert)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> DifferenceWeightedAlpha(Vector128<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector128<ushort> maximum = Vector128.Create((ushort)MaximumMaskAlpha); |
|||
Vector128<ushort> alpha = Vector128.Min(maximum, (difference >> shift) + Vector128.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> DifferenceWeightedAlpha(Vector256<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector256<ushort> maximum = Vector256.Create((ushort)MaximumMaskAlpha); |
|||
Vector256<ushort> alpha = Vector256.Min(maximum, (difference >> shift) + Vector256.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> DifferenceWeightedAlpha(Vector512<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector512<ushort> maximum = Vector512.Create((ushort)MaximumMaskAlpha); |
|||
Vector512<ushort> alpha = Vector512.Min(maximum, (difference >> shift) + Vector512.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
} |
|||
@ -0,0 +1,344 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines biased compound-prediction conversion arithmetic.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundInterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines biased compound-prediction conversion for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1CompoundPredictionOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Converts one integer-position sample to the compound intermediate representation.
|
|||
/// </summary>
|
|||
/// <param name="sample">The source sample.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The biased compound intermediate.</returns>
|
|||
public static abstract ushort Copy(byte sample, int roundBits, int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Converts 128 bits of integer-position samples to compound intermediates.
|
|||
/// </summary>
|
|||
/// <param name="samples">The source samples.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <param name="lower">Receives the lower widened intermediates.</param>
|
|||
/// <param name="upper">Receives the upper widened intermediates.</param>
|
|||
public static abstract void Copy( |
|||
Vector128<byte> samples, |
|||
int roundBits, |
|||
int roundOffset, |
|||
out Vector128<ushort> lower, |
|||
out Vector128<ushort> upper); |
|||
|
|||
/// <summary>
|
|||
/// Converts 256 bits of integer-position samples to compound intermediates.
|
|||
/// </summary>
|
|||
/// <param name="samples">The source samples.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <param name="lower">Receives the lower widened intermediates.</param>
|
|||
/// <param name="upper">Receives the upper widened intermediates.</param>
|
|||
public static abstract void Copy( |
|||
Vector256<byte> samples, |
|||
int roundBits, |
|||
int roundOffset, |
|||
out Vector256<ushort> lower, |
|||
out Vector256<ushort> upper); |
|||
|
|||
/// <summary>
|
|||
/// Converts 512 bits of integer-position samples to compound intermediates.
|
|||
/// </summary>
|
|||
/// <param name="samples">The source samples.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <param name="lower">Receives the lower widened intermediates.</param>
|
|||
/// <param name="upper">Receives the upper widened intermediates.</param>
|
|||
public static abstract void Copy( |
|||
Vector512<byte> samples, |
|||
int roundBits, |
|||
int roundOffset, |
|||
out Vector512<ushort> lower, |
|||
out Vector512<ushort> upper); |
|||
|
|||
/// <summary>
|
|||
/// Applies direct-filter rounding and bias to one convolution result.
|
|||
/// </summary>
|
|||
/// <param name="result">The convolution result.</param>
|
|||
/// <param name="preShift">The shift applied before rounding.</param>
|
|||
/// <param name="round">The rounding shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The biased compound intermediate.</returns>
|
|||
public static abstract ushort PrepareDirect(int result, int preShift, int round, int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Applies direct-filter rounding and bias to 128-bit widened convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <param name="preShift">The shift applied before rounding.</param>
|
|||
/// <param name="round">The rounding shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The biased compound intermediates.</returns>
|
|||
public static abstract Vector128<ushort> PrepareDirect( |
|||
Vector128<int> lower, |
|||
Vector128<int> upper, |
|||
int preShift, |
|||
int round, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Applies direct-filter rounding and bias to 256-bit widened convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <param name="preShift">The shift applied before rounding.</param>
|
|||
/// <param name="round">The rounding shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The biased compound intermediates.</returns>
|
|||
public static abstract Vector256<ushort> PrepareDirect( |
|||
Vector256<int> lower, |
|||
Vector256<int> upper, |
|||
int preShift, |
|||
int round, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Applies direct-filter rounding and bias to 512-bit widened convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <param name="preShift">The shift applied before rounding.</param>
|
|||
/// <param name="round">The rounding shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The biased compound intermediates.</returns>
|
|||
public static abstract Vector512<ushort> PrepareDirect( |
|||
Vector512<int> lower, |
|||
Vector512<int> upper, |
|||
int preShift, |
|||
int round, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Applies first-pass compound rounding to one biased horizontal convolution result.
|
|||
/// </summary>
|
|||
/// <param name="result">The biased horizontal convolution result.</param>
|
|||
/// <returns>The rounded intermediate.</returns>
|
|||
public static abstract short PrepareHorizontal(int result); |
|||
|
|||
/// <summary>
|
|||
/// Applies first-pass compound rounding to 128-bit widened horizontal convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <returns>The rounded intermediates.</returns>
|
|||
public static abstract Vector128<short> PrepareHorizontal(Vector128<int> lower, Vector128<int> upper); |
|||
|
|||
/// <summary>
|
|||
/// Applies first-pass compound rounding to 256-bit widened horizontal convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <returns>The rounded intermediates.</returns>
|
|||
public static abstract Vector256<short> PrepareHorizontal(Vector256<int> lower, Vector256<int> upper); |
|||
|
|||
/// <summary>
|
|||
/// Applies first-pass compound rounding to 512-bit widened horizontal convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <returns>The rounded intermediates.</returns>
|
|||
public static abstract Vector512<short> PrepareHorizontal(Vector512<int> lower, Vector512<int> upper); |
|||
|
|||
/// <summary>
|
|||
/// Applies second-pass compound rounding to one biased vertical convolution result.
|
|||
/// </summary>
|
|||
/// <param name="result">The biased vertical convolution result.</param>
|
|||
/// <returns>The compound intermediate.</returns>
|
|||
public static abstract ushort PrepareVertical(int result); |
|||
|
|||
/// <summary>
|
|||
/// Applies second-pass compound rounding to 128-bit widened vertical convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <returns>The compound intermediates.</returns>
|
|||
public static abstract Vector128<ushort> PrepareVertical(Vector128<int> lower, Vector128<int> upper); |
|||
|
|||
/// <summary>
|
|||
/// Applies second-pass compound rounding to 256-bit widened vertical convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <returns>The compound intermediates.</returns>
|
|||
public static abstract Vector256<ushort> PrepareVertical(Vector256<int> lower, Vector256<int> upper); |
|||
|
|||
/// <summary>
|
|||
/// Applies second-pass compound rounding to 512-bit widened vertical convolution results.
|
|||
/// </summary>
|
|||
/// <param name="lower">The lower convolution results.</param>
|
|||
/// <param name="upper">The upper convolution results.</param>
|
|||
/// <returns>The compound intermediates.</returns>
|
|||
public static abstract Vector512<ushort> PrepareVertical(Vector512<int> lower, Vector512<int> upper); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements AV1 compound-prediction conversion for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct CompoundPredictionOperator : IAv1CompoundPredictionOperator |
|||
{ |
|||
private const int HorizontalBias = 1 << (8 + FilterBits - 1); |
|||
private const int VerticalBias = 1 << (8 + (2 * FilterBits) - Round0Bits); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static ushort Copy(byte sample, int roundBits, int roundOffset) |
|||
=> (ushort)((sample << roundBits) + roundOffset); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Copy( |
|||
Vector128<byte> samples, |
|||
int roundBits, |
|||
int roundOffset, |
|||
out Vector128<ushort> lower, |
|||
out Vector128<ushort> upper) |
|||
{ |
|||
Vector128<ushort> offset = Vector128.Create((ushort)roundOffset); |
|||
lower = (Vector128.WidenLower(samples) << roundBits) + offset; |
|||
upper = (Vector128.WidenUpper(samples) << roundBits) + offset; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Copy( |
|||
Vector256<byte> samples, |
|||
int roundBits, |
|||
int roundOffset, |
|||
out Vector256<ushort> lower, |
|||
out Vector256<ushort> upper) |
|||
{ |
|||
Vector256<ushort> offset = Vector256.Create((ushort)roundOffset); |
|||
lower = (Vector256.WidenLower(samples) << roundBits) + offset; |
|||
upper = (Vector256.WidenUpper(samples) << roundBits) + offset; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Copy( |
|||
Vector512<byte> samples, |
|||
int roundBits, |
|||
int roundOffset, |
|||
out Vector512<ushort> lower, |
|||
out Vector512<ushort> upper) |
|||
{ |
|||
Vector512<ushort> offset = Vector512.Create((ushort)roundOffset); |
|||
lower = (Vector512.WidenLower(samples) << roundBits) + offset; |
|||
upper = (Vector512.WidenUpper(samples) << roundBits) + offset; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static ushort PrepareDirect(int result, int preShift, int round, int roundOffset) |
|||
=> (ushort)(RoundPowerOfTwo(result << preShift, round) + roundOffset); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<ushort> PrepareDirect( |
|||
Vector128<int> lower, |
|||
Vector128<int> upper, |
|||
int preShift, |
|||
int round, |
|||
int roundOffset) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower << preShift, round) + Vector128.Create(roundOffset), |
|||
RoundPowerOfTwo(upper << preShift, round) + Vector128.Create(roundOffset)).AsUInt16(); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<ushort> PrepareDirect( |
|||
Vector256<int> lower, |
|||
Vector256<int> upper, |
|||
int preShift, |
|||
int round, |
|||
int roundOffset) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower << preShift, round) + Vector256.Create(roundOffset), |
|||
RoundPowerOfTwo(upper << preShift, round) + Vector256.Create(roundOffset)).AsUInt16(); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<ushort> PrepareDirect( |
|||
Vector512<int> lower, |
|||
Vector512<int> upper, |
|||
int preShift, |
|||
int round, |
|||
int roundOffset) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower << preShift, round) + Vector512.Create(roundOffset), |
|||
RoundPowerOfTwo(upper << preShift, round) + Vector512.Create(roundOffset)).AsUInt16(); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static short PrepareHorizontal(int result) |
|||
=> (short)RoundPowerOfTwo(HorizontalBias + result, Round0Bits); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<short> PrepareHorizontal(Vector128<int> lower, Vector128<int> upper) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower + Vector128.Create(HorizontalBias), Round0Bits), |
|||
RoundPowerOfTwo(upper + Vector128.Create(HorizontalBias), Round0Bits)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<short> PrepareHorizontal(Vector256<int> lower, Vector256<int> upper) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower + Vector256.Create(HorizontalBias), Round0Bits), |
|||
RoundPowerOfTwo(upper + Vector256.Create(HorizontalBias), Round0Bits)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<short> PrepareHorizontal(Vector512<int> lower, Vector512<int> upper) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower + Vector512.Create(HorizontalBias), Round0Bits), |
|||
RoundPowerOfTwo(upper + Vector512.Create(HorizontalBias), Round0Bits)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static ushort PrepareVertical(int result) |
|||
=> (ushort)RoundPowerOfTwo(VerticalBias + result, CompoundRound1Bits); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<ushort> PrepareVertical(Vector128<int> lower, Vector128<int> upper) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower + Vector128.Create(VerticalBias), CompoundRound1Bits), |
|||
RoundPowerOfTwo(upper + Vector128.Create(VerticalBias), CompoundRound1Bits)).AsUInt16(); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<ushort> PrepareVertical(Vector256<int> lower, Vector256<int> upper) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower + Vector256.Create(VerticalBias), CompoundRound1Bits), |
|||
RoundPowerOfTwo(upper + Vector256.Create(VerticalBias), CompoundRound1Bits)).AsUInt16(); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<ushort> PrepareVertical(Vector512<int> lower, Vector512<int> upper) |
|||
=> Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(lower + Vector512.Create(VerticalBias), CompoundRound1Bits), |
|||
RoundPowerOfTwo(upper + Vector512.Create(VerticalBias), CompoundRound1Bits)).AsUInt16(); |
|||
} |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines final equal-average compound-intermediate reconstruction.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundIntermediateAveragePredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines equal-average finalization for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1CompoundIntermediateAverageOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Equal-averages and finalizes one pair of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first compound intermediate.</param>
|
|||
/// <param name="second">The second compound intermediate.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed sample.</returns>
|
|||
public static abstract byte Average(ushort first, ushort second, int roundBits, int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Equal-averages and finalizes 128 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector128<byte> Average( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int roundBits, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Equal-averages and finalizes 256 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector256<byte> Average( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int roundBits, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Equal-averages and finalizes 512 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector512<byte> Average( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int roundBits, |
|||
int roundOffset); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements equal-average finalization for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct CompoundIntermediateAverageOperator : IAv1CompoundIntermediateAverageOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Average(ushort first, ushort second, int roundBits, int roundOffset) |
|||
{ |
|||
// The reference average deliberately truncates here. Finalization performs the sole rounding step.
|
|||
int result = ((first + second) >> 1) - roundOffset; |
|||
return (byte)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, byte.MaxValue); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Average( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector128.Narrow( |
|||
FinalizeIntermediate((first0 & second0) + ((first0 ^ second0) >> 1), roundBits, roundOffset), |
|||
FinalizeIntermediate((first1 & second1) + ((first1 ^ second1) >> 1), roundBits, roundOffset)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Average( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector256.Narrow( |
|||
FinalizeIntermediate((first0 & second0) + ((first0 ^ second0) >> 1), roundBits, roundOffset), |
|||
FinalizeIntermediate((first1 & second1) + ((first1 ^ second1) >> 1), roundBits, roundOffset)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Average( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector512.Narrow( |
|||
FinalizeIntermediate((first0 & second0) + ((first0 ^ second0) >> 1), roundBits, roundOffset), |
|||
FinalizeIntermediate((first1 & second1) + ((first1 ^ second1) >> 1), roundBits, roundOffset)); |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Reconstructs final samples by equal-averaging compound intermediates.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundIntermediateAveragePredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Combines two compound intermediates by equal averaging.
|
|||
/// </summary>
|
|||
public static void AverageIntermediate( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth) |
|||
=> AverageIntermediate<CompoundIntermediateAverageOperator>( |
|||
destination, |
|||
destinationStride, |
|||
first, |
|||
firstStride, |
|||
second, |
|||
secondStride, |
|||
width, |
|||
height, |
|||
bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed equal-average compound-intermediate operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound-intermediate operator.</typeparam>
|
|||
private static void AverageIntermediate<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth) |
|||
where TOperator : struct, IAv1CompoundIntermediateAverageOperator |
|||
{ |
|||
GetIntermediateRounding(bitDepth, out int roundBits, out int roundOffset); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<ushort> first0 = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<ushort> first1 = Vector512.LoadUnsafe(ref firstReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
Vector512<ushort> second0 = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> second1 = Vector512.LoadUnsafe(ref secondReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
TOperator.Average(first0, first1, second0, second1, roundBits, roundOffset) |
|||
.StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<ushort> first0 = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<ushort> first1 = Vector256.LoadUnsafe(ref firstReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
Vector256<ushort> second0 = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> second1 = Vector256.LoadUnsafe(ref secondReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
TOperator.Average(first0, first1, second0, second1, roundBits, roundOffset) |
|||
.StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe( |
|||
ref firstReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe( |
|||
ref secondReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
TOperator.Average(first0, first1, second0, second1, roundBits, roundOffset).StoreUnsafe( |
|||
ref destinationReference, |
|||
(nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Average(firstRow[column], secondRow[column], roundBits, roundOffset); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,218 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines difference-weighted mask generation from compound intermediates.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundIntermediateDifferenceWeightedMaskBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Defines difference-weighted mask generation for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1CompoundIntermediateDifferenceWeightedMaskOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Creates one difference-weighted mask value from compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first compound intermediate.</param>
|
|||
/// <param name="second">The second compound intermediate.</param>
|
|||
/// <param name="differenceRound">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The AV1 mask value.</returns>
|
|||
public static abstract byte CreateMask(ushort first, ushort second, int differenceRound, bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 128 bits of difference-weighted mask values from compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="differenceRound">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The packed AV1 mask values.</returns>
|
|||
public static abstract Vector128<byte> CreateMask( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int differenceRound, |
|||
bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 256 bits of difference-weighted mask values from compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="differenceRound">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The packed AV1 mask values.</returns>
|
|||
public static abstract Vector256<byte> CreateMask( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int differenceRound, |
|||
bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 512 bits of difference-weighted mask values from compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="differenceRound">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The packed AV1 mask values.</returns>
|
|||
public static abstract Vector512<byte> CreateMask( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int differenceRound, |
|||
bool invert); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements difference-weighted mask generation for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct CompoundIntermediateDifferenceWeightedMaskOperator : IAv1CompoundIntermediateDifferenceWeightedMaskOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte CreateMask(ushort first, ushort second, int differenceRound, bool invert) |
|||
{ |
|||
int difference = RoundPowerOfTwo(Math.Abs(first - second), differenceRound); |
|||
int alpha = Math.Min(MaximumMaskAlpha, 38 + (difference >> 4)); |
|||
return (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> CreateMask( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int differenceRound, |
|||
bool invert) |
|||
=> Vector128.Narrow( |
|||
CreateMask(first0, second0, differenceRound, invert), |
|||
CreateMask(first1, second1, differenceRound, invert)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> CreateMask( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int differenceRound, |
|||
bool invert) |
|||
=> Vector256.Narrow( |
|||
CreateMask(first0, second0, differenceRound, invert), |
|||
CreateMask(first1, second1, differenceRound, invert)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> CreateMask( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int differenceRound, |
|||
bool invert) |
|||
=> Vector512.Narrow( |
|||
CreateMask(first0, second0, differenceRound, invert), |
|||
CreateMask(first1, second1, differenceRound, invert)); |
|||
|
|||
/// <summary>
|
|||
/// Creates 128-bit unpacked mask values without losing the required pre-alpha rounding.
|
|||
/// </summary>
|
|||
private static Vector128<ushort> CreateMask(Vector128<ushort> first, Vector128<ushort> second, int differenceRound, bool invert) |
|||
{ |
|||
Vector128<ushort> difference = Vector128.Max(first, second) - Vector128.Min(first, second); |
|||
Vector128<int> lower = CreateMaskAlpha(Vector128.WidenLower(difference).AsInt32(), differenceRound, invert); |
|||
Vector128<int> upper = CreateMaskAlpha(Vector128.WidenUpper(difference).AsInt32(), differenceRound, invert); |
|||
return Vector128.Narrow(lower, upper).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates 256-bit unpacked mask values without losing the required pre-alpha rounding.
|
|||
/// </summary>
|
|||
private static Vector256<ushort> CreateMask(Vector256<ushort> first, Vector256<ushort> second, int differenceRound, bool invert) |
|||
{ |
|||
Vector256<ushort> difference = Vector256.Max(first, second) - Vector256.Min(first, second); |
|||
Vector256<int> lower = CreateMaskAlpha(Vector256.WidenLower(difference).AsInt32(), differenceRound, invert); |
|||
Vector256<int> upper = CreateMaskAlpha(Vector256.WidenUpper(difference).AsInt32(), differenceRound, invert); |
|||
return Vector256.Narrow(lower, upper).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates 512-bit unpacked mask values without losing the required pre-alpha rounding.
|
|||
/// </summary>
|
|||
private static Vector512<ushort> CreateMask(Vector512<ushort> first, Vector512<ushort> second, int differenceRound, bool invert) |
|||
{ |
|||
Vector512<ushort> difference = Vector512.Max(first, second) - Vector512.Min(first, second); |
|||
Vector512<int> lower = CreateMaskAlpha(Vector512.WidenLower(difference).AsInt32(), differenceRound, invert); |
|||
Vector512<int> upper = CreateMaskAlpha(Vector512.WidenUpper(difference).AsInt32(), differenceRound, invert); |
|||
return Vector512.Narrow(lower, upper).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts 128-bit intermediate differences to the decoded type-38 mask range.
|
|||
/// </summary>
|
|||
private static Vector128<int> CreateMaskAlpha(Vector128<int> difference, int differenceRound, bool invert) |
|||
{ |
|||
if (differenceRound != 0) |
|||
{ |
|||
difference = (difference + Vector128.Create(1 << (differenceRound - 1))) >> differenceRound; |
|||
} |
|||
|
|||
Vector128<int> maximum = Vector128.Create(MaximumMaskAlpha); |
|||
Vector128<int> alpha = Vector128.Min(maximum, (difference >> 4) + Vector128.Create(38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts 256-bit intermediate differences to the decoded type-38 mask range.
|
|||
/// </summary>
|
|||
private static Vector256<int> CreateMaskAlpha(Vector256<int> difference, int differenceRound, bool invert) |
|||
{ |
|||
if (differenceRound != 0) |
|||
{ |
|||
difference = (difference + Vector256.Create(1 << (differenceRound - 1))) >> differenceRound; |
|||
} |
|||
|
|||
Vector256<int> maximum = Vector256.Create(MaximumMaskAlpha); |
|||
Vector256<int> alpha = Vector256.Min(maximum, (difference >> 4) + Vector256.Create(38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts 512-bit intermediate differences to the decoded type-38 mask range.
|
|||
/// </summary>
|
|||
private static Vector512<int> CreateMaskAlpha(Vector512<int> difference, int differenceRound, bool invert) |
|||
{ |
|||
if (differenceRound != 0) |
|||
{ |
|||
difference = (difference + Vector512.Create(1 << (differenceRound - 1))) >> differenceRound; |
|||
} |
|||
|
|||
Vector512<int> maximum = Vector512.Create(MaximumMaskAlpha); |
|||
Vector512<int> alpha = Vector512.Min(maximum, (difference >> 4) + Vector512.Create(38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Builds difference-weighted masks from compound intermediates.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundIntermediateDifferenceWeightedMaskBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Fills a luma-resolution difference-weighted mask from compound intermediates.
|
|||
/// </summary>
|
|||
public static void FillDifferenceWeightedIntermediateMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
=> FillDifferenceWeightedIntermediateMask<CompoundIntermediateDifferenceWeightedMaskOperator>( |
|||
mask, |
|||
maskStride, |
|||
first, |
|||
firstStride, |
|||
second, |
|||
secondStride, |
|||
width, |
|||
height, |
|||
bitDepth, |
|||
maskType); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed difference-mask compound-intermediate operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound-intermediate operator.</typeparam>
|
|||
private static void FillDifferenceWeightedIntermediateMask<TOperator>( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
where TOperator : struct, IAv1CompoundIntermediateDifferenceWeightedMaskOperator |
|||
{ |
|||
bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; |
|||
GetIntermediateRounding(bitDepth, out int roundBits, out _); |
|||
int differenceRound = roundBits + bitDepth - 8; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<ushort> first0 = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<ushort> first1 = Vector512.LoadUnsafe(ref firstReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
Vector512<ushort> second0 = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> second1 = Vector512.LoadUnsafe(ref secondReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
TOperator.CreateMask(first0, first1, second0, second1, differenceRound, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<ushort> first0 = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<ushort> first1 = Vector256.LoadUnsafe(ref firstReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
Vector256<ushort> second0 = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> second1 = Vector256.LoadUnsafe(ref secondReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
TOperator.CreateMask(first0, first1, second0, second1, differenceRound, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe( |
|||
ref firstReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe( |
|||
ref secondReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
TOperator.CreateMask( |
|||
first0, |
|||
first1, |
|||
second0, |
|||
second1, |
|||
differenceRound, |
|||
invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
maskRow[column] = TOperator.CreateMask(firstRow[column], secondRow[column], differenceRound, invert); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,232 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines final distance-weighted compound-intermediate reconstruction.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines distance-weighted finalization for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1CompoundIntermediateDistanceWeightedOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Distance-weights and finalizes one pair of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first compound intermediate.</param>
|
|||
/// <param name="second">The second compound intermediate.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed sample.</returns>
|
|||
public static abstract byte DistanceWeighted( |
|||
ushort first, |
|||
ushort second, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Distance-weights and finalizes 128 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector128<byte> DistanceWeighted( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Distance-weights and finalizes 256 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector256<byte> DistanceWeighted( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Distance-weights and finalizes 512 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="firstWeight">The first predictor weight.</param>
|
|||
/// <param name="secondWeight">The second predictor weight.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector512<byte> DistanceWeighted( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements distance-weighted finalization for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct CompoundIntermediateDistanceWeightedOperator : IAv1CompoundIntermediateDistanceWeightedOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte DistanceWeighted( |
|||
ushort first, |
|||
ushort second, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
int result = ((first * firstWeight) + (second * secondWeight)) >> DistanceWeightBits; |
|||
result -= roundOffset; |
|||
return (byte)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, byte.MaxValue); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> DistanceWeighted( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector128.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight, roundBits, roundOffset), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight, roundBits, roundOffset)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> DistanceWeighted( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector256.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight, roundBits, roundOffset), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight, roundBits, roundOffset)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> DistanceWeighted( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector512.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight, roundBits, roundOffset), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight, roundBits, roundOffset)); |
|||
|
|||
/// <summary>
|
|||
/// Distance-weights 128-bit lanes without overflowing the unsigned intermediate range.
|
|||
/// </summary>
|
|||
private static Vector128<ushort> DistanceWeighted( |
|||
Vector128<ushort> first, |
|||
Vector128<ushort> second, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector128<int> firstLower = Vector128.WidenLower(first).AsInt32(); |
|||
Vector128<int> firstUpper = Vector128.WidenUpper(first).AsInt32(); |
|||
Vector128<int> secondLower = Vector128.WidenLower(second).AsInt32(); |
|||
Vector128<int> secondUpper = Vector128.WidenUpper(second).AsInt32(); |
|||
Vector128<int> lower = ((firstLower * firstWeight) + (secondLower * secondWeight)) >> DistanceWeightBits; |
|||
Vector128<int> upper = ((firstUpper * firstWeight) + (secondUpper * secondWeight)) >> DistanceWeightBits; |
|||
return Vector128.Narrow(FinalizeIntermediate(lower, roundBits, roundOffset), FinalizeIntermediate(upper, roundBits, roundOffset)).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Distance-weights 256-bit lanes without overflowing the unsigned intermediate range.
|
|||
/// </summary>
|
|||
private static Vector256<ushort> DistanceWeighted( |
|||
Vector256<ushort> first, |
|||
Vector256<ushort> second, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector256<int> firstLower = Vector256.WidenLower(first).AsInt32(); |
|||
Vector256<int> firstUpper = Vector256.WidenUpper(first).AsInt32(); |
|||
Vector256<int> secondLower = Vector256.WidenLower(second).AsInt32(); |
|||
Vector256<int> secondUpper = Vector256.WidenUpper(second).AsInt32(); |
|||
Vector256<int> lower = ((firstLower * firstWeight) + (secondLower * secondWeight)) >> DistanceWeightBits; |
|||
Vector256<int> upper = ((firstUpper * firstWeight) + (secondUpper * secondWeight)) >> DistanceWeightBits; |
|||
return Vector256.Narrow(FinalizeIntermediate(lower, roundBits, roundOffset), FinalizeIntermediate(upper, roundBits, roundOffset)).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Distance-weights 512-bit lanes without overflowing the unsigned intermediate range.
|
|||
/// </summary>
|
|||
private static Vector512<ushort> DistanceWeighted( |
|||
Vector512<ushort> first, |
|||
Vector512<ushort> second, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector512<int> firstLower = Vector512.WidenLower(first).AsInt32(); |
|||
Vector512<int> firstUpper = Vector512.WidenUpper(first).AsInt32(); |
|||
Vector512<int> secondLower = Vector512.WidenLower(second).AsInt32(); |
|||
Vector512<int> secondUpper = Vector512.WidenUpper(second).AsInt32(); |
|||
Vector512<int> lower = ((firstLower * firstWeight) + (secondLower * secondWeight)) >> DistanceWeightBits; |
|||
Vector512<int> upper = ((firstUpper * firstWeight) + (secondUpper * secondWeight)) >> DistanceWeightBits; |
|||
return Vector512.Narrow(FinalizeIntermediate(lower, roundBits, roundOffset), FinalizeIntermediate(upper, roundBits, roundOffset)).AsUInt16(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,156 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Reconstructs final samples by distance-weighting compound intermediates.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundIntermediateDistanceWeightedPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Combines two compound intermediates using the decoded temporal-distance weights.
|
|||
/// </summary>
|
|||
public static void DistanceWeightedIntermediate( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int bitDepth) |
|||
=> DistanceWeightedIntermediate<CompoundIntermediateDistanceWeightedOperator>( |
|||
destination, |
|||
destinationStride, |
|||
first, |
|||
firstStride, |
|||
second, |
|||
secondStride, |
|||
width, |
|||
height, |
|||
firstWeight, |
|||
secondWeight, |
|||
bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed distance-weighted compound-intermediate operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound-intermediate operator.</typeparam>
|
|||
private static void DistanceWeightedIntermediate<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight, |
|||
int bitDepth) |
|||
where TOperator : struct, IAv1CompoundIntermediateDistanceWeightedOperator |
|||
{ |
|||
GetIntermediateRounding(bitDepth, out int roundBits, out int roundOffset); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<ushort> first0 = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<ushort> first1 = Vector512.LoadUnsafe(ref firstReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
Vector512<ushort> second0 = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> second1 = Vector512.LoadUnsafe(ref secondReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
TOperator.DistanceWeighted( |
|||
first0, |
|||
first1, |
|||
second0, |
|||
second1, |
|||
firstWeight, |
|||
secondWeight, |
|||
roundBits, |
|||
roundOffset).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<ushort> first0 = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<ushort> first1 = Vector256.LoadUnsafe(ref firstReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
Vector256<ushort> second0 = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> second1 = Vector256.LoadUnsafe(ref secondReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
TOperator.DistanceWeighted( |
|||
first0, |
|||
first1, |
|||
second0, |
|||
second1, |
|||
firstWeight, |
|||
secondWeight, |
|||
roundBits, |
|||
roundOffset).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe( |
|||
ref firstReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe( |
|||
ref secondReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
TOperator.DistanceWeighted( |
|||
first0, |
|||
first1, |
|||
second0, |
|||
second1, |
|||
firstWeight, |
|||
secondWeight, |
|||
roundBits, |
|||
roundOffset).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.DistanceWeighted( |
|||
firstRow[column], |
|||
secondRow[column], |
|||
firstWeight, |
|||
secondWeight, |
|||
roundBits, |
|||
roundOffset); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines final masked compound-intermediate reconstruction.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundIntermediateMaskBlendPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines masked compound-intermediate finalization for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1CompoundIntermediateMaskBlendOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Alpha-blends and finalizes one pair of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first compound intermediate.</param>
|
|||
/// <param name="second">The second compound intermediate.</param>
|
|||
/// <param name="alpha">The first-predictor weight in the AV1 mask range.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed sample.</returns>
|
|||
public static abstract byte Blend(ushort first, ushort second, byte alpha, int roundBits, int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Alpha-blends and finalizes 128 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="alpha">The first-predictor weights in the AV1 mask range.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector128<byte> Blend( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
Vector128<byte> alpha, |
|||
int roundBits, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Alpha-blends and finalizes 256 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="alpha">The first-predictor weights in the AV1 mask range.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector256<byte> Blend( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
Vector256<byte> alpha, |
|||
int roundBits, |
|||
int roundOffset); |
|||
|
|||
/// <summary>
|
|||
/// Alpha-blends and finalizes 512 bits of compound intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor intermediates.</param>
|
|||
/// <param name="first1">The upper first-predictor intermediates.</param>
|
|||
/// <param name="second0">The lower second-predictor intermediates.</param>
|
|||
/// <param name="second1">The upper second-predictor intermediates.</param>
|
|||
/// <param name="alpha">The first-predictor weights in the AV1 mask range.</param>
|
|||
/// <param name="roundBits">The final reconstruction shift.</param>
|
|||
/// <param name="roundOffset">The compound intermediate bias.</param>
|
|||
/// <returns>The reconstructed samples.</returns>
|
|||
public static abstract Vector512<byte> Blend( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
Vector512<byte> alpha, |
|||
int roundBits, |
|||
int roundOffset); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements masked compound-intermediate finalization for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct CompoundIntermediateMaskBlendOperator : IAv1CompoundIntermediateMaskBlendOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Blend(ushort first, ushort second, byte alpha, int roundBits, int roundOffset) |
|||
{ |
|||
// The Q6 blend truncates because final pixel rounding is still pending after bias removal.
|
|||
int result = ((alpha * first) + ((MaximumMaskAlpha - alpha) * second)) >> MaskWeightBits; |
|||
result -= roundOffset; |
|||
return (byte)Math.Clamp(RoundPowerOfTwo(result, roundBits), 0, byte.MaxValue); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Blend( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
Vector128<byte> alpha, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector128.Narrow( |
|||
Blend(first0, second0, Vector128.WidenLower(alpha), roundBits, roundOffset), |
|||
Blend(first1, second1, Vector128.WidenUpper(alpha), roundBits, roundOffset)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Blend( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
Vector256<byte> alpha, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector256.Narrow( |
|||
Blend(first0, second0, Vector256.WidenLower(alpha), roundBits, roundOffset), |
|||
Blend(first1, second1, Vector256.WidenUpper(alpha), roundBits, roundOffset)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Blend( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
Vector512<byte> alpha, |
|||
int roundBits, |
|||
int roundOffset) |
|||
=> Vector512.Narrow( |
|||
Blend(first0, second0, Vector512.WidenLower(alpha), roundBits, roundOffset), |
|||
Blend(first1, second1, Vector512.WidenUpper(alpha), roundBits, roundOffset)); |
|||
|
|||
/// <summary>
|
|||
/// Alpha-blends 128-bit lanes after widening every product to signed 32-bit precision.
|
|||
/// </summary>
|
|||
private static Vector128<ushort> Blend( |
|||
Vector128<ushort> first, |
|||
Vector128<ushort> second, |
|||
Vector128<ushort> alpha, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector128<int> firstLower = Vector128.WidenLower(first).AsInt32(); |
|||
Vector128<int> firstUpper = Vector128.WidenUpper(first).AsInt32(); |
|||
Vector128<int> secondLower = Vector128.WidenLower(second).AsInt32(); |
|||
Vector128<int> secondUpper = Vector128.WidenUpper(second).AsInt32(); |
|||
Vector128<int> alphaLower = Vector128.WidenLower(alpha).AsInt32(); |
|||
Vector128<int> alphaUpper = Vector128.WidenUpper(alpha).AsInt32(); |
|||
Vector128<int> maximum = Vector128.Create(MaximumMaskAlpha); |
|||
Vector128<int> lower = ((alphaLower * firstLower) + ((maximum - alphaLower) * secondLower)) >> MaskWeightBits; |
|||
Vector128<int> upper = ((alphaUpper * firstUpper) + ((maximum - alphaUpper) * secondUpper)) >> MaskWeightBits; |
|||
return Vector128.Narrow(FinalizeIntermediate(lower, roundBits, roundOffset), FinalizeIntermediate(upper, roundBits, roundOffset)).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Alpha-blends 256-bit lanes after widening every product to signed 32-bit precision.
|
|||
/// </summary>
|
|||
private static Vector256<ushort> Blend( |
|||
Vector256<ushort> first, |
|||
Vector256<ushort> second, |
|||
Vector256<ushort> alpha, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector256<int> firstLower = Vector256.WidenLower(first).AsInt32(); |
|||
Vector256<int> firstUpper = Vector256.WidenUpper(first).AsInt32(); |
|||
Vector256<int> secondLower = Vector256.WidenLower(second).AsInt32(); |
|||
Vector256<int> secondUpper = Vector256.WidenUpper(second).AsInt32(); |
|||
Vector256<int> alphaLower = Vector256.WidenLower(alpha).AsInt32(); |
|||
Vector256<int> alphaUpper = Vector256.WidenUpper(alpha).AsInt32(); |
|||
Vector256<int> maximum = Vector256.Create(MaximumMaskAlpha); |
|||
Vector256<int> lower = ((alphaLower * firstLower) + ((maximum - alphaLower) * secondLower)) >> MaskWeightBits; |
|||
Vector256<int> upper = ((alphaUpper * firstUpper) + ((maximum - alphaUpper) * secondUpper)) >> MaskWeightBits; |
|||
return Vector256.Narrow(FinalizeIntermediate(lower, roundBits, roundOffset), FinalizeIntermediate(upper, roundBits, roundOffset)).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Alpha-blends 512-bit lanes after widening every product to signed 32-bit precision.
|
|||
/// </summary>
|
|||
private static Vector512<ushort> Blend( |
|||
Vector512<ushort> first, |
|||
Vector512<ushort> second, |
|||
Vector512<ushort> alpha, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
Vector512<int> firstLower = Vector512.WidenLower(first).AsInt32(); |
|||
Vector512<int> firstUpper = Vector512.WidenUpper(first).AsInt32(); |
|||
Vector512<int> secondLower = Vector512.WidenLower(second).AsInt32(); |
|||
Vector512<int> secondUpper = Vector512.WidenUpper(second).AsInt32(); |
|||
Vector512<int> alphaLower = Vector512.WidenLower(alpha).AsInt32(); |
|||
Vector512<int> alphaUpper = Vector512.WidenUpper(alpha).AsInt32(); |
|||
Vector512<int> maximum = Vector512.Create(MaximumMaskAlpha); |
|||
Vector512<int> lower = ((alphaLower * firstLower) + ((maximum - alphaLower) * secondLower)) >> MaskWeightBits; |
|||
Vector512<int> upper = ((alphaUpper * firstUpper) + ((maximum - alphaUpper) * secondUpper)) >> MaskWeightBits; |
|||
return Vector512.Narrow(FinalizeIntermediate(lower, roundBits, roundOffset), FinalizeIntermediate(upper, roundBits, roundOffset)).AsUInt16(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,189 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Reconstructs final samples by alpha-blending compound intermediates.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundIntermediateMaskBlendPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Blends two compound intermediates through a luma-resolution mask.
|
|||
/// </summary>
|
|||
public static void BlendIntermediate( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height, |
|||
int subX, |
|||
int subY, |
|||
int bitDepth) |
|||
=> BlendIntermediate<CompoundIntermediateMaskBlendOperator>( |
|||
destination, |
|||
destinationStride, |
|||
first, |
|||
firstStride, |
|||
second, |
|||
secondStride, |
|||
mask, |
|||
maskStride, |
|||
width, |
|||
height, |
|||
subX, |
|||
subY, |
|||
bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed alpha-blend compound-intermediate operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound-intermediate operator.</typeparam>
|
|||
private static void BlendIntermediate<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height, |
|||
int subX, |
|||
int subY, |
|||
int bitDepth) |
|||
where TOperator : struct, IAv1CompoundIntermediateMaskBlendOperator |
|||
{ |
|||
GetIntermediateRounding(bitDepth, out int roundBits, out int roundOffset); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated && subX == 0 && subY == 0) |
|||
{ |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(mask); |
|||
int maskRowOffset = row * maskStride; |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<ushort> first0 = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<ushort> first1 = Vector512.LoadUnsafe(ref firstReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
Vector512<ushort> second0 = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> second1 = Vector512.LoadUnsafe(ref secondReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
Vector512<byte> alpha = Vector512.LoadUnsafe(ref maskReference, (nuint)(maskRowOffset + column)); |
|||
TOperator.Blend(first0, first1, second0, second1, alpha, roundBits, roundOffset) |
|||
.StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated && subX == 0 && subY == 0) |
|||
{ |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(mask); |
|||
int maskRowOffset = row * maskStride; |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<ushort> first0 = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<ushort> first1 = Vector256.LoadUnsafe(ref firstReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
Vector256<ushort> second0 = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> second1 = Vector256.LoadUnsafe(ref secondReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
Vector256<byte> alpha = Vector256.LoadUnsafe(ref maskReference, (nuint)(maskRowOffset + column)); |
|||
TOperator.Blend(first0, first1, second0, second1, alpha, roundBits, roundOffset) |
|||
.StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated && subX == 0 && subY == 0) |
|||
{ |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(mask); |
|||
int maskRowOffset = row * maskStride; |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe( |
|||
ref firstReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe( |
|||
ref secondReference, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
|
|||
Vector128<byte> alpha = Vector128.LoadUnsafe( |
|||
ref maskReference, |
|||
(nuint)(maskRowOffset + column)); |
|||
|
|||
TOperator.Blend( |
|||
first0, |
|||
first1, |
|||
second0, |
|||
second1, |
|||
alpha, |
|||
roundBits, |
|||
roundOffset).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
byte alpha = (byte)GetSubsampledMaskAlpha(mask, maskStride, row, column, subX, subY); |
|||
destinationRow[column] = TOperator.Blend(firstRow[column], secondRow[column], alpha, roundBits, roundOffset); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the mask alpha for one plane sample, averaging its two or four luma samples when required.
|
|||
/// </summary>
|
|||
private static int GetSubsampledMaskAlpha( |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int row, |
|||
int column, |
|||
int subX, |
|||
int subY) |
|||
{ |
|||
int maskRow = row << subY; |
|||
int maskColumn = column << subX; |
|||
int alpha = mask[(maskRow * maskStride) + maskColumn]; |
|||
if (subX != 0) |
|||
{ |
|||
alpha += mask[(maskRow * maskStride) + maskColumn + 1]; |
|||
} |
|||
|
|||
if (subY != 0) |
|||
{ |
|||
int lowerOffset = ((maskRow + 1) * maskStride) + maskColumn; |
|||
alpha += mask[lowerOffset]; |
|||
if (subX != 0) |
|||
{ |
|||
alpha += mask[lowerOffset + 1]; |
|||
} |
|||
} |
|||
|
|||
int sampleCountShift = subX + subY; |
|||
return sampleCountShift == 0 |
|||
? alpha |
|||
: RoundPowerOfTwo(alpha, sampleCountShift); |
|||
} |
|||
} |
|||
@ -0,0 +1,203 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines alpha-masked compound prediction arithmetic.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundMaskBlendPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines alpha-masked compound blending for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1CompoundMaskBlendOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Blends two 8-bit samples through an AV1 alpha value.
|
|||
/// </summary>
|
|||
/// <param name="first">The first sample.</param>
|
|||
/// <param name="second">The second sample.</param>
|
|||
/// <param name="alpha">The first-sample weight in the AV1 mask range.</param>
|
|||
/// <returns>The blended sample.</returns>
|
|||
public static abstract byte Blend(byte first, byte second, byte alpha); |
|||
|
|||
/// <summary>
|
|||
/// Blends two high-bit-depth samples through an AV1 alpha value.
|
|||
/// </summary>
|
|||
/// <param name="first">The first sample.</param>
|
|||
/// <param name="second">The second sample.</param>
|
|||
/// <param name="alpha">The first-sample weight in the AV1 mask range.</param>
|
|||
/// <returns>The blended sample.</returns>
|
|||
public static abstract ushort Blend(ushort first, ushort second, byte alpha); |
|||
|
|||
/// <summary>
|
|||
/// Blends 128-bit vectors of 8-bit samples through AV1 alpha values.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="alpha">The first-sample weights in the AV1 mask range.</param>
|
|||
/// <returns>The blended samples.</returns>
|
|||
public static abstract Vector128<byte> Blend(Vector128<byte> first, Vector128<byte> second, Vector128<byte> alpha); |
|||
|
|||
/// <summary>
|
|||
/// Blends 256-bit vectors of 8-bit samples through AV1 alpha values.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="alpha">The first-sample weights in the AV1 mask range.</param>
|
|||
/// <returns>The blended samples.</returns>
|
|||
public static abstract Vector256<byte> Blend(Vector256<byte> first, Vector256<byte> second, Vector256<byte> alpha); |
|||
|
|||
/// <summary>
|
|||
/// Blends 512-bit vectors of 8-bit samples through AV1 alpha values.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="alpha">The first-sample weights in the AV1 mask range.</param>
|
|||
/// <returns>The blended samples.</returns>
|
|||
public static abstract Vector512<byte> Blend(Vector512<byte> first, Vector512<byte> second, Vector512<byte> alpha); |
|||
|
|||
/// <summary>
|
|||
/// Blends 128-bit vectors of high-bit-depth samples through AV1 alpha values.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="alpha">The first-sample weights in the AV1 mask range.</param>
|
|||
/// <returns>The blended samples.</returns>
|
|||
public static abstract Vector128<ushort> Blend(Vector128<ushort> first, Vector128<ushort> second, Vector128<ushort> alpha); |
|||
|
|||
/// <summary>
|
|||
/// Blends 256-bit vectors of high-bit-depth samples through AV1 alpha values.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="alpha">The first-sample weights in the AV1 mask range.</param>
|
|||
/// <returns>The blended samples.</returns>
|
|||
public static abstract Vector256<ushort> Blend(Vector256<ushort> first, Vector256<ushort> second, Vector256<ushort> alpha); |
|||
|
|||
/// <summary>
|
|||
/// Blends 512-bit vectors of high-bit-depth samples through AV1 alpha values.
|
|||
/// </summary>
|
|||
/// <param name="first">The first samples.</param>
|
|||
/// <param name="second">The second samples.</param>
|
|||
/// <param name="alpha">The first-sample weights in the AV1 mask range.</param>
|
|||
/// <returns>The blended samples.</returns>
|
|||
public static abstract Vector512<ushort> Blend(Vector512<ushort> first, Vector512<ushort> second, Vector512<ushort> alpha); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements AV1 alpha-mask blending for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct CompoundMaskBlendOperator : IAv1CompoundMaskBlendOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Blend(byte first, byte second, byte alpha) |
|||
=> (byte)(((alpha * first) + ((MaximumMaskAlpha - alpha) * second) + 32) >> MaskWeightBits); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static ushort Blend(ushort first, ushort second, byte alpha) |
|||
=> (ushort)(((alpha * first) + ((MaximumMaskAlpha - alpha) * second) + 32) >> MaskWeightBits); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Blend(Vector128<byte> first, Vector128<byte> second, Vector128<byte> alpha) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector128<int> first0, out Vector128<int> first1, out Vector128<int> first2, out Vector128<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector128<int> second0, out Vector128<int> second1, out Vector128<int> second2, out Vector128<int> second3); |
|||
Av1IntraPredictorBase.Widen(alpha, out Vector128<int> alpha0, out Vector128<int> alpha1, out Vector128<int> alpha2, out Vector128<int> alpha3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, alpha0), |
|||
Blend(first1, second1, alpha1), |
|||
Blend(first2, second2, alpha2), |
|||
Blend(first3, second3, alpha3)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Blend(Vector256<byte> first, Vector256<byte> second, Vector256<byte> alpha) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector256<int> first0, out Vector256<int> first1, out Vector256<int> first2, out Vector256<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector256<int> second0, out Vector256<int> second1, out Vector256<int> second2, out Vector256<int> second3); |
|||
Av1IntraPredictorBase.Widen(alpha, out Vector256<int> alpha0, out Vector256<int> alpha1, out Vector256<int> alpha2, out Vector256<int> alpha3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, alpha0), |
|||
Blend(first1, second1, alpha1), |
|||
Blend(first2, second2, alpha2), |
|||
Blend(first3, second3, alpha3)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Blend(Vector512<byte> first, Vector512<byte> second, Vector512<byte> alpha) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector512<int> first0, out Vector512<int> first1, out Vector512<int> first2, out Vector512<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector512<int> second0, out Vector512<int> second1, out Vector512<int> second2, out Vector512<int> second3); |
|||
Av1IntraPredictorBase.Widen(alpha, out Vector512<int> alpha0, out Vector512<int> alpha1, out Vector512<int> alpha2, out Vector512<int> alpha3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, alpha0), |
|||
Blend(first1, second1, alpha1), |
|||
Blend(first2, second2, alpha2), |
|||
Blend(first3, second3, alpha3)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<ushort> Blend(Vector128<ushort> first, Vector128<ushort> second, Vector128<ushort> alpha) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector128<int> first0, out Vector128<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector128<int> second0, out Vector128<int> second1); |
|||
Av1IntraPredictorBase.Widen(alpha.AsInt16(), out Vector128<int> alpha0, out Vector128<int> alpha1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, alpha0), Blend(first1, second1, alpha1)).AsUInt16(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<ushort> Blend(Vector256<ushort> first, Vector256<ushort> second, Vector256<ushort> alpha) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector256<int> first0, out Vector256<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector256<int> second0, out Vector256<int> second1); |
|||
Av1IntraPredictorBase.Widen(alpha.AsInt16(), out Vector256<int> alpha0, out Vector256<int> alpha1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, alpha0), Blend(first1, second1, alpha1)).AsUInt16(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<ushort> Blend(Vector512<ushort> first, Vector512<ushort> second, Vector512<ushort> alpha) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector512<int> first0, out Vector512<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector512<int> second0, out Vector512<int> second1); |
|||
Av1IntraPredictorBase.Widen(alpha.AsInt16(), out Vector512<int> alpha0, out Vector512<int> alpha1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, alpha0), Blend(first1, second1, alpha1)).AsUInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies alpha-mask blending to 128-bit vectors of widened samples.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> Blend(Vector128<int> first, Vector128<int> second, Vector128<int> alpha) |
|||
=> ((alpha * first) + ((Vector128.Create(MaximumMaskAlpha) - alpha) * second) + Vector128.Create(32)) >> MaskWeightBits; |
|||
|
|||
/// <summary>
|
|||
/// Applies alpha-mask blending to 256-bit vectors of widened samples.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> Blend(Vector256<int> first, Vector256<int> second, Vector256<int> alpha) |
|||
=> ((alpha * first) + ((Vector256.Create(MaximumMaskAlpha) - alpha) * second) + Vector256.Create(32)) >> MaskWeightBits; |
|||
|
|||
/// <summary>
|
|||
/// Applies alpha-mask blending to 512-bit vectors of widened samples.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> Blend(Vector512<int> first, Vector512<int> second, Vector512<int> alpha) |
|||
=> ((alpha * first) + ((Vector512.Create(MaximumMaskAlpha) - alpha) * second) + Vector512.Create(32)) >> MaskWeightBits; |
|||
} |
|||
} |
|||
@ -0,0 +1,267 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Reconstructs alpha-masked compound prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundMaskBlendPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Blends two 8-bit predictors through a contiguous AV1 alpha mask.
|
|||
/// </summary>
|
|||
public static void Blend( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
=> Blend<CompoundMaskBlendOperator>( |
|||
destination, |
|||
destinationStride, |
|||
second, |
|||
secondStride, |
|||
mask, |
|||
maskStride, |
|||
width, |
|||
height); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed 8-bit masked compound operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound arithmetic operator.</typeparam>
|
|||
private static void Blend<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1CompoundMaskBlendOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<byte> maskVector = Vector512.LoadUnsafe(ref maskReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<byte> maskVector = Vector256.LoadUnsafe(ref maskReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<byte> maskVector = Vector128.LoadUnsafe(ref maskReference, (nuint)column); |
|||
TOperator.Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column], maskRow[column]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two high-bit-depth predictors through a contiguous AV1 alpha mask.
|
|||
/// </summary>
|
|||
public static void Blend( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
=> Blend<CompoundMaskBlendOperator>( |
|||
destination, |
|||
destinationStride, |
|||
second, |
|||
secondStride, |
|||
mask, |
|||
maskStride, |
|||
width, |
|||
height); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed high-bit-depth masked compound operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound arithmetic operator.</typeparam>
|
|||
private static void Blend<TOperator>( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1CompoundMaskBlendOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<ushort> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> maskVector = LoadMask512(ref maskReference, column); |
|||
TOperator.Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<ushort> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> maskVector = LoadMask256(ref maskReference, column); |
|||
TOperator.Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<ushort> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> maskVector = LoadMask128(ref maskReference, column); |
|||
TOperator.Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column], maskRow[column]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two 8-bit predictors through an alpha mask without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void BlendScalar( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
=> BlendScalar<CompoundMaskBlendOperator>( |
|||
destination, |
|||
destinationStride, |
|||
second, |
|||
secondStride, |
|||
mask, |
|||
maskStride, |
|||
width, |
|||
height); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed 8-bit masked compound operator without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The compound arithmetic operator.</typeparam>
|
|||
private static void BlendScalar<TOperator>( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1CompoundMaskBlendOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destinationRow[column] = TOperator.Blend(destinationRow[column], secondRow[column], maskRow[column]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> LoadMask128(ref byte source, int offset) |
|||
{ |
|||
Vector64<byte> packed = Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref source, offset)); |
|||
return Vector128.WidenLower(Vector128.Create(packed, Vector64<byte>.Zero)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> LoadMask256(ref byte source, int offset) |
|||
{ |
|||
Vector128<byte> packed = Vector128.LoadUnsafe(ref source, (nuint)offset); |
|||
return Vector256.WidenLower(Vector256.Create(packed, Vector128<byte>.Zero)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> LoadMask512(ref byte source, int offset) |
|||
{ |
|||
Vector256<byte> packed = Vector256.LoadUnsafe(ref source, (nuint)offset); |
|||
return Vector512.WidenLower(Vector512.Create(packed, Vector256<byte>.Zero)); |
|||
} |
|||
} |
|||
@ -0,0 +1,254 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines difference-weighted compound mask arithmetic.
|
|||
/// </content>
|
|||
internal static partial class Av1DifferenceWeightedMaskBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Defines difference-weighted compound mask generation for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1DifferenceWeightedMaskOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Creates one mask value from two 8-bit predictor samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first predictor sample.</param>
|
|||
/// <param name="second">The second predictor sample.</param>
|
|||
/// <param name="shift">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The AV1 mask value.</returns>
|
|||
public static abstract byte Create(byte first, byte second, int shift, bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates one mask value from two high-bit-depth predictor samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first predictor sample.</param>
|
|||
/// <param name="second">The second predictor sample.</param>
|
|||
/// <param name="shift">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The AV1 mask value.</returns>
|
|||
public static abstract byte Create(ushort first, ushort second, int shift, bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 128 bits of mask values from 8-bit predictor samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first predictor samples.</param>
|
|||
/// <param name="second">The second predictor samples.</param>
|
|||
/// <param name="shift">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The AV1 mask values.</returns>
|
|||
public static abstract Vector128<byte> Create(Vector128<byte> first, Vector128<byte> second, int shift, bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 256 bits of mask values from 8-bit predictor samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first predictor samples.</param>
|
|||
/// <param name="second">The second predictor samples.</param>
|
|||
/// <param name="shift">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The AV1 mask values.</returns>
|
|||
public static abstract Vector256<byte> Create(Vector256<byte> first, Vector256<byte> second, int shift, bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 512 bits of mask values from 8-bit predictor samples.
|
|||
/// </summary>
|
|||
/// <param name="first">The first predictor samples.</param>
|
|||
/// <param name="second">The second predictor samples.</param>
|
|||
/// <param name="shift">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The AV1 mask values.</returns>
|
|||
public static abstract Vector512<byte> Create(Vector512<byte> first, Vector512<byte> second, int shift, bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 128 bits of packed mask values from high-bit-depth predictor samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor samples.</param>
|
|||
/// <param name="first1">The upper first-predictor samples.</param>
|
|||
/// <param name="second0">The lower second-predictor samples.</param>
|
|||
/// <param name="second1">The upper second-predictor samples.</param>
|
|||
/// <param name="shift">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The packed AV1 mask values.</returns>
|
|||
public static abstract Vector128<byte> Create( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int shift, |
|||
bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 256 bits of packed mask values from high-bit-depth predictor samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor samples.</param>
|
|||
/// <param name="first1">The upper first-predictor samples.</param>
|
|||
/// <param name="second0">The lower second-predictor samples.</param>
|
|||
/// <param name="second1">The upper second-predictor samples.</param>
|
|||
/// <param name="shift">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The packed AV1 mask values.</returns>
|
|||
public static abstract Vector256<byte> Create( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int shift, |
|||
bool invert); |
|||
|
|||
/// <summary>
|
|||
/// Creates 512 bits of packed mask values from high-bit-depth predictor samples.
|
|||
/// </summary>
|
|||
/// <param name="first0">The lower first-predictor samples.</param>
|
|||
/// <param name="first1">The upper first-predictor samples.</param>
|
|||
/// <param name="second0">The lower second-predictor samples.</param>
|
|||
/// <param name="second1">The upper second-predictor samples.</param>
|
|||
/// <param name="shift">The difference scaling shift.</param>
|
|||
/// <param name="invert">Whether to invert the selected predictor.</param>
|
|||
/// <returns>The packed AV1 mask values.</returns>
|
|||
public static abstract Vector512<byte> Create( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int shift, |
|||
bool invert); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements AV1 difference-weighted mask generation for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct DifferenceWeightedMaskOperator : IAv1DifferenceWeightedMaskOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Create(byte first, byte second, int shift, bool invert) |
|||
=> Create((ushort)Math.Abs(first - second), shift, invert); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static byte Create(ushort first, ushort second, int shift, bool invert) |
|||
=> Create((ushort)Math.Abs(first - second), shift, invert); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Create(Vector128<byte> first, Vector128<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector128<byte> difference = Vector128.Max(first, second) - Vector128.Min(first, second); |
|||
Vector128<ushort> lower = CreateAlpha(Vector128.WidenLower(difference), shift, invert); |
|||
Vector128<ushort> upper = CreateAlpha(Vector128.WidenUpper(difference), shift, invert); |
|||
return Vector128.Narrow(lower, upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Create(Vector256<byte> first, Vector256<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector256<byte> difference = Vector256.Max(first, second) - Vector256.Min(first, second); |
|||
Vector256<ushort> lower = CreateAlpha(Vector256.WidenLower(difference), shift, invert); |
|||
Vector256<ushort> upper = CreateAlpha(Vector256.WidenUpper(difference), shift, invert); |
|||
return Vector256.Narrow(lower, upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Create(Vector512<byte> first, Vector512<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector512<byte> difference = Vector512.Max(first, second) - Vector512.Min(first, second); |
|||
Vector512<ushort> lower = CreateAlpha(Vector512.WidenLower(difference), shift, invert); |
|||
Vector512<ushort> upper = CreateAlpha(Vector512.WidenUpper(difference), shift, invert); |
|||
return Vector512.Narrow(lower, upper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Create( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector128.Narrow( |
|||
CreateAlpha(Vector128.Max(first0, second0) - Vector128.Min(first0, second0), shift, invert), |
|||
CreateAlpha(Vector128.Max(first1, second1) - Vector128.Min(first1, second1), shift, invert)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<byte> Create( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector256.Narrow( |
|||
CreateAlpha(Vector256.Max(first0, second0) - Vector256.Min(first0, second0), shift, invert), |
|||
CreateAlpha(Vector256.Max(first1, second1) - Vector256.Min(first1, second1), shift, invert)); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<byte> Create( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector512.Narrow( |
|||
CreateAlpha(Vector512.Max(first0, second0) - Vector512.Min(first0, second0), shift, invert), |
|||
CreateAlpha(Vector512.Max(first1, second1) - Vector512.Min(first1, second1), shift, invert)); |
|||
|
|||
/// <summary>
|
|||
/// Creates one mask value from an absolute predictor difference.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static byte Create(ushort difference, int shift, bool invert) |
|||
{ |
|||
int alpha = Math.Min(MaximumMaskAlpha, 38 + (difference >> shift)); |
|||
return (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates 128-bit vectors of unpacked mask values from absolute predictor differences.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> CreateAlpha(Vector128<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector128<ushort> maximum = Vector128.Create((ushort)MaximumMaskAlpha); |
|||
Vector128<ushort> alpha = Vector128.Min(maximum, (difference >> shift) + Vector128.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates 256-bit vectors of unpacked mask values from absolute predictor differences.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> CreateAlpha(Vector256<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector256<ushort> maximum = Vector256.Create((ushort)MaximumMaskAlpha); |
|||
Vector256<ushort> alpha = Vector256.Min(maximum, (difference >> shift) + Vector256.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates 512-bit vectors of unpacked mask values from absolute predictor differences.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> CreateAlpha(Vector512<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector512<ushort> maximum = Vector512.Create((ushort)MaximumMaskAlpha); |
|||
Vector512<ushort> alpha = Vector512.Min(maximum, (difference >> shift) + Vector512.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,214 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Builds AV1 difference-weighted compound masks.
|
|||
/// </content>
|
|||
internal static partial class Av1DifferenceWeightedMaskBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Fills an 8-bit difference-weighted compound mask.
|
|||
/// </summary>
|
|||
public static void FillDifferenceWeightedMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<byte> first, |
|||
int firstStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
=> FillDifferenceWeightedMask<DifferenceWeightedMaskOperator>( |
|||
mask, |
|||
maskStride, |
|||
first, |
|||
firstStride, |
|||
second, |
|||
secondStride, |
|||
width, |
|||
height, |
|||
maskType); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed 8-bit difference-weighted mask operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The difference-weighted mask operator.</typeparam>
|
|||
private static void FillDifferenceWeightedMask<TOperator>( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<byte> first, |
|||
int firstStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
where TOperator : struct, IAv1DifferenceWeightedMaskOperator |
|||
{ |
|||
bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ReadOnlySpan<byte> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
ref byte firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Create(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Create(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
TOperator.Create(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
maskRow[column] = TOperator.Create(firstRow[column], secondRow[column], 4, invert); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills a high-bit-depth difference-weighted compound mask.
|
|||
/// </summary>
|
|||
public static void FillDifferenceWeightedMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
=> FillDifferenceWeightedMask<DifferenceWeightedMaskOperator>( |
|||
mask, |
|||
maskStride, |
|||
first, |
|||
firstStride, |
|||
second, |
|||
secondStride, |
|||
width, |
|||
height, |
|||
bitDepth, |
|||
maskType); |
|||
|
|||
/// <summary>
|
|||
/// Executes one closed high-bit-depth difference-weighted mask operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The difference-weighted mask operator.</typeparam>
|
|||
private static void FillDifferenceWeightedMask<TOperator>( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
where TOperator : struct, IAv1DifferenceWeightedMaskOperator |
|||
{ |
|||
bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; |
|||
int differenceShift = bitDepth - 8 + 4; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
// Two input vectors narrow to one packed byte mask. This keeps mask construction contiguous and avoids
|
|||
// temporary buffers before the following vector blend consumes the complete plane block.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<ushort> first0 = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<ushort> first1 = Vector512.LoadUnsafe(ref firstReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
Vector512<ushort> second0 = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> second1 = Vector512.LoadUnsafe(ref secondReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
TOperator.Create(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<ushort> first0 = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<ushort> first1 = Vector256.LoadUnsafe(ref firstReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
Vector256<ushort> second0 = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> second1 = Vector256.LoadUnsafe(ref secondReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
TOperator.Create(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe(ref firstReference, (nuint)(column + Vector128<ushort>.Count)); |
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe(ref secondReference, (nuint)(column + Vector128<ushort>.Count)); |
|||
TOperator.Create(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
maskRow[column] = TOperator.Create(firstRow[column], secondRow[column], differenceShift, invert); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1CompoundInterPredictor; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Builds AV1 inter-intra prediction masks.
|
|||
/// </content>
|
|||
internal static partial class Av1InterIntraMaskBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Gets libaom's one-dimensional inter-intra alpha curve.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> InterIntraWeights => |
|||
[ |
|||
60, 58, 56, 54, 52, 50, 48, 47, 45, 44, 42, 41, 39, 38, 37, 35, |
|||
34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 22, 21, 20, |
|||
19, 19, 18, 18, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 12, |
|||
11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, |
|||
6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, |
|||
4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, |
|||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, |
|||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Fills a smooth inter-intra mask for one plane.
|
|||
/// </summary>
|
|||
public static void FillInterIntraMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height, |
|||
Av1InterIntraMode mode, |
|||
bool invert) |
|||
{ |
|||
int sizeScale = 128 / Math.Max(width, height); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int alpha = mode switch |
|||
{ |
|||
Av1InterIntraMode.Vertical => InterIntraWeights[row * sizeScale], |
|||
Av1InterIntraMode.Horizontal => InterIntraWeights[column * sizeScale], |
|||
Av1InterIntraMode.Smooth => InterIntraWeights[Math.Min(row, column) * sizeScale], |
|||
_ => 32, |
|||
}; |
|||
|
|||
maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,732 +0,0 @@ |
|||
// 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.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Produces the biased high-precision intermediates required by compound inter prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1InterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The second-round shift retained by every compound convolution path.
|
|||
/// </summary>
|
|||
internal const int CompoundRound1Bits = 7; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one 8-bit translational reference into AV1's unsigned compound intermediate format.
|
|||
/// </summary>
|
|||
public static void PredictCompound( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
int sourceOrigin, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height, |
|||
Av1InterpolationFilter horizontalFilter, |
|||
Av1InterpolationFilter verticalFilter, |
|||
int horizontalPhase, |
|||
int verticalPhase, |
|||
Span<short> scratch) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
PredictCompoundVector128( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
destination, |
|||
destinationStride, |
|||
width, |
|||
height, |
|||
horizontalFilter, |
|||
verticalFilter, |
|||
horizontalPhase, |
|||
verticalPhase, |
|||
scratch); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PredictCompoundScalar( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
destination, |
|||
destinationStride, |
|||
width, |
|||
height, |
|||
horizontalFilter, |
|||
verticalFilter, |
|||
horizontalPhase, |
|||
verticalPhase, |
|||
scratch); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one compound intermediate without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictCompoundScalar( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
int sourceOrigin, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height, |
|||
Av1InterpolationFilter horizontalFilter, |
|||
Av1InterpolationFilter verticalFilter, |
|||
int horizontalPhase, |
|||
int verticalPhase, |
|||
Span<short> scratch) |
|||
{ |
|||
ReadOnlySpan<short> horizontalCoefficients = GetCompoundCoefficients(horizontalFilter, horizontalPhase, width <= 4); |
|||
ReadOnlySpan<short> verticalCoefficients = GetCompoundCoefficients(verticalFilter, verticalPhase, height <= 4); |
|||
int roundBits = (2 * FilterBits) - Round0Bits - CompoundRound1Bits; |
|||
int offsetBits = 8 + (2 * FilterBits) - Round0Bits; |
|||
int roundOffset = (1 << (offsetBits - CompoundRound1Bits)) + |
|||
(1 << (offsetBits - CompoundRound1Bits - 1)); |
|||
|
|||
ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
if (horizontalPhase == 0 && verticalPhase == 0) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = |
|||
(ushort)((Unsafe.Add(ref sourceRow, column) << roundBits) + roundOffset); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (verticalPhase == 0) |
|||
{ |
|||
GetEffectiveKernel(horizontalCoefficients, out int firstCoefficient, out int tapCount); |
|||
ref short coefficientBase = ref Unsafe.Add( |
|||
ref MemoryMarshal.GetReference(horizontalCoefficients), |
|||
firstCoefficient); |
|||
|
|||
int sourceOffset = firstCoefficient - 3; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, (row * sourceStride) + sourceOffset); |
|||
ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int sum = ConvolveScalar(ref Unsafe.Add(ref sourceRow, column), 1, ref coefficientBase, tapCount); |
|||
int result = (RoundPowerOfTwo(sum, Round0Bits) << (FilterBits - CompoundRound1Bits)) + roundOffset; |
|||
Unsafe.Add(ref destinationRow, column) = (ushort)result; |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (horizontalPhase == 0) |
|||
{ |
|||
GetEffectiveKernel(verticalCoefficients, out int firstCoefficient, out int tapCount); |
|||
ref short coefficientBase = ref Unsafe.Add( |
|||
ref MemoryMarshal.GetReference(verticalCoefficients), |
|||
firstCoefficient); |
|||
|
|||
int sourceOffset = firstCoefficient - 3; |
|||
int firstPassBits = FilterBits - Round0Bits; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, (row + sourceOffset) * sourceStride); |
|||
ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int sum = ConvolveScalar( |
|||
ref Unsafe.Add(ref sourceRow, column), |
|||
sourceStride, |
|||
ref coefficientBase, |
|||
tapCount); |
|||
|
|||
int result = RoundPowerOfTwo(sum << firstPassBits, CompoundRound1Bits) + roundOffset; |
|||
Unsafe.Add(ref destinationRow, column) = (ushort)result; |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
GetEffectiveKernel(horizontalCoefficients, out int firstHorizontalCoefficient, out int horizontalTapCount); |
|||
GetEffectiveKernel(verticalCoefficients, out int firstVerticalCoefficient, out int verticalTapCount); |
|||
ref short horizontalCoefficientBase = ref Unsafe.Add( |
|||
ref MemoryMarshal.GetReference(horizontalCoefficients), |
|||
firstHorizontalCoefficient); |
|||
|
|||
ref short verticalCoefficientBase = ref Unsafe.Add( |
|||
ref MemoryMarshal.GetReference(verticalCoefficients), |
|||
firstVerticalCoefficient); |
|||
|
|||
int scratchStride = Math.Max(width, MinimumScratchStride); |
|||
int intermediateHeight = height + verticalTapCount - 1; |
|||
int horizontalSourceOffset = firstHorizontalCoefficient - 3; |
|||
int verticalSourceOffset = firstVerticalCoefficient - 3; |
|||
int horizontalBias = 1 << (8 + FilterBits - 1); |
|||
ref short scratchBase = ref MemoryMarshal.GetReference(scratch); |
|||
|
|||
// The Q7 horizontal pass keeps enough precision for the vertical pass while the positive bias makes every
|
|||
// intermediate representable by signed 16-bit scratch. This is the same no-round compound shape as libaom.
|
|||
for (int row = 0; row < intermediateHeight; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add( |
|||
ref sourceBase, |
|||
((row + verticalSourceOffset) * sourceStride) + horizontalSourceOffset); |
|||
|
|||
ref short scratchRow = ref Unsafe.Add(ref scratchBase, row * scratchStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int sum = horizontalBias + ConvolveScalar( |
|||
ref Unsafe.Add(ref sourceRow, column), |
|||
1, |
|||
ref horizontalCoefficientBase, |
|||
horizontalTapCount); |
|||
|
|||
Unsafe.Add(ref scratchRow, column) = (short)RoundPowerOfTwo(sum, Round0Bits); |
|||
} |
|||
} |
|||
|
|||
int verticalBias = 1 << offsetBits; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short scratchRow = ref Unsafe.Add(ref scratchBase, row * scratchStride); |
|||
ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int sum = verticalBias + ConvolveScalar( |
|||
ref Unsafe.Add(ref scratchRow, column), |
|||
scratchStride, |
|||
ref verticalCoefficientBase, |
|||
verticalTapCount); |
|||
|
|||
Unsafe.Add(ref destinationRow, column) = (ushort)RoundPowerOfTwo(sum, CompoundRound1Bits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one compound intermediate through the 128-bit convolution tier.
|
|||
/// </summary>
|
|||
private static void PredictCompoundVector128( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
int sourceOrigin, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height, |
|||
Av1InterpolationFilter horizontalFilter, |
|||
Av1InterpolationFilter verticalFilter, |
|||
int horizontalPhase, |
|||
int verticalPhase, |
|||
Span<short> scratch) |
|||
{ |
|||
ReadOnlySpan<short> horizontalCoefficients = GetCompoundCoefficients(horizontalFilter, horizontalPhase, width <= 4); |
|||
ReadOnlySpan<short> verticalCoefficients = GetCompoundCoefficients(verticalFilter, verticalPhase, height <= 4); |
|||
int roundBits = (2 * FilterBits) - Round0Bits - CompoundRound1Bits; |
|||
int offsetBits = 8 + (2 * FilterBits) - Round0Bits; |
|||
int roundOffset = (1 << (offsetBits - CompoundRound1Bits)) + |
|||
(1 << (offsetBits - CompoundRound1Bits - 1)); |
|||
|
|||
if (horizontalPhase == 0 && verticalPhase == 0) |
|||
{ |
|||
CopyCompoundVector128( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
destination, |
|||
destinationStride, |
|||
width, |
|||
height, |
|||
roundBits, |
|||
roundOffset); |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (verticalPhase == 0) |
|||
{ |
|||
GetEffectiveKernel(horizontalCoefficients, out int firstCoefficient, out int tapCount); |
|||
FilterCompoundDirectVector128( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
destination, |
|||
destinationStride, |
|||
width, |
|||
height, |
|||
horizontalCoefficients[firstCoefficient..], |
|||
tapCount, |
|||
firstCoefficient - 3, |
|||
tapStride: 1, |
|||
preShift: 0, |
|||
round: Round0Bits, |
|||
roundOffset); |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (horizontalPhase == 0) |
|||
{ |
|||
GetEffectiveKernel(verticalCoefficients, out int firstCoefficient, out int tapCount); |
|||
FilterCompoundDirectVector128( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
destination, |
|||
destinationStride, |
|||
width, |
|||
height, |
|||
verticalCoefficients[firstCoefficient..], |
|||
tapCount, |
|||
(firstCoefficient - 3) * sourceStride, |
|||
sourceStride, |
|||
FilterBits - Round0Bits, |
|||
CompoundRound1Bits, |
|||
roundOffset); |
|||
|
|||
return; |
|||
} |
|||
|
|||
GetEffectiveKernel(horizontalCoefficients, out int firstHorizontalCoefficient, out int horizontalTapCount); |
|||
GetEffectiveKernel(verticalCoefficients, out int firstVerticalCoefficient, out int verticalTapCount); |
|||
FilterCompound2DVector128( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
destination, |
|||
destinationStride, |
|||
width, |
|||
height, |
|||
horizontalCoefficients[firstHorizontalCoefficient..], |
|||
horizontalTapCount, |
|||
firstHorizontalCoefficient - 3, |
|||
verticalCoefficients[firstVerticalCoefficient..], |
|||
verticalTapCount, |
|||
firstVerticalCoefficient - 3, |
|||
scratch); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies integer-position samples into biased compound intermediates in sixteen-sample groups.
|
|||
/// </summary>
|
|||
private static void CopyCompoundVector128( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
int sourceOrigin, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height, |
|||
int roundBits, |
|||
int roundOffset) |
|||
{ |
|||
ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Vector128<ushort> offset = Vector128.Create((ushort)roundOffset); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> samples = Vector128.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
((Vector128.WidenLower(samples) << roundBits) + offset).StoreUnsafe( |
|||
ref destinationRow, |
|||
(nuint)column); |
|||
|
|||
((Vector128.WidenUpper(samples) << roundBits) + offset).StoreUnsafe( |
|||
ref destinationRow, |
|||
(nuint)(column + Vector128<ushort>.Count)); |
|||
} |
|||
|
|||
if (column == 0) |
|||
{ |
|||
// Narrow AV1 blocks still use the SIMD load; the width-specific stores preserve the adjacent block.
|
|||
Vector128<byte> samples = Vector128.LoadUnsafe(ref sourceRow); |
|||
StoreCompoundVectors( |
|||
(Vector128.WidenLower(samples) << roundBits) + offset, |
|||
(Vector128.WidenUpper(samples) << roundBits) + offset, |
|||
ref destinationRow, |
|||
width); |
|||
|
|||
continue; |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
Unsafe.Add(ref destinationRow, column) = |
|||
(ushort)((Unsafe.Add(ref sourceRow, column) << roundBits) + roundOffset); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one compound convolution direction in sixteen-sample groups.
|
|||
/// </summary>
|
|||
private static void FilterCompoundDirectVector128( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
int sourceOrigin, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height, |
|||
ReadOnlySpan<short> coefficients, |
|||
int tapCount, |
|||
int sourceOffset, |
|||
int tapStride, |
|||
int preShift, |
|||
int round, |
|||
int roundOffset) |
|||
{ |
|||
ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
ref short coefficientBase = ref MemoryMarshal.GetReference(coefficients); |
|||
Vector128<int> offset = Vector128.Create(roundOffset); |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, (row * sourceStride) + sourceOffset); |
|||
ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Convolve( |
|||
ref sourceRow, |
|||
tapStride, |
|||
(nuint)column, |
|||
ref coefficientBase, |
|||
tapCount, |
|||
Vector128<int>.Zero, |
|||
out Vector128<int> result0, |
|||
out Vector128<int> result1, |
|||
out Vector128<int> result2, |
|||
out Vector128<int> result3); |
|||
|
|||
PrepareCompoundResults( |
|||
ref result0, |
|||
ref result1, |
|||
ref result2, |
|||
ref result3, |
|||
preShift, |
|||
round, |
|||
offset); |
|||
|
|||
StoreCompoundVectors(result0, result1, result2, result3, ref destinationRow, column, Vector128<byte>.Count); |
|||
} |
|||
|
|||
if (column == 0) |
|||
{ |
|||
Convolve( |
|||
ref sourceRow, |
|||
tapStride, |
|||
0, |
|||
ref coefficientBase, |
|||
tapCount, |
|||
Vector128<int>.Zero, |
|||
out Vector128<int> result0, |
|||
out Vector128<int> result1, |
|||
out Vector128<int> result2, |
|||
out Vector128<int> result3); |
|||
|
|||
PrepareCompoundResults( |
|||
ref result0, |
|||
ref result1, |
|||
ref result2, |
|||
ref result3, |
|||
preShift, |
|||
round, |
|||
offset); |
|||
|
|||
StoreCompoundVectors(result0, result1, result2, result3, ref destinationRow, 0, width); |
|||
continue; |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int sum = ConvolveScalar( |
|||
ref Unsafe.Add(ref sourceRow, column), |
|||
tapStride, |
|||
ref coefficientBase, |
|||
tapCount); |
|||
|
|||
sum = RoundPowerOfTwo(sum << preShift, round) + roundOffset; |
|||
Unsafe.Add(ref destinationRow, column) = (ushort)sum; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies separable compound convolution through caller-owned signed scratch.
|
|||
/// </summary>
|
|||
private static void FilterCompound2DVector128( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
int sourceOrigin, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height, |
|||
ReadOnlySpan<short> horizontalCoefficients, |
|||
int horizontalTapCount, |
|||
int horizontalSourceOffset, |
|||
ReadOnlySpan<short> verticalCoefficients, |
|||
int verticalTapCount, |
|||
int verticalSourceOffset, |
|||
Span<short> scratch) |
|||
{ |
|||
ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
ref short scratchBase = ref MemoryMarshal.GetReference(scratch); |
|||
ref short horizontalCoefficientBase = ref MemoryMarshal.GetReference(horizontalCoefficients); |
|||
ref short verticalCoefficientBase = ref MemoryMarshal.GetReference(verticalCoefficients); |
|||
int scratchStride = Math.Max(width, MinimumScratchStride); |
|||
int intermediateHeight = height + verticalTapCount - 1; |
|||
Vector128<int> horizontalBias = Vector128.Create(1 << (8 + FilterBits - 1)); |
|||
|
|||
// The complete narrow-block vector is retained in scratch because the vertical pass consumes the same lanes.
|
|||
// Wider blocks use one vector per sixteen output samples and finish any nonstandard tail scalarly.
|
|||
for (int row = 0; row < intermediateHeight; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add( |
|||
ref sourceBase, |
|||
((row + verticalSourceOffset) * sourceStride) + horizontalSourceOffset); |
|||
|
|||
ref short scratchRow = ref Unsafe.Add(ref scratchBase, row * scratchStride); |
|||
int column = 0; |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Convolve( |
|||
ref sourceRow, |
|||
1, |
|||
(nuint)column, |
|||
ref horizontalCoefficientBase, |
|||
horizontalTapCount, |
|||
horizontalBias, |
|||
out Vector128<int> result0, |
|||
out Vector128<int> result1, |
|||
out Vector128<int> result2, |
|||
out Vector128<int> result3); |
|||
|
|||
Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(result0, Round0Bits), |
|||
RoundPowerOfTwo(result1, Round0Bits)).StoreUnsafe(ref scratchRow, (nuint)column); |
|||
|
|||
Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(result2, Round0Bits), |
|||
RoundPowerOfTwo(result3, Round0Bits)).StoreUnsafe( |
|||
ref scratchRow, |
|||
(nuint)(column + Vector128<short>.Count)); |
|||
} |
|||
|
|||
if (column == 0) |
|||
{ |
|||
Convolve( |
|||
ref sourceRow, |
|||
1, |
|||
0, |
|||
ref horizontalCoefficientBase, |
|||
horizontalTapCount, |
|||
horizontalBias, |
|||
out Vector128<int> result0, |
|||
out Vector128<int> result1, |
|||
out Vector128<int> result2, |
|||
out Vector128<int> result3); |
|||
|
|||
Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(result0, Round0Bits), |
|||
RoundPowerOfTwo(result1, Round0Bits)).StoreUnsafe(ref scratchRow); |
|||
|
|||
Av1IntraPredictorBase.Narrow( |
|||
RoundPowerOfTwo(result2, Round0Bits), |
|||
RoundPowerOfTwo(result3, Round0Bits)).StoreUnsafe( |
|||
ref scratchRow, |
|||
(nuint)Vector128<short>.Count); |
|||
|
|||
continue; |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int sum = (1 << (8 + FilterBits - 1)) + ConvolveScalar( |
|||
ref Unsafe.Add(ref sourceRow, column), |
|||
1, |
|||
ref horizontalCoefficientBase, |
|||
horizontalTapCount); |
|||
|
|||
Unsafe.Add(ref scratchRow, column) = (short)RoundPowerOfTwo(sum, Round0Bits); |
|||
} |
|||
} |
|||
|
|||
Vector128<int> verticalBias = Vector128.Create(1 << (8 + (2 * FilterBits) - Round0Bits)); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short scratchRow = ref Unsafe.Add(ref scratchBase, row * scratchStride); |
|||
ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
int column = 0; |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Convolve( |
|||
ref scratchRow, |
|||
scratchStride, |
|||
(nuint)column, |
|||
ref verticalCoefficientBase, |
|||
verticalTapCount, |
|||
verticalBias, |
|||
out Vector128<int> result0, |
|||
out Vector128<int> result1); |
|||
|
|||
Convolve( |
|||
ref scratchRow, |
|||
scratchStride, |
|||
(nuint)(column + Vector128<short>.Count), |
|||
ref verticalCoefficientBase, |
|||
verticalTapCount, |
|||
verticalBias, |
|||
out Vector128<int> result2, |
|||
out Vector128<int> result3); |
|||
|
|||
result0 = RoundPowerOfTwo(result0, CompoundRound1Bits); |
|||
result1 = RoundPowerOfTwo(result1, CompoundRound1Bits); |
|||
result2 = RoundPowerOfTwo(result2, CompoundRound1Bits); |
|||
result3 = RoundPowerOfTwo(result3, CompoundRound1Bits); |
|||
StoreCompoundVectors(result0, result1, result2, result3, ref destinationRow, column, Vector128<byte>.Count); |
|||
} |
|||
|
|||
if (column == 0) |
|||
{ |
|||
Convolve( |
|||
ref scratchRow, |
|||
scratchStride, |
|||
0, |
|||
ref verticalCoefficientBase, |
|||
verticalTapCount, |
|||
verticalBias, |
|||
out Vector128<int> result0, |
|||
out Vector128<int> result1); |
|||
|
|||
Convolve( |
|||
ref Unsafe.Add(ref scratchRow, Vector128<short>.Count), |
|||
scratchStride, |
|||
0, |
|||
ref verticalCoefficientBase, |
|||
verticalTapCount, |
|||
verticalBias, |
|||
out Vector128<int> result2, |
|||
out Vector128<int> result3); |
|||
|
|||
result0 = RoundPowerOfTwo(result0, CompoundRound1Bits); |
|||
result1 = RoundPowerOfTwo(result1, CompoundRound1Bits); |
|||
result2 = RoundPowerOfTwo(result2, CompoundRound1Bits); |
|||
result3 = RoundPowerOfTwo(result3, CompoundRound1Bits); |
|||
StoreCompoundVectors(result0, result1, result2, result3, ref destinationRow, 0, width); |
|||
continue; |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int sum = (1 << (8 + (2 * FilterBits) - Round0Bits)) + ConvolveScalar( |
|||
ref Unsafe.Add(ref scratchRow, column), |
|||
scratchStride, |
|||
ref verticalCoefficientBase, |
|||
verticalTapCount); |
|||
|
|||
Unsafe.Add(ref destinationRow, column) = |
|||
(ushort)RoundPowerOfTwo(sum, CompoundRound1Bits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the compound direct-filter shifts and bias to sixteen convolution results.
|
|||
/// </summary>
|
|||
private static void PrepareCompoundResults( |
|||
ref Vector128<int> result0, |
|||
ref Vector128<int> result1, |
|||
ref Vector128<int> result2, |
|||
ref Vector128<int> result3, |
|||
int preShift, |
|||
int round, |
|||
Vector128<int> offset) |
|||
{ |
|||
result0 = RoundPowerOfTwo(result0 << preShift, round) + offset; |
|||
result1 = RoundPowerOfTwo(result1 << preShift, round) + offset; |
|||
result2 = RoundPowerOfTwo(result2 << preShift, round) + offset; |
|||
result3 = RoundPowerOfTwo(result3 << preShift, round) + offset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Packs and stores up to sixteen unsigned compound results.
|
|||
/// </summary>
|
|||
private static void StoreCompoundVectors( |
|||
Vector128<int> result0, |
|||
Vector128<int> result1, |
|||
Vector128<int> result2, |
|||
Vector128<int> result3, |
|||
ref ushort destination, |
|||
int destinationOffset, |
|||
int width) |
|||
{ |
|||
Vector128<ushort> lower = Av1IntraPredictorBase.Narrow(result0, result1).AsUInt16(); |
|||
Vector128<ushort> upper = Av1IntraPredictorBase.Narrow(result2, result3).AsUInt16(); |
|||
ref ushort destinationStart = ref Unsafe.Add(ref destination, destinationOffset); |
|||
StoreCompoundVectors(lower, upper, ref destinationStart, width); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores up to sixteen packed compound results without crossing the logical block edge.
|
|||
/// </summary>
|
|||
private static void StoreCompoundVectors( |
|||
Vector128<ushort> lower, |
|||
Vector128<ushort> upper, |
|||
ref ushort destination, |
|||
int width) |
|||
{ |
|||
int lowerWidth = Math.Min(width, Vector128<ushort>.Count); |
|||
StorePartial(lower, ref destination, lowerWidth); |
|||
if (width > Vector128<ushort>.Count) |
|||
{ |
|||
StorePartial( |
|||
upper, |
|||
ref Unsafe.Add(ref destination, Vector128<ushort>.Count), |
|||
width - Vector128<ushort>.Count); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the selected interpolation kernel for compound traversal.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<short> GetCompoundCoefficients( |
|||
Av1InterpolationFilter filter, |
|||
int phase, |
|||
bool useReducedFilter) |
|||
=> filter switch |
|||
{ |
|||
Av1InterpolationFilter.Regular => RegularOperator.GetCoefficients(phase, useReducedFilter), |
|||
Av1InterpolationFilter.Smooth => SmoothOperator.GetCoefficients(phase, useReducedFilter), |
|||
Av1InterpolationFilter.Sharp => SharpOperator.GetCoefficients(phase, useReducedFilter), |
|||
_ => BilinearOperator.GetCoefficients(phase, useReducedFilter), |
|||
}; |
|||
} |
|||
@ -1,105 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines sample-storage operators for reference-scaled prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1InterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Supplies sample loading, clipping, and storage for a scaled predictor pipeline.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The native sample storage type.</typeparam>
|
|||
private interface IScaledSampleOperator<T> |
|||
where T : unmanaged |
|||
{ |
|||
/// <summary>
|
|||
/// Loads one source sample as a signed accumulator value.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <param name="index">The sample offset.</param>
|
|||
/// <returns>The widened sample value.</returns>
|
|||
public static abstract int Load(ref T source, int index); |
|||
|
|||
/// <summary>
|
|||
/// Clips and stores eight completed vector lanes.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
/// <param name="index">The output offset.</param>
|
|||
/// <param name="result0">The first four completed lanes.</param>
|
|||
/// <param name="result1">The second four completed lanes.</param>
|
|||
/// <param name="bitDepth">The decoded sample precision.</param>
|
|||
public static abstract void StoreVector( |
|||
ref T destination, |
|||
int index, |
|||
Vector128<int> result0, |
|||
Vector128<int> result1, |
|||
int bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Clips and stores one completed scalar value.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
/// <param name="index">The output offset.</param>
|
|||
/// <param name="value">The completed sample value.</param>
|
|||
/// <param name="bitDepth">The decoded sample precision.</param>
|
|||
public static abstract void StoreScalar(ref T destination, int index, int value, int bitDepth); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements scaled prediction storage for 8-bit samples.
|
|||
/// </summary>
|
|||
private readonly struct ScaledByteOperator : IScaledSampleOperator<byte> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Load(ref byte source, int index) => Unsafe.Add(ref source, index); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void StoreVector( |
|||
ref byte destination, |
|||
int index, |
|||
Vector128<int> result0, |
|||
Vector128<int> result1, |
|||
int bitDepth) |
|||
=> PackBytes(result0, result1, Vector128<int>.Zero, Vector128<int>.Zero) |
|||
.GetLower() |
|||
.StoreUnsafe(ref destination, (nuint)index); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void StoreScalar(ref byte destination, int index, int value, int bitDepth) |
|||
=> Unsafe.Add(ref destination, index) = (byte)Math.Clamp(value, byte.MinValue, byte.MaxValue); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements scaled prediction storage for 8-, 10-, and 12-bit samples.
|
|||
/// </summary>
|
|||
private readonly struct ScaledUInt16Operator : IScaledSampleOperator<ushort> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Load(ref ushort source, int index) => Unsafe.Add(ref source, index); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void StoreVector( |
|||
ref ushort destination, |
|||
int index, |
|||
Vector128<int> result0, |
|||
Vector128<int> result1, |
|||
int bitDepth) |
|||
=> PackHighBitDepth(result0, result1, (1 << bitDepth) - 1).StoreUnsafe(ref destination, (nuint)index); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void StoreScalar(ref ushort destination, int index, int value, int bitDepth) |
|||
=> Unsafe.Add(ref destination, index) = (ushort)Math.Clamp(value, 0, (1 << bitDepth) - 1); |
|||
} |
|||
} |
|||
@ -1,730 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Reconstructs local and global affine warped-motion prediction blocks.
|
|||
/// </content>
|
|||
internal static partial class Av1InterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The number of rows in one warped filter's horizontal intermediate tile.
|
|||
/// </summary>
|
|||
private const int WarpedIntermediateRows = 15; |
|||
|
|||
/// <summary>
|
|||
/// The number of columns in one warped filter tile.
|
|||
/// </summary>
|
|||
private const int WarpedTileSize = 8; |
|||
|
|||
/// <summary>
|
|||
/// The number of low model bits removed when addressing the warped filter table.
|
|||
/// </summary>
|
|||
private const int WarpedDifferencePrecisionBits = 10; |
|||
|
|||
/// <summary>
|
|||
/// The number of fractional positions in one warped pixel.
|
|||
/// </summary>
|
|||
private const int WarpedPixelPrecisionShifts = 64; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of signed 16-bit elements required by warped prediction.
|
|||
/// </summary>
|
|||
public const int WarpedScratchLength = WarpedIntermediateRows * WarpedTileSize; |
|||
|
|||
/// <summary>
|
|||
/// Supplies the horizontal and vertical eight-tap dot products for one execution width.
|
|||
/// </summary>
|
|||
private interface IWarpedConvolution |
|||
{ |
|||
/// <summary>
|
|||
/// Convolves eight adjacent unsigned byte samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <returns>The sum of the eight sample-coefficient products.</returns>
|
|||
public static abstract int Convolve(ref byte source, ref short coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves eight adjacent unsigned high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <returns>The sum of the eight sample-coefficient products.</returns>
|
|||
public static abstract int Convolve(ref ushort source, ref short coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves eight vertically strided unsigned intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first intermediate sample.</param>
|
|||
/// <param name="stride">The distance between intermediate rows.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <returns>The sum of the eight sample-coefficient products.</returns>
|
|||
public static abstract int ConvolveVertical(ref ushort source, int stride, ref short coefficients); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit affine warped prediction using the widest supported convolution operator.
|
|||
/// </summary>
|
|||
public static void PredictWarped( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
PredictWarped<WarpedVector128Convolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PredictWarped<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit affine warped reference into AV1's unsigned compound intermediate format.
|
|||
/// </summary>
|
|||
public static void PredictWarpedCompound( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
PredictWarpedCompound<WarpedVector128Convolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PredictWarpedCompound<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth affine warped prediction using the widest supported convolution operator.
|
|||
/// </summary>
|
|||
public static void PredictWarped( |
|||
ReadOnlySpan<ushort> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
int bitDepth, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
PredictWarped<WarpedVector128Convolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
bitDepth, |
|||
parameters, |
|||
scratch); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PredictWarped<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
bitDepth, |
|||
parameters, |
|||
scratch); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit affine warped prediction without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictWarpedScalar( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
=> PredictWarped<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit affine warped reference into compound intermediates without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictWarpedCompoundScalar( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
=> PredictWarpedCompound<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth affine warped prediction without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictWarpedScalar( |
|||
ReadOnlySpan<ushort> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
int bitDepth, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
=> PredictWarped<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
bitDepth, |
|||
parameters, |
|||
scratch); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one 8-bit warped block through a closed convolution operator.
|
|||
/// </summary>
|
|||
private static void PredictWarped<TConvolution>( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
where TConvolution : struct, IWarpedConvolution |
|||
{ |
|||
ref byte sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Span<ushort> intermediate = MemoryMarshal.Cast<short, ushort>(scratch)[..WarpedScratchLength]; |
|||
int horizontalBias = 1 << (8 + FilterBits - 1); |
|||
int verticalBias = 1 << (8 + (2 * FilterBits) - Round0Bits); |
|||
int verticalRound = (2 * FilterBits) - Round0Bits; |
|||
|
|||
for (int tileRow = destinationPosition.Y; tileRow < destinationPosition.Y + height; tileRow += WarpedTileSize) |
|||
{ |
|||
for (int tileColumn = destinationPosition.X; tileColumn < destinationPosition.X + width; tileColumn += WarpedTileSize) |
|||
{ |
|||
DeriveWarpedTilePosition( |
|||
parameters, |
|||
tileColumn, |
|||
tileRow, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
out int integerX, |
|||
out int integerY, |
|||
out int phaseX, |
|||
out int phaseY); |
|||
|
|||
for (int row = -7; row < 8; row++) |
|||
{ |
|||
int sourceY = Math.Clamp(integerY + row, 0, sourceHeight - 1); |
|||
int phase = phaseX + (parameters.Beta * (row + 4)); |
|||
for (int column = -4; column < 4; column++) |
|||
{ |
|||
int sourceX = integerX + column - 3; |
|||
int sourceIndex = ((sourceOrigin.Y + sourceY) * sourceStride) + sourceOrigin.X + sourceX; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = horizontalBias + TConvolution.Convolve(ref Unsafe.Add(ref sourceBase, sourceIndex), ref coefficients); |
|||
intermediate[((row + 7) * WarpedTileSize) + column + 4] = (ushort)RoundPowerOfTwoScalar(sum, Round0Bits); |
|||
phase += parameters.Alpha; |
|||
} |
|||
} |
|||
|
|||
int tileHeight = Math.Min(WarpedTileSize, destinationPosition.Y + height - tileRow); |
|||
int tileWidth = Math.Min(WarpedTileSize, destinationPosition.X + width - tileColumn); |
|||
for (int row = 0; row < tileHeight; row++) |
|||
{ |
|||
int phase = phaseY + (parameters.Delta * row); |
|||
int destinationRowOffset = (tileRow - destinationPosition.Y + row) * destinationStride; |
|||
for (int column = 0; column < tileWidth; column++) |
|||
{ |
|||
ref ushort intermediateSource = ref intermediate[(row * WarpedTileSize) + column]; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = verticalBias + TConvolution.ConvolveVertical(ref intermediateSource, WarpedTileSize, ref coefficients); |
|||
int value = RoundPowerOfTwoScalar(sum, verticalRound) - (1 << 7) - (1 << 8); |
|||
Unsafe.Add(ref destinationBase, destinationRowOffset + tileColumn - destinationPosition.X + column) = |
|||
(byte)Math.Clamp(value, byte.MinValue, byte.MaxValue); |
|||
|
|||
phase += parameters.Gamma; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one 8-bit warped reference without discarding the compound convolution precision.
|
|||
/// </summary>
|
|||
private static void PredictWarpedCompound<TConvolution>( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
where TConvolution : struct, IWarpedConvolution |
|||
{ |
|||
ref byte sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Span<ushort> intermediate = MemoryMarshal.Cast<short, ushort>(scratch)[..WarpedScratchLength]; |
|||
int horizontalBias = 1 << (8 + FilterBits - 1); |
|||
int verticalBias = 1 << (8 + (2 * FilterBits) - Round0Bits); |
|||
|
|||
for (int tileRow = destinationPosition.Y; tileRow < destinationPosition.Y + height; tileRow += WarpedTileSize) |
|||
{ |
|||
for (int tileColumn = destinationPosition.X; tileColumn < destinationPosition.X + width; tileColumn += WarpedTileSize) |
|||
{ |
|||
DeriveWarpedTilePosition( |
|||
parameters, |
|||
tileColumn, |
|||
tileRow, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
out int integerX, |
|||
out int integerY, |
|||
out int phaseX, |
|||
out int phaseY); |
|||
|
|||
for (int row = -7; row < 8; row++) |
|||
{ |
|||
int sourceY = Math.Clamp(integerY + row, 0, sourceHeight - 1); |
|||
int phase = phaseX + (parameters.Beta * (row + 4)); |
|||
for (int column = -4; column < 4; column++) |
|||
{ |
|||
int sourceX = integerX + column - 3; |
|||
int sourceIndex = ((sourceOrigin.Y + sourceY) * sourceStride) + sourceOrigin.X + sourceX; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = horizontalBias + TConvolution.Convolve(ref Unsafe.Add(ref sourceBase, sourceIndex), ref coefficients); |
|||
intermediate[((row + 7) * WarpedTileSize) + column + 4] = |
|||
(ushort)RoundPowerOfTwoScalar(sum, Round0Bits); |
|||
|
|||
phase += parameters.Alpha; |
|||
} |
|||
} |
|||
|
|||
int tileHeight = Math.Min(WarpedTileSize, destinationPosition.Y + height - tileRow); |
|||
int tileWidth = Math.Min(WarpedTileSize, destinationPosition.X + width - tileColumn); |
|||
for (int row = 0; row < tileHeight; row++) |
|||
{ |
|||
int phase = phaseY + (parameters.Delta * row); |
|||
int destinationRowOffset = (tileRow - destinationPosition.Y + row) * destinationStride; |
|||
for (int column = 0; column < tileWidth; column++) |
|||
{ |
|||
ref ushort intermediateSource = ref intermediate[(row * WarpedTileSize) + column]; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = verticalBias + TConvolution.ConvolveVertical(ref intermediateSource, WarpedTileSize, ref coefficients); |
|||
Unsafe.Add(ref destinationBase, destinationRowOffset + tileColumn - destinationPosition.X + column) = |
|||
(ushort)RoundPowerOfTwoScalar(sum, CompoundRound1Bits); |
|||
|
|||
phase += parameters.Gamma; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one high-bit-depth warped block through a closed convolution operator.
|
|||
/// </summary>
|
|||
private static void PredictWarped<TConvolution>( |
|||
ReadOnlySpan<ushort> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
int bitDepth, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
where TConvolution : struct, IWarpedConvolution |
|||
{ |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Span<ushort> intermediate = MemoryMarshal.Cast<short, ushort>(scratch)[..WarpedScratchLength]; |
|||
|
|||
// Twelve-bit prediction increases round0 by two so the biased horizontal intermediate remains representable
|
|||
// in sixteen bits. Reducing round1 by the same amount preserves the complete normative Q14 shift.
|
|||
int intermediateRange = bitDepth + FilterBits - Round0Bits + 2; |
|||
int round0 = Round0Bits + Math.Max(intermediateRange - 16, 0); |
|||
int verticalRound = (2 * FilterBits) - round0; |
|||
int horizontalBias = 1 << (bitDepth + FilterBits - 1); |
|||
int verticalBias = 1 << (bitDepth + (2 * FilterBits) - round0); |
|||
int maximum = (1 << bitDepth) - 1; |
|||
|
|||
for (int tileRow = destinationPosition.Y; tileRow < destinationPosition.Y + height; tileRow += WarpedTileSize) |
|||
{ |
|||
for (int tileColumn = destinationPosition.X; tileColumn < destinationPosition.X + width; tileColumn += WarpedTileSize) |
|||
{ |
|||
DeriveWarpedTilePosition( |
|||
parameters, |
|||
tileColumn, |
|||
tileRow, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
out int integerX, |
|||
out int integerY, |
|||
out int phaseX, |
|||
out int phaseY); |
|||
|
|||
for (int row = -7; row < 8; row++) |
|||
{ |
|||
int sourceY = Math.Clamp(integerY + row, 0, sourceHeight - 1); |
|||
int phase = phaseX + (parameters.Beta * (row + 4)); |
|||
for (int column = -4; column < 4; column++) |
|||
{ |
|||
int sourceX = integerX + column - 3; |
|||
int sourceIndex = ((sourceOrigin.Y + sourceY) * sourceStride) + sourceOrigin.X + sourceX; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = horizontalBias + TConvolution.Convolve(ref Unsafe.Add(ref sourceBase, sourceIndex), ref coefficients); |
|||
intermediate[((row + 7) * WarpedTileSize) + column + 4] = (ushort)RoundPowerOfTwoScalar(sum, round0); |
|||
phase += parameters.Alpha; |
|||
} |
|||
} |
|||
|
|||
int tileHeight = Math.Min(WarpedTileSize, destinationPosition.Y + height - tileRow); |
|||
int tileWidth = Math.Min(WarpedTileSize, destinationPosition.X + width - tileColumn); |
|||
for (int row = 0; row < tileHeight; row++) |
|||
{ |
|||
int phase = phaseY + (parameters.Delta * row); |
|||
int destinationRowOffset = (tileRow - destinationPosition.Y + row) * destinationStride; |
|||
for (int column = 0; column < tileWidth; column++) |
|||
{ |
|||
ref ushort intermediateSource = ref intermediate[(row * WarpedTileSize) + column]; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = verticalBias + TConvolution.ConvolveVertical(ref intermediateSource, WarpedTileSize, ref coefficients); |
|||
int value = RoundPowerOfTwoScalar(sum, verticalRound) - (1 << (bitDepth - 1)) - (1 << bitDepth); |
|||
Unsafe.Add(ref destinationBase, destinationRowOffset + tileColumn - destinationPosition.X + column) = |
|||
(ushort)Math.Clamp(value, 0, maximum); |
|||
|
|||
phase += parameters.Gamma; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects the center of one 8x8 output tile and derives its integer source position and reduced phases.
|
|||
/// </summary>
|
|||
private static void DeriveWarpedTilePosition( |
|||
Av1GlobalMotionParameters parameters, |
|||
int tileColumn, |
|||
int tileRow, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
out int integerX, |
|||
out int integerY, |
|||
out int phaseX, |
|||
out int phaseY) |
|||
{ |
|||
int sourceX = (tileColumn + 4) << subsamplingX; |
|||
int sourceY = (tileRow + 4) << subsamplingY; |
|||
long projectedX = ((long)parameters[2] * sourceX) + ((long)parameters[3] * sourceY) + parameters[0]; |
|||
long projectedY = ((long)parameters[4] * sourceX) + ((long)parameters[5] * sourceY) + parameters[1]; |
|||
long planeX = projectedX >> subsamplingX; |
|||
long planeY = projectedY >> subsamplingY; |
|||
integerX = (int)(planeX >> Av1GlobalMotionParameters.ModelPrecisionBits); |
|||
integerY = (int)(planeY >> Av1GlobalMotionParameters.ModelPrecisionBits); |
|||
phaseX = (int)planeX & (Av1GlobalMotionParameters.ModelScale - 1); |
|||
phaseY = (int)planeY & (Av1GlobalMotionParameters.ModelScale - 1); |
|||
phaseX += (-4 * parameters.Alpha) + (-4 * parameters.Beta); |
|||
phaseY += (-4 * parameters.Gamma) + (-4 * parameters.Delta); |
|||
|
|||
// Shear parameters are quantized to 64-model-unit steps. Clearing the same low bits after the tile-center
|
|||
// projection keeps negative and positive phases on the exact filter-table grid used by the bitstream model.
|
|||
phaseX &= -1 << 6; |
|||
phaseY &= -1 << 6; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a reference to the first coefficient for one reduced warped-filter phase.
|
|||
/// </summary>
|
|||
private static ref short GetWarpedFilterReference(int phase) |
|||
{ |
|||
int filterIndex = ((phase + (1 << (WarpedDifferencePrecisionBits - 1))) >> WarpedDifferencePrecisionBits) + |
|||
WarpedPixelPrecisionShifts; |
|||
|
|||
return ref Unsafe.Add(ref MemoryMarshal.GetReference(WarpedFilter), filterIndex * FilterCoefficientCount); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a nonnegative value by a power of two with nearest-integer rounding.
|
|||
/// </summary>
|
|||
private static int RoundPowerOfTwoScalar(int value, int bitCount) |
|||
=> (value + (1 << (bitCount - 1))) >> bitCount; |
|||
|
|||
/// <summary>
|
|||
/// Executes warped dot products with portable 128-bit SIMD.
|
|||
/// </summary>
|
|||
private readonly struct WarpedVector128Convolution : IWarpedConvolution |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Convolve(ref byte source, ref short coefficients) |
|||
{ |
|||
Vector128<byte> packed = Vector128.LoadUnsafe(ref source); |
|||
(Vector128<ushort> samples, _) = Vector128.Widen(packed); |
|||
return MultiplyAndSum(samples, Vector128.LoadUnsafe(ref coefficients)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int Convolve(ref ushort source, ref short coefficients) |
|||
=> MultiplyAndSum(Vector128.LoadUnsafe(ref source), Vector128.LoadUnsafe(ref coefficients)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int ConvolveVertical(ref ushort source, int stride, ref short coefficients) |
|||
{ |
|||
Vector128<ushort> samples = Vector128.Create( |
|||
source, |
|||
Unsafe.Add(ref source, stride), |
|||
Unsafe.Add(ref source, stride * 2), |
|||
Unsafe.Add(ref source, stride * 3), |
|||
Unsafe.Add(ref source, stride * 4), |
|||
Unsafe.Add(ref source, stride * 5), |
|||
Unsafe.Add(ref source, stride * 6), |
|||
Unsafe.Add(ref source, stride * 7)); |
|||
|
|||
return MultiplyAndSum(samples, Vector128.LoadUnsafe(ref coefficients)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Widens unsigned samples and signed coefficients before accumulating their exact 32-bit products.
|
|||
/// </summary>
|
|||
private static int MultiplyAndSum(Vector128<ushort> samples, Vector128<short> coefficients) |
|||
{ |
|||
(Vector128<uint> sampleLower, Vector128<uint> sampleUpper) = Vector128.Widen(samples); |
|||
(Vector128<int> coefficientLower, Vector128<int> coefficientUpper) = Vector128.Widen(coefficients); |
|||
return Vector128.Sum(sampleLower.AsInt32() * coefficientLower) + |
|||
Vector128.Sum(sampleUpper.AsInt32() * coefficientUpper); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Executes warped dot products without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
private readonly struct WarpedScalarConvolution : IWarpedConvolution |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Convolve(ref byte source, ref short coefficients) |
|||
{ |
|||
int sum = 0; |
|||
for (nuint index = 0; index < FilterCoefficientCount; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref source, index) * Unsafe.Add(ref coefficients, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int Convolve(ref ushort source, ref short coefficients) |
|||
{ |
|||
int sum = 0; |
|||
for (nuint index = 0; index < FilterCoefficientCount; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref source, index) * Unsafe.Add(ref coefficients, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int ConvolveVertical(ref ushort source, int stride, ref short coefficients) |
|||
{ |
|||
int sum = 0; |
|||
for (int index = 0; index < FilterCoefficientCount; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref source, index * stride) * Unsafe.Add(ref coefficients, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,364 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines variable-phase reference-scaled prediction arithmetic.
|
|||
/// </content>
|
|||
internal static partial class Av1ScaledInterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines variable-phase scaled prediction for one native sample storage type.
|
|||
/// </summary>
|
|||
private interface IAv1ScaledPredictionOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Loads one native source sample as a signed accumulator value.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The native sample storage type.</typeparam>
|
|||
/// <param name="source">The first native source sample.</param>
|
|||
/// <param name="index">The source sample offset.</param>
|
|||
/// <returns>The widened sample.</returns>
|
|||
public static abstract int Load<T>(ref T source, int index) |
|||
where T : unmanaged; |
|||
|
|||
/// <summary>
|
|||
/// Accumulates one sample-coefficient product.
|
|||
/// </summary>
|
|||
/// <param name="accumulator">The current convolution sum.</param>
|
|||
/// <param name="sample">The source sample.</param>
|
|||
/// <param name="coefficient">The signed Q7 coefficient.</param>
|
|||
/// <returns>The updated convolution sum.</returns>
|
|||
public static abstract int MultiplyAdd(int accumulator, int sample, int coefficient); |
|||
|
|||
/// <summary>
|
|||
/// Accumulates four independent sample-coefficient products.
|
|||
/// </summary>
|
|||
/// <param name="accumulator">The current convolution sums.</param>
|
|||
/// <param name="samples">The source samples.</param>
|
|||
/// <param name="coefficients">The signed Q7 coefficients.</param>
|
|||
/// <returns>The updated convolution sums.</returns>
|
|||
public static abstract Vector128<int> MultiplyAdd(Vector128<int> accumulator, Vector128<int> samples, Vector128<int> coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Accumulates eight independent sample-coefficient products.
|
|||
/// </summary>
|
|||
/// <param name="accumulator">The current convolution sums.</param>
|
|||
/// <param name="samples">The source samples.</param>
|
|||
/// <param name="coefficients">The signed Q7 coefficients.</param>
|
|||
/// <returns>The updated convolution sums.</returns>
|
|||
public static abstract Vector256<int> MultiplyAdd(Vector256<int> accumulator, Vector256<int> samples, Vector256<int> coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Accumulates sixteen independent sample-coefficient products.
|
|||
/// </summary>
|
|||
/// <param name="accumulator">The current convolution sums.</param>
|
|||
/// <param name="samples">The source samples.</param>
|
|||
/// <param name="coefficients">The signed Q7 coefficients.</param>
|
|||
/// <returns>The updated convolution sums.</returns>
|
|||
public static abstract Vector512<int> MultiplyAdd(Vector512<int> accumulator, Vector512<int> samples, Vector512<int> coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves one intermediate sample column without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="source">The first intermediate sample.</param>
|
|||
/// <param name="sourceStride">The distance between intermediate rows.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <param name="coefficientCount">The number of active coefficients.</param>
|
|||
/// <returns>The exact convolution sum.</returns>
|
|||
public static abstract int Convolve(ref short source, int sourceStride, ref short coefficients, int coefficientCount); |
|||
|
|||
/// <summary>
|
|||
/// Convolves eight adjacent intermediate samples through a 128-bit lane group.
|
|||
/// </summary>
|
|||
/// <param name="source">The first intermediate sample.</param>
|
|||
/// <param name="sourceStride">The distance between intermediate rows.</param>
|
|||
/// <param name="sourceOffset">The first column offset.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <param name="coefficientCount">The number of active coefficients.</param>
|
|||
/// <param name="initial">The initial convolution bias.</param>
|
|||
/// <param name="result0">Receives the first four completed sums.</param>
|
|||
/// <param name="result1">Receives the next four completed sums.</param>
|
|||
public static abstract void Convolve( |
|||
ref short source, |
|||
int sourceStride, |
|||
nuint sourceOffset, |
|||
ref short coefficients, |
|||
int coefficientCount, |
|||
Vector128<int> initial, |
|||
out Vector128<int> result0, |
|||
out Vector128<int> result1); |
|||
|
|||
/// <summary>
|
|||
/// Convolves sixteen adjacent intermediate samples through a 256-bit lane group.
|
|||
/// </summary>
|
|||
/// <param name="source">The first intermediate sample.</param>
|
|||
/// <param name="sourceStride">The distance between intermediate rows.</param>
|
|||
/// <param name="sourceOffset">The first column offset.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <param name="coefficientCount">The number of active coefficients.</param>
|
|||
/// <param name="initial">The initial convolution bias.</param>
|
|||
/// <param name="result0">Receives the first eight completed sums.</param>
|
|||
/// <param name="result1">Receives the next eight completed sums.</param>
|
|||
public static abstract void Convolve( |
|||
ref short source, |
|||
int sourceStride, |
|||
nuint sourceOffset, |
|||
ref short coefficients, |
|||
int coefficientCount, |
|||
Vector256<int> initial, |
|||
out Vector256<int> result0, |
|||
out Vector256<int> result1); |
|||
|
|||
/// <summary>
|
|||
/// Convolves thirty-two adjacent intermediate samples through a 512-bit lane group.
|
|||
/// </summary>
|
|||
/// <param name="source">The first intermediate sample.</param>
|
|||
/// <param name="sourceStride">The distance between intermediate rows.</param>
|
|||
/// <param name="sourceOffset">The first column offset.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <param name="coefficientCount">The number of active coefficients.</param>
|
|||
/// <param name="initial">The initial convolution bias.</param>
|
|||
/// <param name="result0">Receives the first sixteen completed sums.</param>
|
|||
/// <param name="result1">Receives the next sixteen completed sums.</param>
|
|||
public static abstract void Convolve( |
|||
ref short source, |
|||
int sourceStride, |
|||
nuint sourceOffset, |
|||
ref short coefficients, |
|||
int coefficientCount, |
|||
Vector512<int> initial, |
|||
out Vector512<int> result0, |
|||
out Vector512<int> result1); |
|||
|
|||
/// <summary>
|
|||
/// Clips and stores one completed prediction.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The native sample storage type.</typeparam>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
/// <param name="index">The destination offset.</param>
|
|||
/// <param name="value">The completed prediction.</param>
|
|||
/// <param name="bitDepth">The decoded sample precision.</param>
|
|||
public static abstract void Store<T>(ref T destination, int index, int value, int bitDepth) |
|||
where T : unmanaged; |
|||
|
|||
/// <summary>
|
|||
/// Clips and stores eight completed predictions.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The native sample storage type.</typeparam>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
/// <param name="index">The destination offset.</param>
|
|||
/// <param name="result0">The first four completed predictions.</param>
|
|||
/// <param name="result1">The next four completed predictions.</param>
|
|||
/// <param name="bitDepth">The decoded sample precision.</param>
|
|||
public static abstract void Store<T>(ref T destination, int index, Vector128<int> result0, Vector128<int> result1, int bitDepth) |
|||
where T : unmanaged; |
|||
|
|||
/// <summary>
|
|||
/// Clips and stores sixteen completed predictions.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The native sample storage type.</typeparam>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
/// <param name="index">The destination offset.</param>
|
|||
/// <param name="result0">The first eight completed predictions.</param>
|
|||
/// <param name="result1">The next eight completed predictions.</param>
|
|||
/// <param name="bitDepth">The decoded sample precision.</param>
|
|||
public static abstract void Store<T>(ref T destination, int index, Vector256<int> result0, Vector256<int> result1, int bitDepth) |
|||
where T : unmanaged; |
|||
|
|||
/// <summary>
|
|||
/// Clips and stores thirty-two completed predictions.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The native sample storage type.</typeparam>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
/// <param name="index">The destination offset.</param>
|
|||
/// <param name="result0">The first sixteen completed predictions.</param>
|
|||
/// <param name="result1">The next sixteen completed predictions.</param>
|
|||
/// <param name="bitDepth">The decoded sample precision.</param>
|
|||
public static abstract void Store<T>(ref T destination, int index, Vector512<int> result0, Vector512<int> result1, int bitDepth) |
|||
where T : unmanaged; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements variable-phase scaled prediction for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct ScaledOperator : IAv1ScaledPredictionOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Load<T>(ref T source, int index) |
|||
where T : unmanaged |
|||
{ |
|||
// The only closed forms are byte and ushort. The JIT removes this storage choice from each specialization,
|
|||
// leaving the shared variable-phase traversal free of duplicate 8-bit and high-bit-depth implementations.
|
|||
if (typeof(T) == typeof(byte)) |
|||
{ |
|||
return Unsafe.Add(ref Unsafe.As<T, byte>(ref source), index); |
|||
} |
|||
|
|||
return Unsafe.Add(ref Unsafe.As<T, ushort>(ref source), index); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int MultiplyAdd(int accumulator, int sample, int coefficient) |
|||
=> accumulator + (sample * coefficient); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> MultiplyAdd(Vector128<int> accumulator, Vector128<int> samples, Vector128<int> coefficients) |
|||
=> accumulator + (samples * coefficients); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<int> MultiplyAdd(Vector256<int> accumulator, Vector256<int> samples, Vector256<int> coefficients) |
|||
=> accumulator + (samples * coefficients); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<int> MultiplyAdd(Vector512<int> accumulator, Vector512<int> samples, Vector512<int> coefficients) |
|||
=> accumulator + (samples * coefficients); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Convolve(ref short source, int sourceStride, ref short coefficients, int coefficientCount) |
|||
=> ConvolveScalar(ref source, sourceStride, ref coefficients, coefficientCount); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convolve( |
|||
ref short source, |
|||
int sourceStride, |
|||
nuint sourceOffset, |
|||
ref short coefficients, |
|||
int coefficientCount, |
|||
Vector128<int> initial, |
|||
out Vector128<int> result0, |
|||
out Vector128<int> result1) |
|||
=> Av1InterPredictor.Convolve( |
|||
ref source, |
|||
sourceStride, |
|||
sourceOffset, |
|||
ref coefficients, |
|||
coefficientCount, |
|||
initial, |
|||
out result0, |
|||
out result1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convolve( |
|||
ref short source, |
|||
int sourceStride, |
|||
nuint sourceOffset, |
|||
ref short coefficients, |
|||
int coefficientCount, |
|||
Vector256<int> initial, |
|||
out Vector256<int> result0, |
|||
out Vector256<int> result1) |
|||
=> Av1InterPredictor.Convolve( |
|||
ref source, |
|||
sourceStride, |
|||
sourceOffset, |
|||
ref coefficients, |
|||
coefficientCount, |
|||
initial, |
|||
out result0, |
|||
out result1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convolve( |
|||
ref short source, |
|||
int sourceStride, |
|||
nuint sourceOffset, |
|||
ref short coefficients, |
|||
int coefficientCount, |
|||
Vector512<int> initial, |
|||
out Vector512<int> result0, |
|||
out Vector512<int> result1) |
|||
=> Av1InterPredictor.Convolve( |
|||
ref source, |
|||
sourceStride, |
|||
sourceOffset, |
|||
ref coefficients, |
|||
coefficientCount, |
|||
initial, |
|||
out result0, |
|||
out result1); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Store<T>(ref T destination, int index, int value, int bitDepth) |
|||
where T : unmanaged |
|||
{ |
|||
if (typeof(T) == typeof(byte)) |
|||
{ |
|||
Unsafe.Add(ref Unsafe.As<T, byte>(ref destination), index) = (byte)Math.Clamp(value, byte.MinValue, byte.MaxValue); |
|||
return; |
|||
} |
|||
|
|||
Unsafe.Add(ref Unsafe.As<T, ushort>(ref destination), index) = (ushort)Math.Clamp(value, 0, (1 << bitDepth) - 1); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Store<T>(ref T destination, int index, Vector128<int> result0, Vector128<int> result1, int bitDepth) |
|||
where T : unmanaged |
|||
{ |
|||
if (typeof(T) == typeof(byte)) |
|||
{ |
|||
PackBytes(result0, result1, Vector128<int>.Zero, Vector128<int>.Zero) |
|||
.GetLower() |
|||
.StoreUnsafe(ref Unsafe.As<T, byte>(ref destination), (nuint)index); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PackHighBitDepth(result0, result1, (1 << bitDepth) - 1) |
|||
.StoreUnsafe(ref Unsafe.As<T, ushort>(ref destination), (nuint)index); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Store<T>(ref T destination, int index, Vector256<int> result0, Vector256<int> result1, int bitDepth) |
|||
where T : unmanaged |
|||
{ |
|||
if (typeof(T) == typeof(byte)) |
|||
{ |
|||
PackBytes(result0, result1, Vector256<int>.Zero, Vector256<int>.Zero) |
|||
.GetLower() |
|||
.StoreUnsafe(ref Unsafe.As<T, byte>(ref destination), (nuint)index); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PackHighBitDepth(result0, result1, (1 << bitDepth) - 1) |
|||
.StoreUnsafe(ref Unsafe.As<T, ushort>(ref destination), (nuint)index); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Store<T>(ref T destination, int index, Vector512<int> result0, Vector512<int> result1, int bitDepth) |
|||
where T : unmanaged |
|||
{ |
|||
if (typeof(T) == typeof(byte)) |
|||
{ |
|||
PackBytes(result0, result1, Vector512<int>.Zero, Vector512<int>.Zero) |
|||
.GetLower() |
|||
.StoreUnsafe(ref Unsafe.As<T, byte>(ref destination), (nuint)index); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PackHighBitDepth(result0, result1, (1 << bitDepth) - 1) |
|||
.StoreUnsafe(ref Unsafe.As<T, ushort>(ref destination), (nuint)index); |
|||
} |
|||
} |
|||
} |
|||
@ -1,12 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Provides the normative Q7 filter kernels for affine warped-motion prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1InterPredictor |
|||
internal static partial class Av1WarpedInterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the 193 consecutive eight-tap warped-filter phases spanning fractional positions [-1, 2].
|
|||
@ -0,0 +1,141 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter.Av1InterPredictor; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Defines affine warped-motion prediction arithmetic.
|
|||
/// </content>
|
|||
internal static partial class Av1WarpedInterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the affine warped-motion dot product for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Each SIMD overload contains consecutive independent eight-tap filters. The generic warped traversal
|
|||
/// gathers source windows and coefficient phases, while the closed operator owns the exact multiply-and-sum arithmetic.
|
|||
/// </remarks>
|
|||
private interface IAv1WarpedPredictionOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Convolves one eight-sample window without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <param name="sourceStride">The distance between source samples.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <returns>The exact dot product.</returns>
|
|||
public static abstract int Convolve(ref byte source, int sourceStride, ref short coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves one high-bit-depth eight-sample window without hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <param name="sourceStride">The distance between source samples.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <returns>The exact dot product.</returns>
|
|||
public static abstract int Convolve(ref ushort source, int sourceStride, ref short coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves one packed eight-sample window.
|
|||
/// </summary>
|
|||
/// <param name="samples">The unsigned samples.</param>
|
|||
/// <param name="coefficients">The signed Q7 coefficients.</param>
|
|||
/// <returns>The exact dot product.</returns>
|
|||
public static abstract int Convolve(Vector128<ushort> samples, Vector128<short> coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves two packed eight-sample windows.
|
|||
/// </summary>
|
|||
/// <param name="samples">The two unsigned sample windows.</param>
|
|||
/// <param name="coefficients">The two signed Q7 coefficient windows.</param>
|
|||
/// <returns>The two exact dot products in the low lanes.</returns>
|
|||
public static abstract Vector128<int> Convolve(Vector256<ushort> samples, Vector256<short> coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves four packed eight-sample windows.
|
|||
/// </summary>
|
|||
/// <param name="samples">The four unsigned sample windows.</param>
|
|||
/// <param name="coefficients">The four signed Q7 coefficient windows.</param>
|
|||
/// <returns>The four exact dot products.</returns>
|
|||
public static abstract Vector128<int> Convolve(Vector512<ushort> samples, Vector512<short> coefficients); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements the affine warped-motion dot product for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private readonly struct WarpedOperator : IAv1WarpedPredictionOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Convolve(ref byte source, int sourceStride, ref short coefficients) |
|||
{ |
|||
int sum = 0; |
|||
for (int index = 0; index < FilterCoefficientCount; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref source, index * sourceStride) * Unsafe.Add(ref coefficients, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Convolve(ref ushort source, int sourceStride, ref short coefficients) |
|||
{ |
|||
int sum = 0; |
|||
for (int index = 0; index < FilterCoefficientCount; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref source, index * sourceStride) * Unsafe.Add(ref coefficients, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Convolve(Vector128<ushort> samples, Vector128<short> coefficients) |
|||
{ |
|||
(Vector128<uint> sampleLower, Vector128<uint> sampleUpper) = Vector128.Widen(samples); |
|||
(Vector128<int> coefficientLower, Vector128<int> coefficientUpper) = Vector128.Widen(coefficients); |
|||
return Vector128.Sum(sampleLower.AsInt32() * coefficientLower) + |
|||
Vector128.Sum(sampleUpper.AsInt32() * coefficientUpper); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> Convolve(Vector256<ushort> samples, Vector256<short> coefficients) |
|||
{ |
|||
// Widen preserves the two eight-tap windows as separate 256-bit results. Reducing each product vector
|
|||
// therefore produces the two independent predictions without horizontal lane shuffles.
|
|||
(Vector256<uint> sample0, Vector256<uint> sample1) = Vector256.Widen(samples); |
|||
(Vector256<int> coefficient0, Vector256<int> coefficient1) = Vector256.Widen(coefficients); |
|||
return Vector128.Create( |
|||
Vector256.Sum(sample0.AsInt32() * coefficient0), |
|||
Vector256.Sum(sample1.AsInt32() * coefficient1), |
|||
0, |
|||
0); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> Convolve(Vector512<ushort> samples, Vector512<short> coefficients) |
|||
{ |
|||
// The four eight-tap windows occupy four consecutive 256-bit quarters after widening. Multiplication
|
|||
// remains 512-bit; quarter reductions recover the four independent scalar dot products in output order.
|
|||
(Vector512<uint> sampleLower, Vector512<uint> sampleUpper) = Vector512.Widen(samples); |
|||
(Vector512<int> coefficientLower, Vector512<int> coefficientUpper) = Vector512.Widen(coefficients); |
|||
Vector512<int> productLower = sampleLower.AsInt32() * coefficientLower; |
|||
Vector512<int> productUpper = sampleUpper.AsInt32() * coefficientUpper; |
|||
return Vector128.Create( |
|||
Vector256.Sum(productLower.GetLower()), |
|||
Vector256.Sum(productLower.GetUpper()), |
|||
Vector256.Sum(productUpper.GetLower()), |
|||
Vector256.Sum(productUpper.GetUpper())); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,180 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
/// <content>
|
|||
/// Defines the closed horizontal intra-block-copy interpolation operator.
|
|||
/// </content>
|
|||
internal static partial class Av1IntraBlockCopyHorizontalPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines horizontal intra-block-copy filtering for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1IntraBlockCopyHorizontalOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Filters one 8-bit sample.
|
|||
/// </summary>
|
|||
/// <param name="left">The integer-position source sample.</param>
|
|||
/// <param name="right">The source sample one column to the right.</param>
|
|||
/// <returns>The filtered 8-bit sample.</returns>
|
|||
public static abstract byte Filter(byte left, byte right); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixteen 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="left">The integer-position source samples.</param>
|
|||
/// <param name="right">The source samples one column to the right.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector128<byte> Filter( |
|||
Vector128<byte> left, |
|||
Vector128<byte> right); |
|||
|
|||
/// <summary>
|
|||
/// Filters thirty-two 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="left">The integer-position source samples.</param>
|
|||
/// <param name="right">The source samples one column to the right.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector256<byte> Filter( |
|||
Vector256<byte> left, |
|||
Vector256<byte> right); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixty-four 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="left">The integer-position source samples.</param>
|
|||
/// <param name="right">The source samples one column to the right.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector512<byte> Filter( |
|||
Vector512<byte> left, |
|||
Vector512<byte> right); |
|||
|
|||
/// <summary>
|
|||
/// Filters one high-bit-depth sample.
|
|||
/// </summary>
|
|||
/// <param name="left">The integer-position source sample.</param>
|
|||
/// <param name="right">The source sample one column to the right.</param>
|
|||
/// <returns>The filtered high-bit-depth sample.</returns>
|
|||
public static abstract short Filter(short left, short right); |
|||
|
|||
/// <summary>
|
|||
/// Filters eight high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="left">The integer-position source samples.</param>
|
|||
/// <param name="right">The source samples one column to the right.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector128<short> Filter( |
|||
Vector128<short> left, |
|||
Vector128<short> right); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixteen high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="left">The integer-position source samples.</param>
|
|||
/// <param name="right">The source samples one column to the right.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector256<short> Filter( |
|||
Vector256<short> left, |
|||
Vector256<short> right); |
|||
|
|||
/// <summary>
|
|||
/// Filters thirty-two high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="left">The integer-position source samples.</param>
|
|||
/// <param name="right">The source samples one column to the right.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector512<short> Filter( |
|||
Vector512<short> left, |
|||
Vector512<short> right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Averages horizontally adjacent source samples for a half-sample horizontal phase.
|
|||
/// </summary>
|
|||
private readonly struct IntraBlockCopyHorizontalOperator : IAv1IntraBlockCopyHorizontalOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static byte Filter(byte left, byte right) => (byte)((left + right + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<byte> Filter(Vector128<byte> left, Vector128<byte> right) |
|||
=> AverageRounded(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<byte> Filter(Vector256<byte> left, Vector256<byte> right) |
|||
=> AverageRounded(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<byte> Filter(Vector512<byte> left, Vector512<byte> right) |
|||
=> AverageRounded(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static short Filter(short left, short right) => (short)((left + right + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<short> Filter(Vector128<short> left, Vector128<short> right) |
|||
=> AverageRounded(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<short> Filter(Vector256<short> left, Vector256<short> right) |
|||
=> AverageRounded(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<short> Filter(Vector512<short> left, Vector512<short> right) |
|||
=> AverageRounded(left, right); |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing unsigned byte lanes.
|
|||
/// </summary>
|
|||
private static Vector128<byte> AverageRounded(Vector128<byte> left, Vector128<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing unsigned byte lanes.
|
|||
/// </summary>
|
|||
private static Vector256<byte> AverageRounded(Vector256<byte> left, Vector256<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing unsigned byte lanes.
|
|||
/// </summary>
|
|||
private static Vector512<byte> AverageRounded(Vector512<byte> left, Vector512<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing nonnegative high-bit-depth lanes.
|
|||
/// </summary>
|
|||
private static Vector128<short> AverageRounded(Vector128<short> left, Vector128<short> right) |
|||
{ |
|||
Vector128<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector128<ushort> rightUnsigned = right.AsUInt16(); |
|||
|
|||
// This identity computes ceil((a + b) / 2) without an overflowing lane-wise addition.
|
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing nonnegative high-bit-depth lanes.
|
|||
/// </summary>
|
|||
private static Vector256<short> AverageRounded(Vector256<short> left, Vector256<short> right) |
|||
{ |
|||
Vector256<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector256<ushort> rightUnsigned = right.AsUInt16(); |
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing nonnegative high-bit-depth lanes.
|
|||
/// </summary>
|
|||
private static Vector512<short> AverageRounded(Vector512<short> left, Vector512<short> right) |
|||
{ |
|||
Vector512<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector512<ushort> rightUnsigned = right.AsUInt16(); |
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,370 @@ |
|||
// 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.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
/// <content>
|
|||
/// Provides the family-owned scalar and width-progressive SIMD traversal for horizontal intra-block-copy prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1IntraBlockCopyHorizontalPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit filtered intra-block-copy prediction.
|
|||
/// </summary>
|
|||
public static void Predict( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> Predict<IntraBlockCopyHorizontalOperator>(source, sourceStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth filtered intra-block-copy prediction.
|
|||
/// </summary>
|
|||
public static void Predict( |
|||
ReadOnlySpan<short> source, |
|||
int sourceStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> Predict<IntraBlockCopyHorizontalOperator>(source, sourceStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit filtered intra-block-copy prediction without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictScalar( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> PredictScalar<IntraBlockCopyHorizontalOperator>(source, sourceStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth filtered intra-block-copy prediction without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictScalar( |
|||
ReadOnlySpan<short> source, |
|||
int sourceStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> PredictScalar<IntraBlockCopyHorizontalOperator>(source, sourceStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed interpolation operator to an 8-bit source block.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-phase-specific interpolation arithmetic.</typeparam>
|
|||
private static void Predict<TOperator>( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1IntraBlockCopyHorizontalOperator |
|||
{ |
|||
ref byte sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
if (Vector128.IsHardwareAccelerated && width is 4 or 8) |
|||
{ |
|||
// AV1 permits 4- and 8-sample transform widths, both smaller than a byte Vector128. The frame allocation's
|
|||
// 72-sample prediction border makes each full source load readable; exact-width stores avoid touching
|
|||
// destination padding.
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
Vector128<byte> topLeft = Vector128.LoadUnsafe(ref sourceRow); |
|||
Vector128<byte> topRight = Vector128.LoadUnsafe(ref sourceRow, 1); |
|||
|
|||
Vector128<byte> prediction = TOperator.Filter(topLeft, topRight); |
|||
if (width == 8) |
|||
{ |
|||
prediction.GetLower().StoreUnsafe(ref destinationRow); |
|||
} |
|||
else |
|||
{ |
|||
Unsafe.As<byte, uint>(ref destinationRow) = prediction.AsUInt32().GetElement(0); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
int processedColumns = 0; |
|||
|
|||
// AV1 transform widths are powers of two. The widest supported tier normally consumes the complete row; the
|
|||
// cumulative narrower tiers preserve the same contract for future legal widths without over-reading a tail.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorizedColumns = width - (width % Vector512<byte>.Count); |
|||
if (vectorizedColumns > 0) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < vectorizedColumns; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> topLeft = Vector512.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector512<byte> topRight = Vector512.LoadUnsafe(ref sourceRow, (nuint)(column + 1)); |
|||
|
|||
TOperator.Filter(topLeft, topRight).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = vectorizedColumns; |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int remainingColumns = width - processedColumns; |
|||
int vectorizedColumns = remainingColumns - (remainingColumns % Vector256<byte>.Count); |
|||
int endColumn = processedColumns + vectorizedColumns; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < endColumn; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> topLeft = Vector256.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector256<byte> topRight = Vector256.LoadUnsafe(ref sourceRow, (nuint)(column + 1)); |
|||
|
|||
TOperator.Filter(topLeft, topRight).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = endColumn; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int remainingColumns = width - processedColumns; |
|||
int vectorizedColumns = remainingColumns - (remainingColumns % Vector128<byte>.Count); |
|||
int endColumn = processedColumns + vectorizedColumns; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < endColumn; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> topLeft = Vector128.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector128<byte> topRight = Vector128.LoadUnsafe(ref sourceRow, (nuint)(column + 1)); |
|||
|
|||
TOperator.Filter(topLeft, topRight).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = endColumn; |
|||
} |
|||
|
|||
// FeatureTestRunner can disable every intrinsic tier. Keeping the scalar continuation in the same traversal
|
|||
// proves the fallback without changing source addressing or the normative rounding performed by the operator.
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < width; column++) |
|||
{ |
|||
byte topLeft = Unsafe.Add(ref sourceRow, column); |
|||
byte topRight = Unsafe.Add(ref sourceRow, column + 1); |
|||
|
|||
Unsafe.Add(ref destinationRow, column) = TOperator.Filter(topLeft, topRight); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed interpolation operator to a high-bit-depth source block.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-phase-specific interpolation arithmetic.</typeparam>
|
|||
private static void Predict<TOperator>( |
|||
ReadOnlySpan<short> source, |
|||
int sourceStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1IntraBlockCopyHorizontalOperator |
|||
{ |
|||
ref short sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
if (Vector128.IsHardwareAccelerated && width == 4) |
|||
{ |
|||
// Four high-bit-depth samples occupy the lower half of a Vector128. The frame allocation's prediction
|
|||
// border makes the full source load readable; storing only the lower four lanes avoids relying on writable
|
|||
// samples beyond the transform boundary.
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
Vector128<short> topLeft = Vector128.LoadUnsafe(ref sourceRow); |
|||
Vector128<short> topRight = Vector128.LoadUnsafe(ref sourceRow, 1); |
|||
|
|||
TOperator.Filter(topLeft, topRight).GetLower().StoreUnsafe(ref destinationRow); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
int processedColumns = 0; |
|||
|
|||
// High-bit-depth lanes hold half as many samples, but retain the same descending-width traversal and one scalar
|
|||
// continuation as the byte path.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorizedColumns = width - (width % Vector512<short>.Count); |
|||
if (vectorizedColumns > 0) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < vectorizedColumns; column += Vector512<short>.Count) |
|||
{ |
|||
Vector512<short> topLeft = Vector512.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector512<short> topRight = Vector512.LoadUnsafe(ref sourceRow, (nuint)(column + 1)); |
|||
|
|||
TOperator.Filter(topLeft, topRight).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = vectorizedColumns; |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int remainingColumns = width - processedColumns; |
|||
int vectorizedColumns = remainingColumns - (remainingColumns % Vector256<short>.Count); |
|||
int endColumn = processedColumns + vectorizedColumns; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < endColumn; column += Vector256<short>.Count) |
|||
{ |
|||
Vector256<short> topLeft = Vector256.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector256<short> topRight = Vector256.LoadUnsafe(ref sourceRow, (nuint)(column + 1)); |
|||
|
|||
TOperator.Filter(topLeft, topRight).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = endColumn; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int remainingColumns = width - processedColumns; |
|||
int vectorizedColumns = remainingColumns - (remainingColumns % Vector128<short>.Count); |
|||
int endColumn = processedColumns + vectorizedColumns; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < endColumn; column += Vector128<short>.Count) |
|||
{ |
|||
Vector128<short> topLeft = Vector128.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector128<short> topRight = Vector128.LoadUnsafe(ref sourceRow, (nuint)(column + 1)); |
|||
|
|||
TOperator.Filter(topLeft, topRight).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = endColumn; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < width; column++) |
|||
{ |
|||
short topLeft = Unsafe.Add(ref sourceRow, column); |
|||
short topRight = Unsafe.Add(ref sourceRow, column + 1); |
|||
|
|||
Unsafe.Add(ref destinationRow, column) = TOperator.Filter(topLeft, topRight); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed interpolation operator to an 8-bit source block without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-phase-specific interpolation arithmetic.</typeparam>
|
|||
private static void PredictScalar<TOperator>( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1IntraBlockCopyHorizontalOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int sourceRow = row * sourceStride; |
|||
int destinationRow = row * destinationStride; |
|||
|
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
byte topLeft = source[sourceRow + column]; |
|||
byte topRight = source[sourceRow + column + 1]; |
|||
destination[destinationRow + column] = TOperator.Filter(topLeft, topRight); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed interpolation operator to a high-bit-depth source block without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-phase-specific interpolation arithmetic.</typeparam>
|
|||
private static void PredictScalar<TOperator>( |
|||
ReadOnlySpan<short> source, |
|||
int sourceStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1IntraBlockCopyHorizontalOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int sourceRow = row * sourceStride; |
|||
int destinationRow = row * destinationStride; |
|||
|
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
short topLeft = source[sourceRow + column]; |
|||
short topRight = source[sourceRow + column + 1]; |
|||
destination[destinationRow + column] = TOperator.Filter(topLeft, topRight); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,80 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
/// <content>
|
|||
/// Provides the overflow-free rounded-average arithmetic shared by the interpolation operators.
|
|||
/// </content>
|
|||
internal static partial class Av1IntraBlockCopyPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Computes the AV1 rounded average of two unsigned 8-bit vectors without widening their lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The first source vector.</param>
|
|||
/// <param name="right">The second source vector.</param>
|
|||
/// <returns>The lane-wise rounded averages.</returns>
|
|||
private static Vector128<byte> AverageRounded(Vector128<byte> left, Vector128<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes the AV1 rounded average of two unsigned 8-bit vectors without widening their lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The first source vector.</param>
|
|||
/// <param name="right">The second source vector.</param>
|
|||
/// <returns>The lane-wise rounded averages.</returns>
|
|||
private static Vector256<byte> AverageRounded(Vector256<byte> left, Vector256<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes the AV1 rounded average of two unsigned 8-bit vectors without widening their lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The first source vector.</param>
|
|||
/// <param name="right">The second source vector.</param>
|
|||
/// <returns>The lane-wise rounded averages.</returns>
|
|||
private static Vector512<byte> AverageRounded(Vector512<byte> left, Vector512<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes the AV1 rounded average of two nonnegative high-bit-depth vectors without widening their lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The first source vector.</param>
|
|||
/// <param name="right">The second source vector.</param>
|
|||
/// <returns>The lane-wise rounded averages.</returns>
|
|||
private static Vector128<short> AverageRounded(Vector128<short> left, Vector128<short> right) |
|||
{ |
|||
Vector128<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector128<ushort> rightUnsigned = right.AsUInt16(); |
|||
|
|||
// (a | b) - ((a ^ b) >> 1) is ceil((a + b) / 2) without an overflowing lane-wise addition.
|
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the AV1 rounded average of two nonnegative high-bit-depth vectors without widening their lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The first source vector.</param>
|
|||
/// <param name="right">The second source vector.</param>
|
|||
/// <returns>The lane-wise rounded averages.</returns>
|
|||
private static Vector256<short> AverageRounded(Vector256<short> left, Vector256<short> right) |
|||
{ |
|||
Vector256<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector256<ushort> rightUnsigned = right.AsUInt16(); |
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the AV1 rounded average of two nonnegative high-bit-depth vectors without widening their lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The first source vector.</param>
|
|||
/// <param name="right">The second source vector.</param>
|
|||
/// <returns>The lane-wise rounded averages.</returns>
|
|||
private static Vector512<short> AverageRounded(Vector512<short> left, Vector512<short> right) |
|||
{ |
|||
Vector512<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector512<ushort> rightUnsigned = right.AsUInt16(); |
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
} |
|||
@ -1,78 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
/// <content>
|
|||
/// Defines the closed interpolation operators used by intra-block-copy prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1IntraBlockCopyPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Averages horizontally adjacent source samples for a half-sample horizontal phase.
|
|||
/// </summary>
|
|||
private readonly struct HorizontalOperator : IAv1IntraBlockCopyOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesRight => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesBottom => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static byte Filter(byte topLeft, byte topRight, byte bottomLeft, byte bottomRight) => (byte)((topLeft + topRight + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<byte> Filter( |
|||
Vector128<byte> topLeft, |
|||
Vector128<byte> topRight, |
|||
Vector128<byte> bottomLeft, |
|||
Vector128<byte> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<byte> Filter( |
|||
Vector256<byte> topLeft, |
|||
Vector256<byte> topRight, |
|||
Vector256<byte> bottomLeft, |
|||
Vector256<byte> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<byte> Filter( |
|||
Vector512<byte> topLeft, |
|||
Vector512<byte> topRight, |
|||
Vector512<byte> bottomLeft, |
|||
Vector512<byte> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static short Filter(short topLeft, short topRight, short bottomLeft, short bottomRight) => (short)((topLeft + topRight + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<short> Filter( |
|||
Vector128<short> topLeft, |
|||
Vector128<short> topRight, |
|||
Vector128<short> bottomLeft, |
|||
Vector128<short> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<short> Filter( |
|||
Vector256<short> topLeft, |
|||
Vector256<short> topRight, |
|||
Vector256<short> bottomLeft, |
|||
Vector256<short> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<short> Filter( |
|||
Vector512<short> topLeft, |
|||
Vector512<short> topRight, |
|||
Vector512<short> bottomLeft, |
|||
Vector512<short> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
} |
|||
} |
|||
@ -1,75 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
internal static partial class Av1IntraBlockCopyPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Averages vertically adjacent source samples for a half-sample vertical phase.
|
|||
/// </summary>
|
|||
private readonly struct VerticalOperator : IAv1IntraBlockCopyOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesRight => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesBottom => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static byte Filter(byte topLeft, byte topRight, byte bottomLeft, byte bottomRight) => (byte)((topLeft + bottomLeft + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<byte> Filter( |
|||
Vector128<byte> topLeft, |
|||
Vector128<byte> topRight, |
|||
Vector128<byte> bottomLeft, |
|||
Vector128<byte> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<byte> Filter( |
|||
Vector256<byte> topLeft, |
|||
Vector256<byte> topRight, |
|||
Vector256<byte> bottomLeft, |
|||
Vector256<byte> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<byte> Filter( |
|||
Vector512<byte> topLeft, |
|||
Vector512<byte> topRight, |
|||
Vector512<byte> bottomLeft, |
|||
Vector512<byte> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static short Filter(short topLeft, short topRight, short bottomLeft, short bottomRight) => (short)((topLeft + bottomLeft + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<short> Filter( |
|||
Vector128<short> topLeft, |
|||
Vector128<short> topRight, |
|||
Vector128<short> bottomLeft, |
|||
Vector128<short> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<short> Filter( |
|||
Vector256<short> topLeft, |
|||
Vector256<short> topRight, |
|||
Vector256<short> bottomLeft, |
|||
Vector256<short> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<short> Filter( |
|||
Vector512<short> topLeft, |
|||
Vector512<short> topRight, |
|||
Vector512<short> bottomLeft, |
|||
Vector512<short> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
} |
|||
} |
|||
@ -0,0 +1,180 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
/// <content>
|
|||
/// Defines the closed vertical intra-block-copy interpolation operator.
|
|||
/// </content>
|
|||
internal static partial class Av1IntraBlockCopyVerticalPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines vertical intra-block-copy filtering for scalar and SIMD lane groups.
|
|||
/// </summary>
|
|||
private interface IAv1IntraBlockCopyVerticalOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Filters one 8-bit sample.
|
|||
/// </summary>
|
|||
/// <param name="top">The integer-position source sample.</param>
|
|||
/// <param name="bottom">The source sample one row below.</param>
|
|||
/// <returns>The filtered 8-bit sample.</returns>
|
|||
public static abstract byte Filter(byte top, byte bottom); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixteen 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="top">The integer-position source samples.</param>
|
|||
/// <param name="bottom">The source samples one row below.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector128<byte> Filter( |
|||
Vector128<byte> top, |
|||
Vector128<byte> bottom); |
|||
|
|||
/// <summary>
|
|||
/// Filters thirty-two 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="top">The integer-position source samples.</param>
|
|||
/// <param name="bottom">The source samples one row below.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector256<byte> Filter( |
|||
Vector256<byte> top, |
|||
Vector256<byte> bottom); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixty-four 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="top">The integer-position source samples.</param>
|
|||
/// <param name="bottom">The source samples one row below.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector512<byte> Filter( |
|||
Vector512<byte> top, |
|||
Vector512<byte> bottom); |
|||
|
|||
/// <summary>
|
|||
/// Filters one high-bit-depth sample.
|
|||
/// </summary>
|
|||
/// <param name="top">The integer-position source sample.</param>
|
|||
/// <param name="bottom">The source sample one row below.</param>
|
|||
/// <returns>The filtered high-bit-depth sample.</returns>
|
|||
public static abstract short Filter(short top, short bottom); |
|||
|
|||
/// <summary>
|
|||
/// Filters eight high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="top">The integer-position source samples.</param>
|
|||
/// <param name="bottom">The source samples one row below.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector128<short> Filter( |
|||
Vector128<short> top, |
|||
Vector128<short> bottom); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixteen high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="top">The integer-position source samples.</param>
|
|||
/// <param name="bottom">The source samples one row below.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector256<short> Filter( |
|||
Vector256<short> top, |
|||
Vector256<short> bottom); |
|||
|
|||
/// <summary>
|
|||
/// Filters thirty-two high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="top">The integer-position source samples.</param>
|
|||
/// <param name="bottom">The source samples one row below.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector512<short> Filter( |
|||
Vector512<short> top, |
|||
Vector512<short> bottom); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Averages vertically adjacent source samples for a half-sample vertical phase.
|
|||
/// </summary>
|
|||
private readonly struct IntraBlockCopyVerticalOperator : IAv1IntraBlockCopyVerticalOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static byte Filter(byte top, byte bottom) => (byte)((top + bottom + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<byte> Filter(Vector128<byte> top, Vector128<byte> bottom) |
|||
=> AverageRounded(top, bottom); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<byte> Filter(Vector256<byte> top, Vector256<byte> bottom) |
|||
=> AverageRounded(top, bottom); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<byte> Filter(Vector512<byte> top, Vector512<byte> bottom) |
|||
=> AverageRounded(top, bottom); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static short Filter(short top, short bottom) => (short)((top + bottom + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<short> Filter(Vector128<short> top, Vector128<short> bottom) |
|||
=> AverageRounded(top, bottom); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<short> Filter(Vector256<short> top, Vector256<short> bottom) |
|||
=> AverageRounded(top, bottom); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<short> Filter(Vector512<short> top, Vector512<short> bottom) |
|||
=> AverageRounded(top, bottom); |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing unsigned byte lanes.
|
|||
/// </summary>
|
|||
private static Vector128<byte> AverageRounded(Vector128<byte> left, Vector128<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing unsigned byte lanes.
|
|||
/// </summary>
|
|||
private static Vector256<byte> AverageRounded(Vector256<byte> left, Vector256<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing unsigned byte lanes.
|
|||
/// </summary>
|
|||
private static Vector512<byte> AverageRounded(Vector512<byte> left, Vector512<byte> right) |
|||
=> (left | right) - ((left ^ right) >> 1); |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing nonnegative high-bit-depth lanes.
|
|||
/// </summary>
|
|||
private static Vector128<short> AverageRounded(Vector128<short> left, Vector128<short> right) |
|||
{ |
|||
Vector128<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector128<ushort> rightUnsigned = right.AsUInt16(); |
|||
|
|||
// This identity computes ceil((a + b) / 2) without an overflowing lane-wise addition.
|
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing nonnegative high-bit-depth lanes.
|
|||
/// </summary>
|
|||
private static Vector256<short> AverageRounded(Vector256<short> left, Vector256<short> right) |
|||
{ |
|||
Vector256<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector256<ushort> rightUnsigned = right.AsUInt16(); |
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes a rounded average without overflowing nonnegative high-bit-depth lanes.
|
|||
/// </summary>
|
|||
private static Vector512<short> AverageRounded(Vector512<short> left, Vector512<short> right) |
|||
{ |
|||
Vector512<ushort> leftUnsigned = left.AsUInt16(); |
|||
Vector512<ushort> rightUnsigned = right.AsUInt16(); |
|||
return ((leftUnsigned | rightUnsigned) - ((leftUnsigned ^ rightUnsigned) >> 1)).AsInt16(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,370 @@ |
|||
// 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.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
/// <content>
|
|||
/// Provides the family-owned scalar and width-progressive SIMD traversal for vertical intra-block-copy prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1IntraBlockCopyVerticalPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit filtered intra-block-copy prediction.
|
|||
/// </summary>
|
|||
public static void Predict( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> Predict<IntraBlockCopyVerticalOperator>(source, sourceStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth filtered intra-block-copy prediction.
|
|||
/// </summary>
|
|||
public static void Predict( |
|||
ReadOnlySpan<short> source, |
|||
int sourceStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> Predict<IntraBlockCopyVerticalOperator>(source, sourceStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit filtered intra-block-copy prediction without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictScalar( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> PredictScalar<IntraBlockCopyVerticalOperator>(source, sourceStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth filtered intra-block-copy prediction without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictScalar( |
|||
ReadOnlySpan<short> source, |
|||
int sourceStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
=> PredictScalar<IntraBlockCopyVerticalOperator>(source, sourceStride, destination, destinationStride, width, height); |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed interpolation operator to an 8-bit source block.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-phase-specific interpolation arithmetic.</typeparam>
|
|||
private static void Predict<TOperator>( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1IntraBlockCopyVerticalOperator |
|||
{ |
|||
ref byte sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
if (Vector128.IsHardwareAccelerated && width is 4 or 8) |
|||
{ |
|||
// AV1 permits 4- and 8-sample transform widths, both smaller than a byte Vector128. The frame allocation's
|
|||
// 72-sample prediction border makes each full source load readable; exact-width stores avoid touching
|
|||
// destination padding.
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
Vector128<byte> topLeft = Vector128.LoadUnsafe(ref sourceRow); |
|||
Vector128<byte> bottomLeft = Vector128.LoadUnsafe(ref sourceRow, (nuint)sourceStride); |
|||
|
|||
Vector128<byte> prediction = TOperator.Filter(topLeft, bottomLeft); |
|||
if (width == 8) |
|||
{ |
|||
prediction.GetLower().StoreUnsafe(ref destinationRow); |
|||
} |
|||
else |
|||
{ |
|||
Unsafe.As<byte, uint>(ref destinationRow) = prediction.AsUInt32().GetElement(0); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
int processedColumns = 0; |
|||
|
|||
// AV1 transform widths are powers of two. The widest supported tier normally consumes the complete row; the
|
|||
// cumulative narrower tiers preserve the same contract for future legal widths without over-reading a tail.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorizedColumns = width - (width % Vector512<byte>.Count); |
|||
if (vectorizedColumns > 0) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < vectorizedColumns; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> topLeft = Vector512.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector512<byte> bottomLeft = Vector512.LoadUnsafe(ref sourceRow, (nuint)(sourceStride + column)); |
|||
|
|||
TOperator.Filter(topLeft, bottomLeft).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = vectorizedColumns; |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int remainingColumns = width - processedColumns; |
|||
int vectorizedColumns = remainingColumns - (remainingColumns % Vector256<byte>.Count); |
|||
int endColumn = processedColumns + vectorizedColumns; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < endColumn; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> topLeft = Vector256.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector256<byte> bottomLeft = Vector256.LoadUnsafe(ref sourceRow, (nuint)(sourceStride + column)); |
|||
|
|||
TOperator.Filter(topLeft, bottomLeft).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = endColumn; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int remainingColumns = width - processedColumns; |
|||
int vectorizedColumns = remainingColumns - (remainingColumns % Vector128<byte>.Count); |
|||
int endColumn = processedColumns + vectorizedColumns; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < endColumn; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> topLeft = Vector128.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector128<byte> bottomLeft = Vector128.LoadUnsafe(ref sourceRow, (nuint)(sourceStride + column)); |
|||
|
|||
TOperator.Filter(topLeft, bottomLeft).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = endColumn; |
|||
} |
|||
|
|||
// FeatureTestRunner can disable every intrinsic tier. Keeping the scalar continuation in the same traversal
|
|||
// proves the fallback without changing source addressing or the normative rounding performed by the operator.
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < width; column++) |
|||
{ |
|||
byte topLeft = Unsafe.Add(ref sourceRow, column); |
|||
byte bottomLeft = Unsafe.Add(ref sourceRow, sourceStride + column); |
|||
|
|||
Unsafe.Add(ref destinationRow, column) = TOperator.Filter(topLeft, bottomLeft); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed interpolation operator to a high-bit-depth source block.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-phase-specific interpolation arithmetic.</typeparam>
|
|||
private static void Predict<TOperator>( |
|||
ReadOnlySpan<short> source, |
|||
int sourceStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1IntraBlockCopyVerticalOperator |
|||
{ |
|||
ref short sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
if (Vector128.IsHardwareAccelerated && width == 4) |
|||
{ |
|||
// Four high-bit-depth samples occupy the lower half of a Vector128. The frame allocation's prediction
|
|||
// border makes the full source load readable; storing only the lower four lanes avoids relying on writable
|
|||
// samples beyond the transform boundary.
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
Vector128<short> topLeft = Vector128.LoadUnsafe(ref sourceRow); |
|||
Vector128<short> bottomLeft = Vector128.LoadUnsafe(ref sourceRow, (nuint)sourceStride); |
|||
|
|||
TOperator.Filter(topLeft, bottomLeft).GetLower().StoreUnsafe(ref destinationRow); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
int processedColumns = 0; |
|||
|
|||
// High-bit-depth lanes hold half as many samples, but retain the same descending-width traversal and one scalar
|
|||
// continuation as the byte path.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorizedColumns = width - (width % Vector512<short>.Count); |
|||
if (vectorizedColumns > 0) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = 0; column < vectorizedColumns; column += Vector512<short>.Count) |
|||
{ |
|||
Vector512<short> topLeft = Vector512.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector512<short> bottomLeft = Vector512.LoadUnsafe(ref sourceRow, (nuint)(sourceStride + column)); |
|||
|
|||
TOperator.Filter(topLeft, bottomLeft).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = vectorizedColumns; |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int remainingColumns = width - processedColumns; |
|||
int vectorizedColumns = remainingColumns - (remainingColumns % Vector256<short>.Count); |
|||
int endColumn = processedColumns + vectorizedColumns; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < endColumn; column += Vector256<short>.Count) |
|||
{ |
|||
Vector256<short> topLeft = Vector256.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector256<short> bottomLeft = Vector256.LoadUnsafe(ref sourceRow, (nuint)(sourceStride + column)); |
|||
|
|||
TOperator.Filter(topLeft, bottomLeft).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = endColumn; |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int remainingColumns = width - processedColumns; |
|||
int vectorizedColumns = remainingColumns - (remainingColumns % Vector128<short>.Count); |
|||
int endColumn = processedColumns + vectorizedColumns; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < endColumn; column += Vector128<short>.Count) |
|||
{ |
|||
Vector128<short> topLeft = Vector128.LoadUnsafe(ref sourceRow, (nuint)column); |
|||
Vector128<short> bottomLeft = Vector128.LoadUnsafe(ref sourceRow, (nuint)(sourceStride + column)); |
|||
|
|||
TOperator.Filter(topLeft, bottomLeft).StoreUnsafe(ref destinationRow, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
processedColumns = endColumn; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref short sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride); |
|||
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
|||
|
|||
for (int column = processedColumns; column < width; column++) |
|||
{ |
|||
short topLeft = Unsafe.Add(ref sourceRow, column); |
|||
short bottomLeft = Unsafe.Add(ref sourceRow, sourceStride + column); |
|||
|
|||
Unsafe.Add(ref destinationRow, column) = TOperator.Filter(topLeft, bottomLeft); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed interpolation operator to an 8-bit source block without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-phase-specific interpolation arithmetic.</typeparam>
|
|||
private static void PredictScalar<TOperator>( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1IntraBlockCopyVerticalOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int sourceRow = row * sourceStride; |
|||
int destinationRow = row * destinationStride; |
|||
|
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
byte topLeft = source[sourceRow + column]; |
|||
byte bottomLeft = source[sourceRow + sourceStride + column]; |
|||
destination[destinationRow + column] = TOperator.Filter(topLeft, bottomLeft); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed interpolation operator to a high-bit-depth source block without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-phase-specific interpolation arithmetic.</typeparam>
|
|||
private static void PredictScalar<TOperator>( |
|||
ReadOnlySpan<short> source, |
|||
int sourceStride, |
|||
Span<short> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height) |
|||
where TOperator : struct, IAv1IntraBlockCopyVerticalOperator |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int sourceRow = row * sourceStride; |
|||
int destinationRow = row * destinationStride; |
|||
|
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
short topLeft = source[sourceRow + column]; |
|||
short bottomLeft = source[sourceRow + sourceStride + column]; |
|||
destination[destinationRow + column] = TOperator.Filter(topLeft, bottomLeft); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:1211ebefbc9ccef9ed19be4cce3f807d69fffe338e95cca1b5f4ca8023482175 |
|||
size 5930767 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5fcd265fd9f9bdd0d3179340b4c4532f1422ca5e5d97741c7481b84cb5dc122f |
|||
size 1488725 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:4fbff73ff0de2d9084dae557d1d4bd677b0486516525bf4d327d2d795d5a7779 |
|||
size 304178 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:14a3dbf537b6bf15efc003182d9916d61438c93624a8bd26e6e3ae7eaf33ea82 |
|||
size 36088 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:f7db607694818c19e62fd9a27f53e1a3e2d00b72c39c0430c1b26399cc76777d |
|||
size 608318 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:b59bf9586d8546dfda81dfec4ee4e32ceb502c9d22412ab0b63a2abb534a1f14 |
|||
size 44964 |
|||
Loading…
Reference in new issue