mirror of https://github.com/SixLabors/ImageSharp
4 changed files with 545 additions and 1 deletions
@ -0,0 +1,118 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
||||
|
|
||||
|
/// <content>
|
||||
|
/// Defines the sample-width-specific arithmetic used by <see cref="Av1ResidualBuilder"/>.
|
||||
|
/// </content>
|
||||
|
internal static partial class Av1ResidualBuilder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines one AV1 source-minus-prediction operation across hardware widths.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TSample">The source and prediction sample type.</typeparam>
|
||||
|
internal interface IResidualOperator<TSample> |
||||
|
where TSample : unmanaged |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Subtracts eight or sixteen source and prediction samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source samples.</param>
|
||||
|
/// <param name="prediction">The prediction samples.</param>
|
||||
|
/// <param name="upper">The upper residual lanes when the inputs contain 8-bit samples.</param>
|
||||
|
/// <returns>The lower residual lanes.</returns>
|
||||
|
public static abstract Vector128<short> Subtract(Vector128<TSample> source, Vector128<TSample> prediction, out Vector128<short> upper); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Subtracts sixteen or thirty-two source and prediction samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source samples.</param>
|
||||
|
/// <param name="prediction">The prediction samples.</param>
|
||||
|
/// <param name="upper">The upper residual lanes when the inputs contain 8-bit samples.</param>
|
||||
|
/// <returns>The lower residual lanes.</returns>
|
||||
|
public static abstract Vector256<short> Subtract(Vector256<TSample> source, Vector256<TSample> prediction, out Vector256<short> upper); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Subtracts thirty-two or sixty-four source and prediction samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source samples.</param>
|
||||
|
/// <param name="prediction">The prediction samples.</param>
|
||||
|
/// <param name="upper">The upper residual lanes when the inputs contain 8-bit samples.</param>
|
||||
|
/// <returns>The lower residual lanes.</returns>
|
||||
|
public static abstract Vector512<short> Subtract(Vector512<TSample> source, Vector512<TSample> prediction, out Vector512<short> upper); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Subtracts one source and prediction sample.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source sample.</param>
|
||||
|
/// <param name="prediction">The prediction sample.</param>
|
||||
|
/// <returns>The signed residual.</returns>
|
||||
|
public static abstract short Subtract(TSample source, TSample prediction); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Widens 8-bit samples before subtraction so every residual is represented without precision loss.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct ByteOperator : IResidualOperator<byte> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Vector128<short> Subtract(Vector128<byte> source, Vector128<byte> prediction, out Vector128<short> upper) |
||||
|
{ |
||||
|
Vector128<short> lower = Vector128.WidenLower(source).AsInt16() - Vector128.WidenLower(prediction).AsInt16(); |
||||
|
upper = Vector128.WidenUpper(source).AsInt16() - Vector128.WidenUpper(prediction).AsInt16(); |
||||
|
return lower; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Vector256<short> Subtract(Vector256<byte> source, Vector256<byte> prediction, out Vector256<short> upper) |
||||
|
{ |
||||
|
Vector256<short> lower = Vector256.WidenLower(source).AsInt16() - Vector256.WidenLower(prediction).AsInt16(); |
||||
|
upper = Vector256.WidenUpper(source).AsInt16() - Vector256.WidenUpper(prediction).AsInt16(); |
||||
|
return lower; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Vector512<short> Subtract(Vector512<byte> source, Vector512<byte> prediction, out Vector512<short> upper) |
||||
|
{ |
||||
|
Vector512<short> lower = Vector512.WidenLower(source).AsInt16() - Vector512.WidenLower(prediction).AsInt16(); |
||||
|
upper = Vector512.WidenUpper(source).AsInt16() - Vector512.WidenUpper(prediction).AsInt16(); |
||||
|
return lower; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static short Subtract(byte source, byte prediction) => (short)(source - prediction); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Subtracts high-bit-depth samples directly because AV1's 10-bit and 12-bit ranges fit signed-short lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct UInt16Operator : IResidualOperator<ushort> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Vector128<short> Subtract(Vector128<ushort> source, Vector128<ushort> prediction, out Vector128<short> upper) |
||||
|
{ |
||||
|
upper = default; |
||||
|
return source.AsInt16() - prediction.AsInt16(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Vector256<short> Subtract(Vector256<ushort> source, Vector256<ushort> prediction, out Vector256<short> upper) |
||||
|
{ |
||||
|
upper = default; |
||||
|
return source.AsInt16() - prediction.AsInt16(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Vector512<short> Subtract(Vector512<ushort> source, Vector512<ushort> prediction, out Vector512<short> upper) |
||||
|
{ |
||||
|
upper = default; |
||||
|
return source.AsInt16() - prediction.AsInt16(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static short Subtract(ushort source, ushort prediction) => (short)(source - prediction); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,150 @@ |
|||||
|
// 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.Pipeline; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Builds signed AV1 residual planes from source and prediction samples.
|
||||
|
/// </summary>
|
||||
|
internal static partial class Av1ResidualBuilder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Subtracts an 8-bit prediction plane from its source plane.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source samples.</param>
|
||||
|
/// <param name="sourceStride">The source row stride.</param>
|
||||
|
/// <param name="prediction">The prediction samples.</param>
|
||||
|
/// <param name="predictionStride">The prediction row stride.</param>
|
||||
|
/// <param name="residual">The destination residual samples.</param>
|
||||
|
/// <param name="residualStride">The residual row stride.</param>
|
||||
|
/// <param name="width">The number of samples per row.</param>
|
||||
|
/// <param name="height">The number of rows.</param>
|
||||
|
public static void Subtract( |
||||
|
ReadOnlySpan<byte> source, |
||||
|
int sourceStride, |
||||
|
ReadOnlySpan<byte> prediction, |
||||
|
int predictionStride, |
||||
|
Span<short> residual, |
||||
|
int residualStride, |
||||
|
int width, |
||||
|
int height) |
||||
|
=> Subtract<byte, ByteOperator>(source, sourceStride, prediction, predictionStride, residual, residualStride, width, height); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Subtracts a high-bit-depth prediction plane from its source plane.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source samples.</param>
|
||||
|
/// <param name="sourceStride">The source row stride.</param>
|
||||
|
/// <param name="prediction">The prediction samples.</param>
|
||||
|
/// <param name="predictionStride">The prediction row stride.</param>
|
||||
|
/// <param name="residual">The destination residual samples.</param>
|
||||
|
/// <param name="residualStride">The residual row stride.</param>
|
||||
|
/// <param name="width">The number of samples per row.</param>
|
||||
|
/// <param name="height">The number of rows.</param>
|
||||
|
public static void Subtract( |
||||
|
ReadOnlySpan<ushort> source, |
||||
|
int sourceStride, |
||||
|
ReadOnlySpan<ushort> prediction, |
||||
|
int predictionStride, |
||||
|
Span<short> residual, |
||||
|
int residualStride, |
||||
|
int width, |
||||
|
int height) |
||||
|
=> Subtract<ushort, UInt16Operator>(source, sourceStride, prediction, predictionStride, residual, residualStride, width, height); |
||||
|
|
||||
|
private static void Subtract<TSample, TOperator>( |
||||
|
ReadOnlySpan<TSample> source, |
||||
|
int sourceStride, |
||||
|
ReadOnlySpan<TSample> prediction, |
||||
|
int predictionStride, |
||||
|
Span<short> residual, |
||||
|
int residualStride, |
||||
|
int width, |
||||
|
int height) |
||||
|
where TSample : unmanaged |
||||
|
where TOperator : struct, IResidualOperator<TSample> |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
ReadOnlySpan<TSample> sourceRow = source.Slice(y * sourceStride, width); |
||||
|
ReadOnlySpan<TSample> predictionRow = prediction.Slice(y * predictionStride, width); |
||||
|
Span<short> residualRow = residual.Slice(y * residualStride, width); |
||||
|
|
||||
|
ref TSample sourceBase = ref MemoryMarshal.GetReference(sourceRow); |
||||
|
ref TSample predictionBase = ref MemoryMarshal.GetReference(predictionRow); |
||||
|
ref short residualBase = ref MemoryMarshal.GetReference(residualRow); |
||||
|
int x = 0; |
||||
|
|
||||
|
// Each narrower tier resumes at the shared sample offset, preserving SIMD execution for the widest
|
||||
|
// possible remainder while leaving only a sub-vector tail for scalar subtraction.
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
nuint vectorCount = sourceRow.Vector512Count<TSample>(); |
||||
|
|
||||
|
for (; vectorCount > 0; vectorCount--, x += Vector512<TSample>.Count) |
||||
|
{ |
||||
|
Vector512<TSample> sourceVector = Unsafe.As<TSample, Vector512<TSample>>(ref Unsafe.Add(ref sourceBase, x)); |
||||
|
Vector512<TSample> predictionVector = Unsafe.As<TSample, Vector512<TSample>>(ref Unsafe.Add(ref predictionBase, x)); |
||||
|
Vector512<short> lower = TOperator.Subtract(sourceVector, predictionVector, out Vector512<short> upper); |
||||
|
|
||||
|
Unsafe.As<short, Vector512<short>>(ref Unsafe.Add(ref residualBase, x)) = lower; |
||||
|
|
||||
|
// Byte vectors widen into two signed-short vectors; high-bit-depth vectors retain one lane per sample.
|
||||
|
if (Vector512<TSample>.Count != Vector512<short>.Count) |
||||
|
{ |
||||
|
Unsafe.As<short, Vector512<short>>(ref Unsafe.Add(ref residualBase, x + Vector512<short>.Count)) = upper; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
nuint vectorCount = sourceRow[x..].Vector256Count<TSample>(); |
||||
|
|
||||
|
for (; vectorCount > 0; vectorCount--, x += Vector256<TSample>.Count) |
||||
|
{ |
||||
|
Vector256<TSample> sourceVector = Unsafe.As<TSample, Vector256<TSample>>(ref Unsafe.Add(ref sourceBase, x)); |
||||
|
Vector256<TSample> predictionVector = Unsafe.As<TSample, Vector256<TSample>>(ref Unsafe.Add(ref predictionBase, x)); |
||||
|
Vector256<short> lower = TOperator.Subtract(sourceVector, predictionVector, out Vector256<short> upper); |
||||
|
|
||||
|
Unsafe.As<short, Vector256<short>>(ref Unsafe.Add(ref residualBase, x)) = lower; |
||||
|
|
||||
|
if (Vector256<TSample>.Count != Vector256<short>.Count) |
||||
|
{ |
||||
|
Unsafe.As<short, Vector256<short>>(ref Unsafe.Add(ref residualBase, x + Vector256<short>.Count)) = upper; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
nuint vectorCount = sourceRow[x..].Vector128Count<TSample>(); |
||||
|
|
||||
|
for (; vectorCount > 0; vectorCount--, x += Vector128<TSample>.Count) |
||||
|
{ |
||||
|
Vector128<TSample> sourceVector = Unsafe.As<TSample, Vector128<TSample>>(ref Unsafe.Add(ref sourceBase, x)); |
||||
|
Vector128<TSample> predictionVector = Unsafe.As<TSample, Vector128<TSample>>(ref Unsafe.Add(ref predictionBase, x)); |
||||
|
Vector128<short> lower = TOperator.Subtract(sourceVector, predictionVector, out Vector128<short> upper); |
||||
|
|
||||
|
Unsafe.As<short, Vector128<short>>(ref Unsafe.Add(ref residualBase, x)) = lower; |
||||
|
|
||||
|
if (Vector128<TSample>.Count != Vector128<short>.Count) |
||||
|
{ |
||||
|
Unsafe.As<short, Vector128<short>>(ref Unsafe.Add(ref residualBase, x + Vector128<short>.Count)) = upper; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; x < width; x++) |
||||
|
{ |
||||
|
Unsafe.Add(ref residualBase, x) = TOperator.Subtract( |
||||
|
Unsafe.Add(ref sourceBase, x), |
||||
|
Unsafe.Add(ref predictionBase, x)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,275 @@ |
|||||
|
// 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.Pipeline; |
||||
|
using SixLabors.ImageSharp.Tests.TestUtilities; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies AV1 residual construction against source-minus-prediction reference arithmetic.
|
||||
|
/// </summary>
|
||||
|
[Trait("Format", "Avif")] |
||||
|
public class Av1ResidualBuilderTests |
||||
|
{ |
||||
|
private const HwIntrinsics ResidualConfigurations = |
||||
|
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies 8-bit, 10-bit, and 12-bit residuals across misaligned planes, independent strides, and SIMD tails.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void ResidualsMatchReferenceAcrossHardwareWidths() |
||||
|
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateResiduals, ResidualConfigurations); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies every width-specific operator even when the current processor cannot select that width in the driver.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void ResidualOperatorsMatchReferenceAtEveryVectorWidth() |
||||
|
{ |
||||
|
byte[] byteSource = new byte[Vector512<byte>.Count]; |
||||
|
byte[] bytePrediction = new byte[Vector512<byte>.Count]; |
||||
|
short[] byteExpected = new short[Vector512<byte>.Count]; |
||||
|
short[] byteActual = new short[Vector512<byte>.Count]; |
||||
|
FillBytePlanes(byteSource, byteSource.Length, bytePrediction, bytePrediction.Length, byteSource.Length, 1); |
||||
|
FillReference(byteSource, byteSource.Length, bytePrediction, bytePrediction.Length, byteExpected, byteExpected.Length, byteExpected.Length, 1); |
||||
|
|
||||
|
ref byte byteSourceBase = ref MemoryMarshal.GetArrayDataReference(byteSource); |
||||
|
ref byte bytePredictionBase = ref MemoryMarshal.GetArrayDataReference(bytePrediction); |
||||
|
Vector128<short> byteLower128 = Av1ResidualBuilder.ByteOperator.Subtract( |
||||
|
Unsafe.As<byte, Vector128<byte>>(ref byteSourceBase), |
||||
|
Unsafe.As<byte, Vector128<byte>>(ref bytePredictionBase), |
||||
|
out Vector128<short> byteUpper128); |
||||
|
|
||||
|
byteLower128.CopyTo(byteActual); |
||||
|
byteUpper128.CopyTo(byteActual.AsSpan(Vector128<short>.Count)); |
||||
|
AssertEqual(byteExpected, byteActual, Vector128<byte>.Count); |
||||
|
|
||||
|
Vector256<short> byteLower256 = Av1ResidualBuilder.ByteOperator.Subtract( |
||||
|
Unsafe.As<byte, Vector256<byte>>(ref byteSourceBase), |
||||
|
Unsafe.As<byte, Vector256<byte>>(ref bytePredictionBase), |
||||
|
out Vector256<short> byteUpper256); |
||||
|
|
||||
|
byteLower256.CopyTo(byteActual); |
||||
|
byteUpper256.CopyTo(byteActual.AsSpan(Vector256<short>.Count)); |
||||
|
AssertEqual(byteExpected, byteActual, Vector256<byte>.Count); |
||||
|
|
||||
|
Vector512<short> byteLower512 = Av1ResidualBuilder.ByteOperator.Subtract( |
||||
|
Unsafe.As<byte, Vector512<byte>>(ref byteSourceBase), |
||||
|
Unsafe.As<byte, Vector512<byte>>(ref bytePredictionBase), |
||||
|
out Vector512<short> byteUpper512); |
||||
|
|
||||
|
byteLower512.CopyTo(byteActual); |
||||
|
byteUpper512.CopyTo(byteActual.AsSpan(Vector512<short>.Count)); |
||||
|
AssertEqual(byteExpected, byteActual, Vector512<byte>.Count); |
||||
|
|
||||
|
ushort[] uint16Source = new ushort[Vector512<ushort>.Count]; |
||||
|
ushort[] uint16Prediction = new ushort[Vector512<ushort>.Count]; |
||||
|
short[] uint16Expected = new short[Vector512<ushort>.Count]; |
||||
|
short[] uint16Actual = new short[Vector512<ushort>.Count]; |
||||
|
FillUInt16Planes(uint16Source, uint16Source.Length, uint16Prediction, uint16Prediction.Length, uint16Source.Length, 1, 4095); |
||||
|
FillReference(uint16Source, uint16Source.Length, uint16Prediction, uint16Prediction.Length, uint16Expected, uint16Expected.Length, uint16Expected.Length, 1); |
||||
|
|
||||
|
ref ushort uint16SourceBase = ref MemoryMarshal.GetArrayDataReference(uint16Source); |
||||
|
ref ushort uint16PredictionBase = ref MemoryMarshal.GetArrayDataReference(uint16Prediction); |
||||
|
Av1ResidualBuilder.UInt16Operator.Subtract( |
||||
|
Unsafe.As<ushort, Vector128<ushort>>(ref uint16SourceBase), |
||||
|
Unsafe.As<ushort, Vector128<ushort>>(ref uint16PredictionBase), |
||||
|
out _).CopyTo(uint16Actual); |
||||
|
|
||||
|
AssertEqual(uint16Expected, uint16Actual, Vector128<ushort>.Count); |
||||
|
|
||||
|
Av1ResidualBuilder.UInt16Operator.Subtract( |
||||
|
Unsafe.As<ushort, Vector256<ushort>>(ref uint16SourceBase), |
||||
|
Unsafe.As<ushort, Vector256<ushort>>(ref uint16PredictionBase), |
||||
|
out _).CopyTo(uint16Actual); |
||||
|
|
||||
|
AssertEqual(uint16Expected, uint16Actual, Vector256<ushort>.Count); |
||||
|
|
||||
|
Av1ResidualBuilder.UInt16Operator.Subtract( |
||||
|
Unsafe.As<ushort, Vector512<ushort>>(ref uint16SourceBase), |
||||
|
Unsafe.As<ushort, Vector512<ushort>>(ref uint16PredictionBase), |
||||
|
out _).CopyTo(uint16Actual); |
||||
|
|
||||
|
AssertEqual(uint16Expected, uint16Actual, Vector512<ushort>.Count); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies that repeated maximum-transform residual construction uses only caller-owned buffers.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void ResidualConstructionDoesNotAllocate() |
||||
|
{ |
||||
|
const int width = 64; |
||||
|
const int height = 64; |
||||
|
byte[] source = new byte[width * height]; |
||||
|
byte[] prediction = new byte[width * height]; |
||||
|
short[] residual = new short[width * height]; |
||||
|
ushort[] highBitDepthSource = new ushort[width * height]; |
||||
|
ushort[] highBitDepthPrediction = new ushort[width * height]; |
||||
|
short[] highBitDepthResidual = new short[width * height]; |
||||
|
FillBytePlanes(source, width, prediction, width, width, height); |
||||
|
FillUInt16Planes(highBitDepthSource, width, highBitDepthPrediction, width, width, height, 4095); |
||||
|
|
||||
|
Av1ResidualBuilder.Subtract(source, width, prediction, width, residual, width, width, height); |
||||
|
Av1ResidualBuilder.Subtract(highBitDepthSource, width, highBitDepthPrediction, width, highBitDepthResidual, width, width, height); |
||||
|
|
||||
|
long before = GC.GetAllocatedBytesForCurrentThread(); |
||||
|
for (int iteration = 0; iteration < 32; iteration++) |
||||
|
{ |
||||
|
Av1ResidualBuilder.Subtract(source, width, prediction, width, residual, width, width, height); |
||||
|
Av1ResidualBuilder.Subtract(highBitDepthSource, width, highBitDepthPrediction, width, highBitDepthResidual, width, width, height); |
||||
|
} |
||||
|
|
||||
|
Assert.Equal(0, GC.GetAllocatedBytesForCurrentThread() - before); |
||||
|
} |
||||
|
|
||||
|
private static void ValidateResiduals() |
||||
|
{ |
||||
|
ValidateByteResiduals(); |
||||
|
ValidateUInt16Residuals(1023); |
||||
|
ValidateUInt16Residuals(4095); |
||||
|
} |
||||
|
|
||||
|
private static void ValidateByteResiduals() |
||||
|
{ |
||||
|
const int width = 127; |
||||
|
const int height = 3; |
||||
|
const int sourceStride = 131; |
||||
|
const int predictionStride = 137; |
||||
|
const int residualStride = 139; |
||||
|
const int sourceOffset = 1; |
||||
|
const int predictionOffset = 2; |
||||
|
const int residualOffset = 3; |
||||
|
byte[] source = new byte[sourceOffset + (sourceStride * height)]; |
||||
|
byte[] prediction = new byte[predictionOffset + (predictionStride * height)]; |
||||
|
short[] expected = new short[residualOffset + (residualStride * height)]; |
||||
|
short[] actual = new short[expected.Length]; |
||||
|
Array.Fill(expected, short.MinValue); |
||||
|
Array.Fill(actual, short.MinValue); |
||||
|
|
||||
|
Span<byte> sourcePlane = source.AsSpan(sourceOffset); |
||||
|
Span<byte> predictionPlane = prediction.AsSpan(predictionOffset); |
||||
|
Span<short> expectedPlane = expected.AsSpan(residualOffset); |
||||
|
Span<short> actualPlane = actual.AsSpan(residualOffset); |
||||
|
FillBytePlanes(sourcePlane, sourceStride, predictionPlane, predictionStride, width, height); |
||||
|
FillReference(sourcePlane, sourceStride, predictionPlane, predictionStride, expectedPlane, residualStride, width, height); |
||||
|
|
||||
|
Av1ResidualBuilder.Subtract(sourcePlane, sourceStride, predictionPlane, predictionStride, actualPlane, residualStride, width, height); |
||||
|
|
||||
|
Assert.Equal(expected, actual); |
||||
|
} |
||||
|
|
||||
|
private static void ValidateUInt16Residuals(int maximumSample) |
||||
|
{ |
||||
|
const int width = 127; |
||||
|
const int height = 3; |
||||
|
const int sourceStride = 131; |
||||
|
const int predictionStride = 137; |
||||
|
const int residualStride = 139; |
||||
|
const int sourceOffset = 1; |
||||
|
const int predictionOffset = 2; |
||||
|
const int residualOffset = 3; |
||||
|
ushort[] source = new ushort[sourceOffset + (sourceStride * height)]; |
||||
|
ushort[] prediction = new ushort[predictionOffset + (predictionStride * height)]; |
||||
|
short[] expected = new short[residualOffset + (residualStride * height)]; |
||||
|
short[] actual = new short[expected.Length]; |
||||
|
Array.Fill(expected, short.MinValue); |
||||
|
Array.Fill(actual, short.MinValue); |
||||
|
|
||||
|
Span<ushort> sourcePlane = source.AsSpan(sourceOffset); |
||||
|
Span<ushort> predictionPlane = prediction.AsSpan(predictionOffset); |
||||
|
Span<short> expectedPlane = expected.AsSpan(residualOffset); |
||||
|
Span<short> actualPlane = actual.AsSpan(residualOffset); |
||||
|
FillUInt16Planes(sourcePlane, sourceStride, predictionPlane, predictionStride, width, height, maximumSample); |
||||
|
FillReference(sourcePlane, sourceStride, predictionPlane, predictionStride, expectedPlane, residualStride, width, height); |
||||
|
|
||||
|
Av1ResidualBuilder.Subtract(sourcePlane, sourceStride, predictionPlane, predictionStride, actualPlane, residualStride, width, height); |
||||
|
|
||||
|
Assert.Equal(expected, actual); |
||||
|
} |
||||
|
|
||||
|
private static void FillBytePlanes(Span<byte> source, int sourceStride, Span<byte> prediction, int predictionStride, int width, int height) |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
source[(y * sourceStride) + x] = (byte)(((x * 37) + (y * 19) + 251) & byte.MaxValue); |
||||
|
prediction[(y * predictionStride) + x] = (byte)(((x * 11) + (y * 43) + 127) & byte.MaxValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
source[0] = byte.MaxValue; |
||||
|
prediction[0] = 0; |
||||
|
source[1] = 0; |
||||
|
prediction[1] = byte.MaxValue; |
||||
|
} |
||||
|
|
||||
|
private static void FillUInt16Planes(Span<ushort> source, int sourceStride, Span<ushort> prediction, int predictionStride, int width, int height, int maximumSample) |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
source[(y * sourceStride) + x] = (ushort)(((x * 197) + (y * 389) + maximumSample) & maximumSample); |
||||
|
prediction[(y * predictionStride) + x] = (ushort)(((x * 283) + (y * 151) + (maximumSample / 2)) & maximumSample); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
source[0] = (ushort)maximumSample; |
||||
|
prediction[0] = 0; |
||||
|
source[1] = 0; |
||||
|
prediction[1] = (ushort)maximumSample; |
||||
|
} |
||||
|
|
||||
|
private static void FillReference( |
||||
|
ReadOnlySpan<byte> source, |
||||
|
int sourceStride, |
||||
|
ReadOnlySpan<byte> prediction, |
||||
|
int predictionStride, |
||||
|
Span<short> residual, |
||||
|
int residualStride, |
||||
|
int width, |
||||
|
int height) |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
residual[(y * residualStride) + x] = (short)(source[(y * sourceStride) + x] - prediction[(y * predictionStride) + x]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void FillReference( |
||||
|
ReadOnlySpan<ushort> source, |
||||
|
int sourceStride, |
||||
|
ReadOnlySpan<ushort> prediction, |
||||
|
int predictionStride, |
||||
|
Span<short> residual, |
||||
|
int residualStride, |
||||
|
int width, |
||||
|
int height) |
||||
|
{ |
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < width; x++) |
||||
|
{ |
||||
|
residual[(y * residualStride) + x] = (short)(source[(y * sourceStride) + x] - prediction[(y * predictionStride) + x]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void AssertEqual(ReadOnlySpan<short> expected, ReadOnlySpan<short> actual, int count) |
||||
|
{ |
||||
|
for (int i = 0; i < count; i++) |
||||
|
{ |
||||
|
Assert.Equal(expected[i], actual[i]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue