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