mirror of https://github.com/SixLabors/ImageSharp
24 changed files with 2017 additions and 402 deletions
@ -0,0 +1,985 @@ |
|||
// 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.X86; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.FilmGrain; |
|||
|
|||
/// <summary>
|
|||
/// Applies selected AV1 grain blocks to restored luma and chroma samples.
|
|||
/// </summary>
|
|||
internal static class Av1FilmGrainNoise |
|||
{ |
|||
/// <summary>
|
|||
/// The lower restricted-range luma value at eight-bit precision.
|
|||
/// </summary>
|
|||
private const int RestrictedLumaMinimum = 16; |
|||
|
|||
/// <summary>
|
|||
/// The upper restricted-range luma value at eight-bit precision.
|
|||
/// </summary>
|
|||
private const int RestrictedLumaMaximum = 235; |
|||
|
|||
/// <summary>
|
|||
/// The lower restricted-range chroma value at eight-bit precision.
|
|||
/// </summary>
|
|||
private const int RestrictedChromaMinimum = 16; |
|||
|
|||
/// <summary>
|
|||
/// The upper restricted-range chroma value at eight-bit precision.
|
|||
/// </summary>
|
|||
private const int RestrictedChromaMaximum = 240; |
|||
|
|||
/// <summary>
|
|||
/// Adds a selected grain rectangle to its corresponding restored samples.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The native sample type.</typeparam>
|
|||
/// <param name="parameters">The complete frame grain parameters.</param>
|
|||
/// <param name="scalingY">The luma scaling lookup table.</param>
|
|||
/// <param name="scalingCb">The first chroma scaling lookup table.</param>
|
|||
/// <param name="scalingCr">The second chroma scaling lookup table.</param>
|
|||
/// <param name="luma">The restored luma rectangle.</param>
|
|||
/// <param name="cb">The restored first chroma rectangle.</param>
|
|||
/// <param name="cr">The restored second chroma rectangle.</param>
|
|||
/// <param name="lumaStride">The luma row stride in samples.</param>
|
|||
/// <param name="chromaStride">The chroma row stride in samples.</param>
|
|||
/// <param name="lumaGrain">The selected luma grain rectangle.</param>
|
|||
/// <param name="cbGrain">The selected first chroma grain rectangle.</param>
|
|||
/// <param name="crGrain">The selected second chroma grain rectangle.</param>
|
|||
/// <param name="lumaGrainStride">The luma grain row stride.</param>
|
|||
/// <param name="chromaGrainStride">The chroma grain row stride.</param>
|
|||
/// <param name="halfLumaHeight">Half the luma rectangle height.</param>
|
|||
/// <param name="halfLumaWidth">Half the luma rectangle width.</param>
|
|||
/// <param name="bitDepth">The decoded sample bit depth.</param>
|
|||
/// <param name="subsamplingX">The horizontal chroma subsampling shift.</param>
|
|||
/// <param name="subsamplingY">The vertical chroma subsampling shift.</param>
|
|||
/// <param name="isMonochrome">Whether the frame has no chroma planes.</param>
|
|||
/// <param name="isIdentityMatrix">Whether every plane uses the luma restricted range.</param>
|
|||
public static void Apply<TSample>( |
|||
ObuFilmGrainParameters parameters, |
|||
ReadOnlySpan<int> scalingY, |
|||
ReadOnlySpan<int> scalingCb, |
|||
ReadOnlySpan<int> scalingCr, |
|||
Span<TSample> luma, |
|||
Span<TSample> cb, |
|||
Span<TSample> cr, |
|||
int lumaStride, |
|||
int chromaStride, |
|||
ReadOnlySpan<int> lumaGrain, |
|||
ReadOnlySpan<int> cbGrain, |
|||
ReadOnlySpan<int> crGrain, |
|||
int lumaGrainStride, |
|||
int chromaGrainStride, |
|||
int halfLumaHeight, |
|||
int halfLumaWidth, |
|||
int bitDepth, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
bool isMonochrome, |
|||
bool isIdentityMatrix) |
|||
where TSample : unmanaged |
|||
{ |
|||
int scalingShift = (int)parameters.GrainScalingMinus8 + 8; |
|||
int roundingOffset = 1 << (scalingShift - 1); |
|||
int depthScale = 1 << (bitDepth - 8); |
|||
int sampleMaximum = (256 * depthScale) - 1; |
|||
int lumaMinimum = 0; |
|||
int lumaMaximum = sampleMaximum; |
|||
int chromaMinimum = 0; |
|||
int chromaMaximum = sampleMaximum; |
|||
if (parameters.ClipToRestrictedRange) |
|||
{ |
|||
// Restricted-range endpoints are signaled at eight-bit precision and scale exactly at higher depths.
|
|||
lumaMinimum = RestrictedLumaMinimum * depthScale; |
|||
lumaMaximum = RestrictedLumaMaximum * depthScale; |
|||
chromaMinimum = (isIdentityMatrix ? RestrictedLumaMinimum : RestrictedChromaMinimum) * depthScale; |
|||
chromaMaximum = (isIdentityMatrix ? RestrictedLumaMaximum : RestrictedChromaMaximum) * depthScale; |
|||
} |
|||
|
|||
if (!isMonochrome) |
|||
{ |
|||
int cbMultiplier = (int)parameters.CbMult - 128; |
|||
int cbLumaMultiplier = (int)parameters.CbLumaMult - 128; |
|||
int cbOffset = ((int)parameters.CbOffset * depthScale) - (256 * depthScale); |
|||
int crMultiplier = (int)parameters.CrMult - 128; |
|||
int crLumaMultiplier = (int)parameters.CrLumaMult - 128; |
|||
int crOffset = ((int)parameters.CrOffset * depthScale) - (256 * depthScale); |
|||
if (parameters.ChromaScalingFromLuma) |
|||
{ |
|||
// Unity in the Q6 luma-multiplier domain selects luma directly and removes the chroma contribution.
|
|||
cbMultiplier = 0; |
|||
cbLumaMultiplier = 64; |
|||
cbOffset = 0; |
|||
crMultiplier = 0; |
|||
crLumaMultiplier = 64; |
|||
crOffset = 0; |
|||
} |
|||
|
|||
ApplyChroma( |
|||
scalingCb, |
|||
scalingCr, |
|||
luma, |
|||
cb, |
|||
cr, |
|||
lumaStride, |
|||
chromaStride, |
|||
cbGrain, |
|||
crGrain, |
|||
chromaGrainStride, |
|||
halfLumaHeight << (1 - subsamplingY), |
|||
halfLumaWidth << (1 - subsamplingX), |
|||
bitDepth, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters.NumCbPoints != 0 || parameters.ChromaScalingFromLuma, |
|||
parameters.NumCrPoints != 0 || parameters.ChromaScalingFromLuma, |
|||
cbMultiplier, |
|||
cbLumaMultiplier, |
|||
cbOffset, |
|||
crMultiplier, |
|||
crLumaMultiplier, |
|||
crOffset, |
|||
roundingOffset, |
|||
scalingShift, |
|||
sampleMaximum, |
|||
chromaMinimum, |
|||
chromaMaximum); |
|||
} |
|||
|
|||
if (parameters.NumYPoints != 0) |
|||
{ |
|||
ApplyLuma( |
|||
scalingY, |
|||
luma, |
|||
lumaStride, |
|||
lumaGrain, |
|||
lumaGrainStride, |
|||
halfLumaHeight << 1, |
|||
halfLumaWidth << 1, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
lumaMinimum, |
|||
lumaMaximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the widest available luma traversal.
|
|||
/// </summary>
|
|||
private static void ApplyLuma<TSample>( |
|||
ReadOnlySpan<int> scaling, |
|||
Span<TSample> samples, |
|||
int sampleStride, |
|||
ReadOnlySpan<int> grain, |
|||
int grainStride, |
|||
int height, |
|||
int width, |
|||
int bitDepth, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int minimum, |
|||
int maximum) |
|||
where TSample : unmanaged |
|||
{ |
|||
if (Avx2.IsSupported) |
|||
{ |
|||
ApplyLuma( |
|||
scaling, |
|||
samples, |
|||
sampleStride, |
|||
grain, |
|||
grainStride, |
|||
height, |
|||
width, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
minimum, |
|||
maximum, |
|||
Vector256<int>.Zero); |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (CanVectorizeWithoutGather(bitDepth)) |
|||
{ |
|||
ApplyLuma( |
|||
scaling, |
|||
samples, |
|||
sampleStride, |
|||
grain, |
|||
grainStride, |
|||
height, |
|||
width, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
minimum, |
|||
maximum, |
|||
Vector128<int>.Zero); |
|||
|
|||
return; |
|||
} |
|||
|
|||
ApplyLumaScalar(scaling, samples, sampleStride, grain, grainStride, height, width, bitDepth, roundingOffset, scalingShift, minimum, maximum); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies luma grain eight samples at a time.
|
|||
/// </summary>
|
|||
private static void ApplyLuma<TSample>( |
|||
ReadOnlySpan<int> scaling, |
|||
Span<TSample> samples, |
|||
int sampleStride, |
|||
ReadOnlySpan<int> grain, |
|||
int grainStride, |
|||
int height, |
|||
int width, |
|||
int bitDepth, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int minimum, |
|||
int maximum, |
|||
Vector256<int> vector) |
|||
where TSample : unmanaged |
|||
{ |
|||
ref TSample sampleBase = ref MemoryMarshal.GetReference(samples); |
|||
ref int grainBase = ref MemoryMarshal.GetReference(grain); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int sampleRowOffset = row * sampleStride; |
|||
int grainRowOffset = row * grainStride; |
|||
int column = 0; |
|||
int vectorEnd = width - Vector256<int>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<int>.Count) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref sampleBase, sampleRowOffset + column); |
|||
Vector256<int> source = Av1FilmGrainSampleOperator<TSample>.Load8(ref destination); |
|||
Vector256<int> grainValues = Vector256.LoadUnsafe(ref grainBase, (nuint)(grainRowOffset + column)); |
|||
Vector256<int> result = AddNoise(source, grainValues, scaling, bitDepth, roundingOffset, scalingShift, minimum, maximum); |
|||
Av1FilmGrainSampleOperator<TSample>.Store8(ref destination, result); |
|||
} |
|||
|
|||
ApplyLumaScalar( |
|||
scaling, |
|||
samples.Slice(sampleRowOffset + column), |
|||
sampleStride, |
|||
grain.Slice(grainRowOffset + column), |
|||
grainStride, |
|||
1, |
|||
width - column, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
minimum, |
|||
maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies luma grain four samples at a time.
|
|||
/// </summary>
|
|||
private static void ApplyLuma<TSample>( |
|||
ReadOnlySpan<int> scaling, |
|||
Span<TSample> samples, |
|||
int sampleStride, |
|||
ReadOnlySpan<int> grain, |
|||
int grainStride, |
|||
int height, |
|||
int width, |
|||
int bitDepth, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int minimum, |
|||
int maximum, |
|||
Vector128<int> vector) |
|||
where TSample : unmanaged |
|||
{ |
|||
ref TSample sampleBase = ref MemoryMarshal.GetReference(samples); |
|||
ref int grainBase = ref MemoryMarshal.GetReference(grain); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int sampleRowOffset = row * sampleStride; |
|||
int grainRowOffset = row * grainStride; |
|||
int column = 0; |
|||
int vectorEnd = width - Vector128<int>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<int>.Count) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref sampleBase, sampleRowOffset + column); |
|||
Vector128<int> source = Av1FilmGrainSampleOperator<TSample>.Load4(ref destination); |
|||
Vector128<int> grainValues = Vector128.LoadUnsafe(ref grainBase, (nuint)(grainRowOffset + column)); |
|||
Vector128<int> result = AddNoise(source, grainValues, scaling, bitDepth, roundingOffset, scalingShift, minimum, maximum); |
|||
Av1FilmGrainSampleOperator<TSample>.Store4(ref destination, result); |
|||
} |
|||
|
|||
ApplyLumaScalar( |
|||
scaling, |
|||
samples.Slice(sampleRowOffset + column), |
|||
sampleStride, |
|||
grain.Slice(grainRowOffset + column), |
|||
grainStride, |
|||
1, |
|||
width - column, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
minimum, |
|||
maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the luma scalar remainder or complete scalar fallback.
|
|||
/// </summary>
|
|||
private static void ApplyLumaScalar<TSample>( |
|||
ReadOnlySpan<int> scaling, |
|||
Span<TSample> samples, |
|||
int sampleStride, |
|||
ReadOnlySpan<int> grain, |
|||
int grainStride, |
|||
int height, |
|||
int width, |
|||
int bitDepth, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int minimum, |
|||
int maximum) |
|||
where TSample : unmanaged |
|||
{ |
|||
ref TSample sampleBase = ref MemoryMarshal.GetReference(samples); |
|||
ref int grainBase = ref MemoryMarshal.GetReference(grain); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int sampleRowOffset = row * sampleStride; |
|||
int grainRowOffset = row * grainStride; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref sampleBase, sampleRowOffset + column); |
|||
int source = Av1FilmGrainSampleOperator<TSample>.Load(ref destination); |
|||
int scale = ScaleLookup(scaling, source, bitDepth); |
|||
int value = source + (((scale * Unsafe.Add(ref grainBase, grainRowOffset + column)) + roundingOffset) >> scalingShift); |
|||
Av1FilmGrainSampleOperator<TSample>.Store(ref destination, Av1Math.Clamp(value, minimum, maximum)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the widest available chroma traversal.
|
|||
/// </summary>
|
|||
private static void ApplyChroma<TSample>( |
|||
ReadOnlySpan<int> scalingCb, |
|||
ReadOnlySpan<int> scalingCr, |
|||
Span<TSample> luma, |
|||
Span<TSample> cb, |
|||
Span<TSample> cr, |
|||
int lumaStride, |
|||
int chromaStride, |
|||
ReadOnlySpan<int> cbGrain, |
|||
ReadOnlySpan<int> crGrain, |
|||
int grainStride, |
|||
int height, |
|||
int width, |
|||
int bitDepth, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
bool applyCb, |
|||
bool applyCr, |
|||
int cbMultiplier, |
|||
int cbLumaMultiplier, |
|||
int cbOffset, |
|||
int crMultiplier, |
|||
int crLumaMultiplier, |
|||
int crOffset, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int sampleMaximum, |
|||
int minimum, |
|||
int maximum) |
|||
where TSample : unmanaged |
|||
{ |
|||
if (Avx2.IsSupported) |
|||
{ |
|||
ApplyChroma( |
|||
scalingCb, |
|||
scalingCr, |
|||
luma, |
|||
cb, |
|||
cr, |
|||
lumaStride, |
|||
chromaStride, |
|||
cbGrain, |
|||
crGrain, |
|||
grainStride, |
|||
height, |
|||
width, |
|||
bitDepth, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
applyCb, |
|||
applyCr, |
|||
cbMultiplier, |
|||
cbLumaMultiplier, |
|||
cbOffset, |
|||
crMultiplier, |
|||
crLumaMultiplier, |
|||
crOffset, |
|||
roundingOffset, |
|||
scalingShift, |
|||
sampleMaximum, |
|||
minimum, |
|||
maximum, |
|||
Vector256<int>.Zero); |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (CanVectorizeWithoutGather(bitDepth)) |
|||
{ |
|||
ApplyChroma( |
|||
scalingCb, |
|||
scalingCr, |
|||
luma, |
|||
cb, |
|||
cr, |
|||
lumaStride, |
|||
chromaStride, |
|||
cbGrain, |
|||
crGrain, |
|||
grainStride, |
|||
height, |
|||
width, |
|||
bitDepth, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
applyCb, |
|||
applyCr, |
|||
cbMultiplier, |
|||
cbLumaMultiplier, |
|||
cbOffset, |
|||
crMultiplier, |
|||
crLumaMultiplier, |
|||
crOffset, |
|||
roundingOffset, |
|||
scalingShift, |
|||
sampleMaximum, |
|||
minimum, |
|||
maximum, |
|||
Vector128<int>.Zero); |
|||
|
|||
return; |
|||
} |
|||
|
|||
ApplyChromaScalar( |
|||
scalingCb, |
|||
scalingCr, |
|||
luma, |
|||
cb, |
|||
cr, |
|||
lumaStride, |
|||
chromaStride, |
|||
cbGrain, |
|||
crGrain, |
|||
grainStride, |
|||
height, |
|||
width, |
|||
bitDepth, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
applyCb, |
|||
applyCr, |
|||
cbMultiplier, |
|||
cbLumaMultiplier, |
|||
cbOffset, |
|||
crMultiplier, |
|||
crLumaMultiplier, |
|||
crOffset, |
|||
roundingOffset, |
|||
scalingShift, |
|||
sampleMaximum, |
|||
minimum, |
|||
maximum); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies chroma grain eight samples at a time.
|
|||
/// </summary>
|
|||
private static void ApplyChroma<TSample>( |
|||
ReadOnlySpan<int> scalingCb, |
|||
ReadOnlySpan<int> scalingCr, |
|||
Span<TSample> luma, |
|||
Span<TSample> cb, |
|||
Span<TSample> cr, |
|||
int lumaStride, |
|||
int chromaStride, |
|||
ReadOnlySpan<int> cbGrain, |
|||
ReadOnlySpan<int> crGrain, |
|||
int grainStride, |
|||
int height, |
|||
int width, |
|||
int bitDepth, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
bool applyCb, |
|||
bool applyCr, |
|||
int cbMultiplier, |
|||
int cbLumaMultiplier, |
|||
int cbOffset, |
|||
int crMultiplier, |
|||
int crLumaMultiplier, |
|||
int crOffset, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int sampleMaximum, |
|||
int minimum, |
|||
int maximum, |
|||
Vector256<int> vector) |
|||
where TSample : unmanaged |
|||
{ |
|||
ref TSample lumaBase = ref MemoryMarshal.GetReference(luma); |
|||
ref TSample cbBase = ref MemoryMarshal.GetReference(cb); |
|||
ref TSample crBase = ref MemoryMarshal.GetReference(cr); |
|||
ref int cbGrainBase = ref MemoryMarshal.GetReference(cbGrain); |
|||
ref int crGrainBase = ref MemoryMarshal.GetReference(crGrain); |
|||
Vector256<int> zero = Vector256<int>.Zero; |
|||
Vector256<int> maximumIndex = Vector256.Create(sampleMaximum); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref TSample lumaRow = ref Unsafe.Add(ref lumaBase, (row << subsamplingY) * lumaStride); |
|||
int chromaRowOffset = row * chromaStride; |
|||
int grainRowOffset = row * grainStride; |
|||
int column = 0; |
|||
int vectorEnd = width - Vector256<int>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<int>.Count) |
|||
{ |
|||
ref TSample lumaSource = ref Unsafe.Add(ref lumaRow, column << subsamplingX); |
|||
Vector256<int> averageLuma = Av1FilmGrainSampleOperator<TSample>.LoadChromaLuma8(ref lumaSource, subsamplingX); |
|||
if (applyCb) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref cbBase, chromaRowOffset + column); |
|||
Vector256<int> source = Av1FilmGrainSampleOperator<TSample>.Load8(ref destination); |
|||
Vector256<int> scalingIndex = ((averageLuma * cbLumaMultiplier) + (source * cbMultiplier)) >> 6; |
|||
scalingIndex = Vector256.Min(Vector256.Max(scalingIndex + Vector256.Create(cbOffset), zero), maximumIndex); |
|||
Vector256<int> grainValues = Vector256.LoadUnsafe(ref cbGrainBase, (nuint)(grainRowOffset + column)); |
|||
Vector256<int> result = AddNoise( |
|||
source, |
|||
grainValues, |
|||
scalingCb, |
|||
scalingIndex, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
minimum, |
|||
maximum); |
|||
|
|||
Av1FilmGrainSampleOperator<TSample>.Store8(ref destination, result); |
|||
} |
|||
|
|||
if (applyCr) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref crBase, chromaRowOffset + column); |
|||
Vector256<int> source = Av1FilmGrainSampleOperator<TSample>.Load8(ref destination); |
|||
Vector256<int> scalingIndex = ((averageLuma * crLumaMultiplier) + (source * crMultiplier)) >> 6; |
|||
scalingIndex = Vector256.Min(Vector256.Max(scalingIndex + Vector256.Create(crOffset), zero), maximumIndex); |
|||
Vector256<int> grainValues = Vector256.LoadUnsafe(ref crGrainBase, (nuint)(grainRowOffset + column)); |
|||
Vector256<int> result = AddNoise( |
|||
source, |
|||
grainValues, |
|||
scalingCr, |
|||
scalingIndex, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
minimum, |
|||
maximum); |
|||
|
|||
Av1FilmGrainSampleOperator<TSample>.Store8(ref destination, result); |
|||
} |
|||
} |
|||
|
|||
ApplyChromaScalar( |
|||
scalingCb, |
|||
scalingCr, |
|||
luma.Slice(((row << subsamplingY) * lumaStride) + (column << subsamplingX)), |
|||
cb.Slice(chromaRowOffset + column), |
|||
cr.Slice(chromaRowOffset + column), |
|||
lumaStride, |
|||
chromaStride, |
|||
cbGrain.Slice(grainRowOffset + column), |
|||
crGrain.Slice(grainRowOffset + column), |
|||
grainStride, |
|||
1, |
|||
width - column, |
|||
bitDepth, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
applyCb, |
|||
applyCr, |
|||
cbMultiplier, |
|||
cbLumaMultiplier, |
|||
cbOffset, |
|||
crMultiplier, |
|||
crLumaMultiplier, |
|||
crOffset, |
|||
roundingOffset, |
|||
scalingShift, |
|||
sampleMaximum, |
|||
minimum, |
|||
maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies chroma grain four samples at a time.
|
|||
/// </summary>
|
|||
private static void ApplyChroma<TSample>( |
|||
ReadOnlySpan<int> scalingCb, |
|||
ReadOnlySpan<int> scalingCr, |
|||
Span<TSample> luma, |
|||
Span<TSample> cb, |
|||
Span<TSample> cr, |
|||
int lumaStride, |
|||
int chromaStride, |
|||
ReadOnlySpan<int> cbGrain, |
|||
ReadOnlySpan<int> crGrain, |
|||
int grainStride, |
|||
int height, |
|||
int width, |
|||
int bitDepth, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
bool applyCb, |
|||
bool applyCr, |
|||
int cbMultiplier, |
|||
int cbLumaMultiplier, |
|||
int cbOffset, |
|||
int crMultiplier, |
|||
int crLumaMultiplier, |
|||
int crOffset, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int sampleMaximum, |
|||
int minimum, |
|||
int maximum, |
|||
Vector128<int> vector) |
|||
where TSample : unmanaged |
|||
{ |
|||
ref TSample lumaBase = ref MemoryMarshal.GetReference(luma); |
|||
ref TSample cbBase = ref MemoryMarshal.GetReference(cb); |
|||
ref TSample crBase = ref MemoryMarshal.GetReference(cr); |
|||
ref int cbGrainBase = ref MemoryMarshal.GetReference(cbGrain); |
|||
ref int crGrainBase = ref MemoryMarshal.GetReference(crGrain); |
|||
Vector128<int> zero = Vector128<int>.Zero; |
|||
Vector128<int> maximumIndex = Vector128.Create(sampleMaximum); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
ref TSample lumaRow = ref Unsafe.Add(ref lumaBase, (row << subsamplingY) * lumaStride); |
|||
int chromaRowOffset = row * chromaStride; |
|||
int grainRowOffset = row * grainStride; |
|||
int column = 0; |
|||
int vectorEnd = width - Vector128<int>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<int>.Count) |
|||
{ |
|||
ref TSample lumaSource = ref Unsafe.Add(ref lumaRow, column << subsamplingX); |
|||
Vector128<int> averageLuma = Av1FilmGrainSampleOperator<TSample>.LoadChromaLuma4(ref lumaSource, subsamplingX); |
|||
if (applyCb) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref cbBase, chromaRowOffset + column); |
|||
Vector128<int> source = Av1FilmGrainSampleOperator<TSample>.Load4(ref destination); |
|||
Vector128<int> scalingIndex = ((averageLuma * cbLumaMultiplier) + (source * cbMultiplier)) >> 6; |
|||
scalingIndex = Vector128.Min(Vector128.Max(scalingIndex + Vector128.Create(cbOffset), zero), maximumIndex); |
|||
Vector128<int> grainValues = Vector128.LoadUnsafe(ref cbGrainBase, (nuint)(grainRowOffset + column)); |
|||
Vector128<int> result = AddNoise( |
|||
source, |
|||
grainValues, |
|||
scalingCb, |
|||
scalingIndex, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
minimum, |
|||
maximum); |
|||
|
|||
Av1FilmGrainSampleOperator<TSample>.Store4(ref destination, result); |
|||
} |
|||
|
|||
if (applyCr) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref crBase, chromaRowOffset + column); |
|||
Vector128<int> source = Av1FilmGrainSampleOperator<TSample>.Load4(ref destination); |
|||
Vector128<int> scalingIndex = ((averageLuma * crLumaMultiplier) + (source * crMultiplier)) >> 6; |
|||
scalingIndex = Vector128.Min(Vector128.Max(scalingIndex + Vector128.Create(crOffset), zero), maximumIndex); |
|||
Vector128<int> grainValues = Vector128.LoadUnsafe(ref crGrainBase, (nuint)(grainRowOffset + column)); |
|||
Vector128<int> result = AddNoise( |
|||
source, |
|||
grainValues, |
|||
scalingCr, |
|||
scalingIndex, |
|||
bitDepth, |
|||
roundingOffset, |
|||
scalingShift, |
|||
minimum, |
|||
maximum); |
|||
|
|||
Av1FilmGrainSampleOperator<TSample>.Store4(ref destination, result); |
|||
} |
|||
} |
|||
|
|||
ApplyChromaScalar( |
|||
scalingCb, |
|||
scalingCr, |
|||
luma.Slice(((row << subsamplingY) * lumaStride) + (column << subsamplingX)), |
|||
cb.Slice(chromaRowOffset + column), |
|||
cr.Slice(chromaRowOffset + column), |
|||
lumaStride, |
|||
chromaStride, |
|||
cbGrain.Slice(grainRowOffset + column), |
|||
crGrain.Slice(grainRowOffset + column), |
|||
grainStride, |
|||
1, |
|||
width - column, |
|||
bitDepth, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
applyCb, |
|||
applyCr, |
|||
cbMultiplier, |
|||
cbLumaMultiplier, |
|||
cbOffset, |
|||
crMultiplier, |
|||
crLumaMultiplier, |
|||
crOffset, |
|||
roundingOffset, |
|||
scalingShift, |
|||
sampleMaximum, |
|||
minimum, |
|||
maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the chroma scalar remainder or complete scalar fallback.
|
|||
/// </summary>
|
|||
private static void ApplyChromaScalar<TSample>( |
|||
ReadOnlySpan<int> scalingCb, |
|||
ReadOnlySpan<int> scalingCr, |
|||
Span<TSample> luma, |
|||
Span<TSample> cb, |
|||
Span<TSample> cr, |
|||
int lumaStride, |
|||
int chromaStride, |
|||
ReadOnlySpan<int> cbGrain, |
|||
ReadOnlySpan<int> crGrain, |
|||
int grainStride, |
|||
int height, |
|||
int width, |
|||
int bitDepth, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
bool applyCb, |
|||
bool applyCr, |
|||
int cbMultiplier, |
|||
int cbLumaMultiplier, |
|||
int cbOffset, |
|||
int crMultiplier, |
|||
int crLumaMultiplier, |
|||
int crOffset, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int sampleMaximum, |
|||
int minimum, |
|||
int maximum) |
|||
where TSample : unmanaged |
|||
{ |
|||
ref TSample lumaBase = ref MemoryMarshal.GetReference(luma); |
|||
ref TSample cbBase = ref MemoryMarshal.GetReference(cb); |
|||
ref TSample crBase = ref MemoryMarshal.GetReference(cr); |
|||
ref int cbGrainBase = ref MemoryMarshal.GetReference(cbGrain); |
|||
ref int crGrainBase = ref MemoryMarshal.GetReference(crGrain); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int lumaRowOffset = (row << subsamplingY) * lumaStride; |
|||
int chromaRowOffset = row * chromaStride; |
|||
int grainRowOffset = row * grainStride; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int lumaOffset = lumaRowOffset + (column << subsamplingX); |
|||
int averageLuma = Av1FilmGrainSampleOperator<TSample>.Load(ref Unsafe.Add(ref lumaBase, lumaOffset)); |
|||
if (subsamplingX != 0) |
|||
{ |
|||
averageLuma = (averageLuma + Av1FilmGrainSampleOperator<TSample>.Load(ref Unsafe.Add(ref lumaBase, lumaOffset + 1)) + 1) >> 1; |
|||
} |
|||
|
|||
int chromaOffset = chromaRowOffset + column; |
|||
int grainOffset = grainRowOffset + column; |
|||
if (applyCb) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref cbBase, chromaOffset); |
|||
int source = Av1FilmGrainSampleOperator<TSample>.Load(ref destination); |
|||
int scalingIndex = Av1Math.Clamp( |
|||
(((averageLuma * cbLumaMultiplier) + (source * cbMultiplier)) >> 6) + cbOffset, |
|||
0, |
|||
sampleMaximum); |
|||
|
|||
int scale = ScaleLookup(scalingCb, scalingIndex, bitDepth); |
|||
int value = source + (((scale * Unsafe.Add(ref cbGrainBase, grainOffset)) + roundingOffset) >> scalingShift); |
|||
Av1FilmGrainSampleOperator<TSample>.Store(ref destination, Av1Math.Clamp(value, minimum, maximum)); |
|||
} |
|||
|
|||
if (applyCr) |
|||
{ |
|||
ref TSample destination = ref Unsafe.Add(ref crBase, chromaOffset); |
|||
int source = Av1FilmGrainSampleOperator<TSample>.Load(ref destination); |
|||
int scalingIndex = Av1Math.Clamp( |
|||
(((averageLuma * crLumaMultiplier) + (source * crMultiplier)) >> 6) + crOffset, |
|||
0, |
|||
sampleMaximum); |
|||
|
|||
int scale = ScaleLookup(scalingCr, scalingIndex, bitDepth); |
|||
int value = source + (((scale * Unsafe.Add(ref crGrainBase, grainOffset)) + roundingOffset) >> scalingShift); |
|||
Av1FilmGrainSampleOperator<TSample>.Store(ref destination, Av1Math.Clamp(value, minimum, maximum)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds scaled grain to eight source samples and clips the result.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> AddNoise( |
|||
Vector256<int> source, |
|||
Vector256<int> grain, |
|||
ReadOnlySpan<int> scaling, |
|||
int bitDepth, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int minimum, |
|||
int maximum) |
|||
=> AddNoise(source, grain, scaling, source, bitDepth, roundingOffset, scalingShift, minimum, maximum); |
|||
|
|||
/// <summary>
|
|||
/// Adds scaled grain to eight source samples using independent scaling coordinates.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> AddNoise( |
|||
Vector256<int> source, |
|||
Vector256<int> grain, |
|||
ReadOnlySpan<int> scaling, |
|||
Vector256<int> scalingIndex, |
|||
int bitDepth, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int minimum, |
|||
int maximum) |
|||
{ |
|||
Vector256<int> scale = ScaleLookup(scaling, scalingIndex, bitDepth); |
|||
Vector256<int> result = source + (((scale * grain) + Vector256.Create(roundingOffset)) >> scalingShift); |
|||
return Vector256.Min(Vector256.Max(result, Vector256.Create(minimum)), Vector256.Create(maximum)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds scaled grain to four source samples and clips the result.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> AddNoise( |
|||
Vector128<int> source, |
|||
Vector128<int> grain, |
|||
ReadOnlySpan<int> scaling, |
|||
int bitDepth, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int minimum, |
|||
int maximum) |
|||
=> AddNoise(source, grain, scaling, source, bitDepth, roundingOffset, scalingShift, minimum, maximum); |
|||
|
|||
/// <summary>
|
|||
/// Adds scaled grain to four source samples using independent scaling coordinates.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> AddNoise( |
|||
Vector128<int> source, |
|||
Vector128<int> grain, |
|||
ReadOnlySpan<int> scaling, |
|||
Vector128<int> scalingIndex, |
|||
int bitDepth, |
|||
int roundingOffset, |
|||
int scalingShift, |
|||
int minimum, |
|||
int maximum) |
|||
{ |
|||
Vector128<int> scale = ScaleLookup(scaling, scalingIndex, bitDepth); |
|||
Vector128<int> result = source + (((scale * grain) + Vector128.Create(roundingOffset)) >> scalingShift); |
|||
return Vector128.Min(Vector128.Max(result, Vector128.Create(minimum)), Vector128.Create(maximum)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether portable vector arithmetic repays the cost of scalar scaling-table reads.
|
|||
/// </summary>
|
|||
/// <param name="bitDepth">The decoded sample bit depth.</param>
|
|||
/// <returns>Whether to use the portable vector traversal.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static bool CanVectorizeWithoutGather(int bitDepth) |
|||
{ |
|||
// Portable Vector128 has no indexed table load. At eight bits, assembling each scaling vector from four
|
|||
// scalar reads is slower than the complete scalar loop; high-depth interpolation contains enough arithmetic
|
|||
// to amortize those reads. The AVX2 path uses native gather and remains the primary traversal at every depth.
|
|||
return bitDepth > 8 && Vector128.IsHardwareAccelerated; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gathers eight scaling values and interpolates high-bit-depth coordinates.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static unsafe Vector256<int> ScaleLookup(ReadOnlySpan<int> scaling, Vector256<int> index, int bitDepth) |
|||
{ |
|||
int depthShift = bitDepth - 8; |
|||
Vector256<int> tableIndex = index >> depthShift; |
|||
fixed (int* table = scaling) |
|||
{ |
|||
Vector256<int> current = Avx2.GatherVector256(table, tableIndex, sizeof(int)); |
|||
if (depthShift == 0) |
|||
{ |
|||
return current; |
|||
} |
|||
|
|||
// Clamping the following index extends entry 255 across the final interpolation interval.
|
|||
Vector256<int> nextIndex = Vector256.Min(tableIndex + Vector256<int>.One, Vector256.Create(255)); |
|||
Vector256<int> next = Avx2.GatherVector256(table, nextIndex, sizeof(int)); |
|||
Vector256<int> fraction = index & Vector256.Create((1 << depthShift) - 1); |
|||
return current + ((((next - current) * fraction) + Vector256.Create(1 << (depthShift - 1))) >> depthShift); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads four scaling values and interpolates high-bit-depth coordinates.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> ScaleLookup(ReadOnlySpan<int> scaling, Vector128<int> index, int bitDepth) |
|||
=> Vector128.Create( |
|||
ScaleLookup(scaling, index.GetElement(0), bitDepth), |
|||
ScaleLookup(scaling, index.GetElement(1), bitDepth), |
|||
ScaleLookup(scaling, index.GetElement(2), bitDepth), |
|||
ScaleLookup(scaling, index.GetElement(3), bitDepth)); |
|||
|
|||
/// <summary>
|
|||
/// Reads one scaling value, interpolating between eight-bit entries when required.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int ScaleLookup(ReadOnlySpan<int> scaling, int index, int bitDepth) |
|||
{ |
|||
int depthShift = bitDepth - 8; |
|||
int tableIndex = index >> depthShift; |
|||
if (depthShift == 0 || tableIndex == 255) |
|||
{ |
|||
return scaling[tableIndex]; |
|||
} |
|||
|
|||
int fraction = index & ((1 << depthShift) - 1); |
|||
return scaling[tableIndex] + ((((scaling[tableIndex + 1] - scaling[tableIndex]) * fraction) + (1 << (depthShift - 1))) >> depthShift); |
|||
} |
|||
} |
|||
@ -0,0 +1,313 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.FilmGrain; |
|||
|
|||
/// <summary>
|
|||
/// Blends grain samples across adjacent synthesis blocks.
|
|||
/// </summary>
|
|||
internal static class Av1FilmGrainOverlap |
|||
{ |
|||
/// <summary>
|
|||
/// Blends the two grain columns on a vertical block boundary.
|
|||
/// </summary>
|
|||
/// <param name="left">The saved grain columns from the block on the left.</param>
|
|||
/// <param name="leftStride">The saved-column row stride.</param>
|
|||
/// <param name="right">The grain columns selected for the block on the right.</param>
|
|||
/// <param name="rightStride">The right-block row stride.</param>
|
|||
/// <param name="destination">The overlap destination.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="width">The one- or two-sample overlap width.</param>
|
|||
/// <param name="height">The overlap height.</param>
|
|||
/// <param name="minimum">The minimum grain value.</param>
|
|||
/// <param name="maximum">The maximum grain value.</param>
|
|||
public static void Vertical( |
|||
ReadOnlySpan<int> left, |
|||
int leftStride, |
|||
ReadOnlySpan<int> right, |
|||
int rightStride, |
|||
Span<int> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height, |
|||
int minimum, |
|||
int maximum) |
|||
{ |
|||
// Each row contributes only one or two strided samples. Gather plus scalar scatter would do more work than
|
|||
// the fixed scalar kernel, while the horizontally contiguous boundary below benefits directly from SIMD.
|
|||
if (width == 1) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int leftOffset = row * leftStride; |
|||
int rightOffset = row * rightStride; |
|||
int destinationOffset = row * destinationStride; |
|||
|
|||
// A subsampled one-column boundary uses the dedicated 23:22 overlap weights.
|
|||
destination[destinationOffset] = Av1Math.Clamp( |
|||
((left[leftOffset] * 23) + (right[rightOffset] * 22) + 16) >> 5, |
|||
minimum, |
|||
maximum); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
int leftOffset = row * leftStride; |
|||
int rightOffset = row * rightStride; |
|||
int destinationOffset = row * destinationStride; |
|||
|
|||
// The two-column kernel biases the outer samples toward their originating block and crosses the 27:17
|
|||
// weights for the inner samples. These fixed weights are part of AV1 grain synthesis.
|
|||
destination[destinationOffset] = Av1Math.Clamp( |
|||
((left[leftOffset] * 27) + (right[rightOffset] * 17) + 16) >> 5, |
|||
minimum, |
|||
maximum); |
|||
|
|||
destination[destinationOffset + 1] = Av1Math.Clamp( |
|||
((left[leftOffset + 1] * 17) + (right[rightOffset + 1] * 27) + 16) >> 5, |
|||
minimum, |
|||
maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends the one or two grain rows on a horizontal block boundary.
|
|||
/// </summary>
|
|||
/// <param name="top">The saved grain rows from the block above.</param>
|
|||
/// <param name="topStride">The saved-row stride.</param>
|
|||
/// <param name="bottom">The grain rows selected for the block below.</param>
|
|||
/// <param name="bottomStride">The lower-block row stride.</param>
|
|||
/// <param name="destination">The overlap destination.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="width">The overlap width.</param>
|
|||
/// <param name="height">The one- or two-sample overlap height.</param>
|
|||
/// <param name="minimum">The minimum grain value.</param>
|
|||
/// <param name="maximum">The maximum grain value.</param>
|
|||
public static void Horizontal( |
|||
ReadOnlySpan<int> top, |
|||
int topStride, |
|||
ReadOnlySpan<int> bottom, |
|||
int bottomStride, |
|||
Span<int> destination, |
|||
int destinationStride, |
|||
int width, |
|||
int height, |
|||
int minimum, |
|||
int maximum) |
|||
{ |
|||
if (height == 1) |
|||
{ |
|||
// Vertically subsampled chroma collapses the overlap to the single-row 23:22 kernel.
|
|||
BlendRow(top, bottom, destination, width, 23, 22, minimum, maximum); |
|||
return; |
|||
} |
|||
|
|||
// Luma and full-height chroma use the crossed two-row 27:17 overlap kernel.
|
|||
BlendRow(top, bottom, destination, width, 27, 17, minimum, maximum); |
|||
BlendRow( |
|||
top[topStride..], |
|||
bottom[bottomStride..], |
|||
destination[destinationStride..], |
|||
width, |
|||
17, |
|||
27, |
|||
minimum, |
|||
maximum); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends one contiguous overlap row using the widest useful vector width and a scalar remainder.
|
|||
/// </summary>
|
|||
/// <param name="left">The samples from the preceding block.</param>
|
|||
/// <param name="right">The samples from the following block.</param>
|
|||
/// <param name="destination">The blended samples.</param>
|
|||
/// <param name="width">The number of samples to blend.</param>
|
|||
/// <param name="leftWeight">The preceding-block weight.</param>
|
|||
/// <param name="rightWeight">The following-block weight.</param>
|
|||
/// <param name="minimum">The minimum grain value.</param>
|
|||
/// <param name="maximum">The maximum grain value.</param>
|
|||
private static void BlendRow( |
|||
ReadOnlySpan<int> left, |
|||
ReadOnlySpan<int> right, |
|||
Span<int> destination, |
|||
int width, |
|||
int leftWeight, |
|||
int rightWeight, |
|||
int minimum, |
|||
int maximum) |
|||
{ |
|||
int column = 0; |
|||
|
|||
// Vector<T> exposes the runtime's preferred native width. This avoids selecting split 512-bit operations on
|
|||
// machines whose execution resources are 256 bits wide while retaining a native 512-bit traversal elsewhere.
|
|||
if (Vector512.IsHardwareAccelerated && Vector<int>.Count == Vector512<int>.Count) |
|||
{ |
|||
column = Blend(left, right, destination, width, column, leftWeight, rightWeight, minimum, maximum, Vector512<int>.Zero); |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
column = Blend(left, right, destination, width, column, leftWeight, rightWeight, minimum, maximum, Vector256<int>.Zero); |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
column = Blend(left, right, destination, width, column, leftWeight, rightWeight, minimum, maximum, Vector128<int>.Zero); |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int value = ((left[column] * leftWeight) + (right[column] * rightWeight) + 16) >> 5; |
|||
destination[column] = Av1Math.Clamp(value, minimum, maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends complete 512-bit groups from one overlap row.
|
|||
/// </summary>
|
|||
/// <param name="left">The samples from the preceding block.</param>
|
|||
/// <param name="right">The samples from the following block.</param>
|
|||
/// <param name="destination">The blended samples.</param>
|
|||
/// <param name="width">The number of samples to blend.</param>
|
|||
/// <param name="column">The first unprocessed sample.</param>
|
|||
/// <param name="leftWeight">The preceding-block weight.</param>
|
|||
/// <param name="rightWeight">The following-block weight.</param>
|
|||
/// <param name="minimum">The minimum grain value.</param>
|
|||
/// <param name="maximum">The maximum grain value.</param>
|
|||
/// <param name="vector">The overload-selection value.</param>
|
|||
/// <returns>The first sample not processed by this vector width.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int Blend( |
|||
ReadOnlySpan<int> left, |
|||
ReadOnlySpan<int> right, |
|||
Span<int> destination, |
|||
int width, |
|||
int column, |
|||
int leftWeight, |
|||
int rightWeight, |
|||
int minimum, |
|||
int maximum, |
|||
Vector512<int> vector) |
|||
{ |
|||
ref int leftBase = ref MemoryMarshal.GetReference(left); |
|||
ref int rightBase = ref MemoryMarshal.GetReference(right); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Vector512<int> leftWeights = Vector512.Create(leftWeight); |
|||
Vector512<int> rightWeights = Vector512.Create(rightWeight); |
|||
Vector512<int> rounding = Vector512.Create(16); |
|||
Vector512<int> minima = Vector512.Create(minimum); |
|||
Vector512<int> maxima = Vector512.Create(maximum); |
|||
int vectorEnd = width - Vector512<int>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> leftValues = Vector512.LoadUnsafe(ref leftBase, (nuint)column); |
|||
Vector512<int> rightValues = Vector512.LoadUnsafe(ref rightBase, (nuint)column); |
|||
Vector512<int> result = ((leftValues * leftWeights) + (rightValues * rightWeights) + rounding) >> 5; |
|||
Vector512.Min(Vector512.Max(result, minima), maxima).StoreUnsafe(ref destinationBase, (nuint)column); |
|||
} |
|||
|
|||
return column; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends complete 256-bit groups from one overlap row.
|
|||
/// </summary>
|
|||
/// <param name="left">The samples from the preceding block.</param>
|
|||
/// <param name="right">The samples from the following block.</param>
|
|||
/// <param name="destination">The blended samples.</param>
|
|||
/// <param name="width">The number of samples to blend.</param>
|
|||
/// <param name="column">The first unprocessed sample.</param>
|
|||
/// <param name="leftWeight">The preceding-block weight.</param>
|
|||
/// <param name="rightWeight">The following-block weight.</param>
|
|||
/// <param name="minimum">The minimum grain value.</param>
|
|||
/// <param name="maximum">The maximum grain value.</param>
|
|||
/// <param name="vector">The overload-selection value.</param>
|
|||
/// <returns>The first sample not processed by this vector width.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int Blend( |
|||
ReadOnlySpan<int> left, |
|||
ReadOnlySpan<int> right, |
|||
Span<int> destination, |
|||
int width, |
|||
int column, |
|||
int leftWeight, |
|||
int rightWeight, |
|||
int minimum, |
|||
int maximum, |
|||
Vector256<int> vector) |
|||
{ |
|||
ref int leftBase = ref MemoryMarshal.GetReference(left); |
|||
ref int rightBase = ref MemoryMarshal.GetReference(right); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Vector256<int> leftWeights = Vector256.Create(leftWeight); |
|||
Vector256<int> rightWeights = Vector256.Create(rightWeight); |
|||
Vector256<int> rounding = Vector256.Create(16); |
|||
Vector256<int> minima = Vector256.Create(minimum); |
|||
Vector256<int> maxima = Vector256.Create(maximum); |
|||
int vectorEnd = width - Vector256<int>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> leftValues = Vector256.LoadUnsafe(ref leftBase, (nuint)column); |
|||
Vector256<int> rightValues = Vector256.LoadUnsafe(ref rightBase, (nuint)column); |
|||
Vector256<int> result = ((leftValues * leftWeights) + (rightValues * rightWeights) + rounding) >> 5; |
|||
Vector256.Min(Vector256.Max(result, minima), maxima).StoreUnsafe(ref destinationBase, (nuint)column); |
|||
} |
|||
|
|||
return column; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends complete 128-bit groups from one overlap row.
|
|||
/// </summary>
|
|||
/// <param name="left">The samples from the preceding block.</param>
|
|||
/// <param name="right">The samples from the following block.</param>
|
|||
/// <param name="destination">The blended samples.</param>
|
|||
/// <param name="width">The number of samples to blend.</param>
|
|||
/// <param name="column">The first unprocessed sample.</param>
|
|||
/// <param name="leftWeight">The preceding-block weight.</param>
|
|||
/// <param name="rightWeight">The following-block weight.</param>
|
|||
/// <param name="minimum">The minimum grain value.</param>
|
|||
/// <param name="maximum">The maximum grain value.</param>
|
|||
/// <param name="vector">The overload-selection value.</param>
|
|||
/// <returns>The first sample not processed by this vector width.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int Blend( |
|||
ReadOnlySpan<int> left, |
|||
ReadOnlySpan<int> right, |
|||
Span<int> destination, |
|||
int width, |
|||
int column, |
|||
int leftWeight, |
|||
int rightWeight, |
|||
int minimum, |
|||
int maximum, |
|||
Vector128<int> vector) |
|||
{ |
|||
ref int leftBase = ref MemoryMarshal.GetReference(left); |
|||
ref int rightBase = ref MemoryMarshal.GetReference(right); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Vector128<int> leftWeights = Vector128.Create(leftWeight); |
|||
Vector128<int> rightWeights = Vector128.Create(rightWeight); |
|||
Vector128<int> rounding = Vector128.Create(16); |
|||
Vector128<int> minima = Vector128.Create(minimum); |
|||
Vector128<int> maxima = Vector128.Create(maximum); |
|||
int vectorEnd = width - Vector128<int>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> leftValues = Vector128.LoadUnsafe(ref leftBase, (nuint)column); |
|||
Vector128<int> rightValues = Vector128.LoadUnsafe(ref rightBase, (nuint)column); |
|||
Vector128<int> result = ((leftValues * leftWeights) + (rightValues * rightWeights) + rounding) >> 5; |
|||
Vector128.Min(Vector128.Max(result, minima), maxima).StoreUnsafe(ref destinationBase, (nuint)column); |
|||
} |
|||
|
|||
return column; |
|||
} |
|||
} |
|||
@ -0,0 +1,189 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.FilmGrain; |
|||
|
|||
/// <summary>
|
|||
/// Loads and stores native AV1 samples for the film-grain arithmetic pipeline.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The native sample type.</typeparam>
|
|||
internal readonly struct Av1FilmGrainSampleOperator<TSample> |
|||
where TSample : unmanaged |
|||
{ |
|||
/// <summary>
|
|||
/// Loads eight consecutive samples into 32-bit lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <returns>The widened samples.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<int> Load8(ref TSample source) |
|||
{ |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
ref byte sourceBytes = ref Unsafe.As<TSample, byte>(ref source); |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref sourceBytes); |
|||
return Avx2.ConvertToVector256Int32(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
} |
|||
|
|||
ref ushort sourceValues = ref Unsafe.As<TSample, ushort>(ref source); |
|||
return Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sourceValues)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads four consecutive samples into 32-bit lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <returns>The widened samples.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> Load4(ref TSample source) |
|||
{ |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
ref byte sourceBytes = ref Unsafe.As<TSample, byte>(ref source); |
|||
uint packedBytes = Unsafe.ReadUnaligned<uint>(ref sourceBytes); |
|||
Vector128<ushort> widened = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packedBytes).AsByte()); |
|||
return Vector128.WidenLower(widened).AsInt32(); |
|||
} |
|||
|
|||
ref ushort sourceValues = ref Unsafe.As<TSample, ushort>(ref source); |
|||
ulong packedValues = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<ushort, byte>(ref sourceValues)); |
|||
Vector64<ushort> packedSamples = Vector64.CreateScalarUnsafe(packedValues).AsUInt16(); |
|||
return Vector128.WidenLower(Vector128.Create(packedSamples, Vector64<ushort>.Zero)).AsInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads the luma coordinates corresponding to eight chroma samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first luma sample.</param>
|
|||
/// <param name="subsamplingX">The horizontal chroma subsampling shift.</param>
|
|||
/// <returns>The luma values used by chroma scaling.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<int> LoadChromaLuma8(ref TSample source, int subsamplingX) |
|||
{ |
|||
if (subsamplingX == 0) |
|||
{ |
|||
return Load8(ref source); |
|||
} |
|||
|
|||
Vector256<short> lumaPairs; |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
ref byte sourceBytes = ref Unsafe.As<TSample, byte>(ref source); |
|||
Vector128<byte> packed = Vector128.LoadUnsafe(ref sourceBytes); |
|||
lumaPairs = Vector256.Create(Vector128.WidenLower(packed).AsInt16(), Vector128.WidenUpper(packed).AsInt16()); |
|||
} |
|||
else |
|||
{ |
|||
ref ushort sourceValues = ref Unsafe.As<TSample, ushort>(ref source); |
|||
lumaPairs = Vector256.LoadUnsafe(ref sourceValues).AsInt16(); |
|||
} |
|||
|
|||
// Horizontal 4:2:x chroma uses the rounded mean of each adjacent luma pair.
|
|||
return (Avx2.MultiplyAddAdjacent(lumaPairs, Vector256.Create((short)1)) + Vector256<int>.One) >> 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads the luma coordinates corresponding to four chroma samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first luma sample.</param>
|
|||
/// <param name="subsamplingX">The horizontal chroma subsampling shift.</param>
|
|||
/// <returns>The luma values used by chroma scaling.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> LoadChromaLuma4(ref TSample source, int subsamplingX) |
|||
{ |
|||
if (subsamplingX == 0) |
|||
{ |
|||
return Load4(ref source); |
|||
} |
|||
|
|||
Vector128<short> lumaPairs; |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
ref byte sourceBytes = ref Unsafe.As<TSample, byte>(ref source); |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref sourceBytes); |
|||
lumaPairs = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()).AsInt16(); |
|||
} |
|||
else |
|||
{ |
|||
ref ushort sourceValues = ref Unsafe.As<TSample, ushort>(ref source); |
|||
lumaPairs = Vector128.LoadUnsafe(ref sourceValues).AsInt16(); |
|||
} |
|||
|
|||
// Multiply-add with unity coefficients collapses four adjacent pairs without scalar deinterleaving.
|
|||
return (Vector128_.MultiplyAddAdjacent(lumaPairs, Vector128.Create((short)1)) + Vector128<int>.One) >> 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores eight already clipped 32-bit samples in their native representation.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
/// <param name="values">The samples to store.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Store8(ref TSample destination, Vector256<int> values) |
|||
{ |
|||
Vector128<ushort> packed = Vector128.Narrow(values.GetLower().AsUInt32(), values.GetUpper().AsUInt32()); |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
Vector64<byte> bytes = Vector128.Narrow(packed, Vector128<ushort>.Zero).GetLower(); |
|||
bytes.StoreUnsafe(ref Unsafe.As<TSample, byte>(ref destination)); |
|||
} |
|||
else |
|||
{ |
|||
packed.StoreUnsafe(ref Unsafe.As<TSample, ushort>(ref destination)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores four already clipped 32-bit samples in their native representation.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
/// <param name="values">The samples to store.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Store4(ref TSample destination, Vector128<int> values) |
|||
{ |
|||
Vector64<ushort> packed = Vector128.Narrow(values.AsUInt32(), Vector128<uint>.Zero).GetLower(); |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
Vector64<byte> bytes = Vector128.Narrow(Vector128.Create(packed, Vector64<ushort>.Zero), Vector128<ushort>.Zero).GetLower(); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<TSample, byte>(ref destination), bytes.AsUInt32().GetElement(0)); |
|||
} |
|||
else |
|||
{ |
|||
packed.StoreUnsafe(ref Unsafe.As<TSample, ushort>(ref destination)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads one native sample as an integer.
|
|||
/// </summary>
|
|||
/// <param name="source">The source sample.</param>
|
|||
/// <returns>The unsigned sample value.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Load(ref TSample source) |
|||
=> typeof(TSample) == typeof(byte) |
|||
? Unsafe.As<TSample, byte>(ref source) |
|||
: Unsafe.As<TSample, ushort>(ref source); |
|||
|
|||
/// <summary>
|
|||
/// Stores one already clipped sample in its native representation.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination sample.</param>
|
|||
/// <param name="value">The sample value.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Store(ref TSample destination, int value) |
|||
{ |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
Unsafe.As<TSample, byte>(ref destination) = (byte)value; |
|||
} |
|||
else |
|||
{ |
|||
Unsafe.As<TSample, ushort>(ref destination) = (ushort)value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,272 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
using BenchmarkDotNet.Columns; |
|||
using BenchmarkDotNet.Configs; |
|||
using BenchmarkDotNet.Jobs; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.FilmGrain; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; |
|||
|
|||
/// <summary>
|
|||
/// Measures AV1 film-grain application across full-HD-equivalent 4:2:0 component planes.
|
|||
/// </summary>
|
|||
[Config(typeof(Configuration))] |
|||
[MemoryDiagnoser(displayGenColumns: false)] |
|||
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
|||
[CategoriesColumn] |
|||
public class Av1FilmGrainBenchmarks |
|||
{ |
|||
/// <summary>
|
|||
/// The aligned full-HD luma width.
|
|||
/// </summary>
|
|||
private const int Width = 1920; |
|||
|
|||
/// <summary>
|
|||
/// The aligned full-HD luma height.
|
|||
/// </summary>
|
|||
private const int Height = 1088; |
|||
|
|||
/// <summary>
|
|||
/// The luma width and height of one selected grain block.
|
|||
/// </summary>
|
|||
private const int BlockSize = 32; |
|||
|
|||
/// <summary>
|
|||
/// The 4:2:0 chroma-plane width.
|
|||
/// </summary>
|
|||
private const int ChromaWidth = Width / 2; |
|||
|
|||
/// <summary>
|
|||
/// The 4:2:0 chroma-plane height.
|
|||
/// </summary>
|
|||
private const int ChromaHeight = Height / 2; |
|||
|
|||
/// <summary>
|
|||
/// The 4:2:0 chroma width and height of one selected grain block.
|
|||
/// </summary>
|
|||
private const int ChromaBlockSize = BlockSize / 2; |
|||
|
|||
/// <summary>
|
|||
/// The deterministic eight-bit luma plane.
|
|||
/// </summary>
|
|||
private readonly byte[] luma8 = new byte[Width * Height]; |
|||
|
|||
/// <summary>
|
|||
/// The deterministic eight-bit first chroma plane.
|
|||
/// </summary>
|
|||
private readonly byte[] cb8 = new byte[ChromaWidth * ChromaHeight]; |
|||
|
|||
/// <summary>
|
|||
/// The deterministic eight-bit second chroma plane.
|
|||
/// </summary>
|
|||
private readonly byte[] cr8 = new byte[ChromaWidth * ChromaHeight]; |
|||
|
|||
/// <summary>
|
|||
/// The deterministic twelve-bit luma plane.
|
|||
/// </summary>
|
|||
private readonly ushort[] luma12 = new ushort[Width * Height]; |
|||
|
|||
/// <summary>
|
|||
/// The deterministic twelve-bit first chroma plane.
|
|||
/// </summary>
|
|||
private readonly ushort[] cb12 = new ushort[ChromaWidth * ChromaHeight]; |
|||
|
|||
/// <summary>
|
|||
/// The deterministic twelve-bit second chroma plane.
|
|||
/// </summary>
|
|||
private readonly ushort[] cr12 = new ushort[ChromaWidth * ChromaHeight]; |
|||
|
|||
/// <summary>
|
|||
/// The expanded luma scaling function.
|
|||
/// </summary>
|
|||
private readonly int[] scalingY = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The expanded first chroma scaling function.
|
|||
/// </summary>
|
|||
private readonly int[] scalingCb = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The expanded second chroma scaling function.
|
|||
/// </summary>
|
|||
private readonly int[] scalingCr = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The selected luma grain block.
|
|||
/// </summary>
|
|||
private readonly int[] lumaGrain = new int[BlockSize * BlockSize]; |
|||
|
|||
/// <summary>
|
|||
/// The selected first chroma grain block.
|
|||
/// </summary>
|
|||
private readonly int[] cbGrain = new int[ChromaBlockSize * ChromaBlockSize]; |
|||
|
|||
/// <summary>
|
|||
/// The selected second chroma grain block.
|
|||
/// </summary>
|
|||
private readonly int[] crGrain = new int[ChromaBlockSize * ChromaBlockSize]; |
|||
|
|||
/// <summary>
|
|||
/// The active grain parameters shared by both measured sample precisions.
|
|||
/// </summary>
|
|||
private readonly ObuFilmGrainParameters parameters = new() |
|||
{ |
|||
NumYPoints = 2, |
|||
ChromaScalingFromLuma = true, |
|||
GrainScalingMinus8 = 3 |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Populates deterministic source planes and grain blocks outside the measured traversal.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
for (int index = 0; index < this.luma8.Length; index++) |
|||
{ |
|||
int value = ((index * 37) + 113) & byte.MaxValue; |
|||
this.luma8[index] = (byte)value; |
|||
this.luma12[index] = (ushort)(value << 4); |
|||
} |
|||
|
|||
for (int index = 0; index < this.cb8.Length; index++) |
|||
{ |
|||
int cb = ((index * 53) + 97) & byte.MaxValue; |
|||
int cr = ((index * 71) + 41) & byte.MaxValue; |
|||
this.cb8[index] = (byte)cb; |
|||
this.cr8[index] = (byte)cr; |
|||
this.cb12[index] = (ushort)(cb << 4); |
|||
this.cr12[index] = (ushort)(cr << 4); |
|||
} |
|||
|
|||
for (int index = 0; index < this.lumaGrain.Length; index++) |
|||
{ |
|||
this.lumaGrain[index] = ((index * 29) & byte.MaxValue) - 128; |
|||
} |
|||
|
|||
for (int index = 0; index < this.cbGrain.Length; index++) |
|||
{ |
|||
this.cbGrain[index] = ((index * 43) & byte.MaxValue) - 128; |
|||
this.crGrain[index] = ((index * 61) & byte.MaxValue) - 128; |
|||
} |
|||
|
|||
// A zero scaling function keeps every invocation's source planes stable. The measured code still performs the
|
|||
// production lookup, interpolation, grain multiplication, clipping, and native sample packing for every lane.
|
|||
this.scalingY.AsSpan().Clear(); |
|||
this.scalingCb.AsSpan().Clear(); |
|||
this.scalingCr.AsSpan().Clear(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies eight-bit grain blocks across full-HD-equivalent 4:2:0 planes.
|
|||
/// </summary>
|
|||
/// <returns>The final luma sample, keeping the output observable.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("8Bit")] |
|||
public byte Apply8Bit() |
|||
{ |
|||
for (int y = 0; y < Height; y += BlockSize) |
|||
{ |
|||
for (int x = 0; x < Width; x += BlockSize) |
|||
{ |
|||
int lumaOffset = (y * Width) + x; |
|||
int chromaOffset = ((y / 2) * ChromaWidth) + (x / 2); |
|||
|
|||
Av1FilmGrainNoise.Apply( |
|||
this.parameters, |
|||
this.scalingY, |
|||
this.scalingCb, |
|||
this.scalingCr, |
|||
this.luma8.AsSpan(lumaOffset), |
|||
this.cb8.AsSpan(chromaOffset), |
|||
this.cr8.AsSpan(chromaOffset), |
|||
Width, |
|||
ChromaWidth, |
|||
this.lumaGrain, |
|||
this.cbGrain, |
|||
this.crGrain, |
|||
BlockSize, |
|||
ChromaBlockSize, |
|||
BlockSize / 2, |
|||
BlockSize / 2, |
|||
8, |
|||
1, |
|||
1, |
|||
isMonochrome: false, |
|||
isIdentityMatrix: false); |
|||
} |
|||
} |
|||
|
|||
return this.luma8[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies twelve-bit grain blocks across full-HD-equivalent 4:2:0 planes.
|
|||
/// </summary>
|
|||
/// <returns>The final luma sample, keeping the output observable.</returns>
|
|||
[Benchmark] |
|||
[BenchmarkCategory("12Bit")] |
|||
public ushort Apply12Bit() |
|||
{ |
|||
for (int y = 0; y < Height; y += BlockSize) |
|||
{ |
|||
for (int x = 0; x < Width; x += BlockSize) |
|||
{ |
|||
int lumaOffset = (y * Width) + x; |
|||
int chromaOffset = ((y / 2) * ChromaWidth) + (x / 2); |
|||
|
|||
Av1FilmGrainNoise.Apply( |
|||
this.parameters, |
|||
this.scalingY, |
|||
this.scalingCb, |
|||
this.scalingCr, |
|||
this.luma12.AsSpan(lumaOffset), |
|||
this.cb12.AsSpan(chromaOffset), |
|||
this.cr12.AsSpan(chromaOffset), |
|||
Width, |
|||
ChromaWidth, |
|||
this.lumaGrain, |
|||
this.cbGrain, |
|||
this.crGrain, |
|||
BlockSize, |
|||
ChromaBlockSize, |
|||
BlockSize / 2, |
|||
BlockSize / 2, |
|||
12, |
|||
1, |
|||
1, |
|||
isMonochrome: false, |
|||
isIdentityMatrix: false); |
|||
} |
|||
} |
|||
|
|||
return this.luma12[^1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Configures production-process measurements for hardware, no-AVX, and scalar grain application.
|
|||
/// </summary>
|
|||
public sealed class Configuration : ManualConfig |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Configuration"/> class.
|
|||
/// </summary>
|
|||
public Configuration() |
|||
{ |
|||
this.AddJob(Job.ShortRun.WithId("Hardware").AsBaseline()); |
|||
|
|||
this.AddJob( |
|||
Job.ShortRun |
|||
.WithId("NoAvx") |
|||
.WithEnvironmentVariable("DOTNET_EnableAVX", "0")); |
|||
|
|||
this.AddJob( |
|||
Job.ShortRun |
|||
.WithId("Scalar") |
|||
.WithEnvironmentVariable("DOTNET_EnableHWIntrinsic", "0")); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:2bcf2a42e9f01044a32557e45ac19d0077c4bb3f2791cb3d01e22d86d9d9c7c5 |
|||
size 24000 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:9fa3da33ba476f569c7825e134f7727571463416814486e95656806756d91b69 |
|||
size 407 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:7970ad0150245766dddc2b6ce480e1b0be68c734d0d15fd1f38a0d7b87ec42c9 |
|||
size 36000 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:d295643fb78a8705a2ff2de6c9b40db9d94fb92a510038b00d5fc9ebee83d9a5 |
|||
size 364 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:583077d4dc530c57271ab4c990708dbe5765622978878f4305b5f64a6512fc37 |
|||
size 36000 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:48fe85c152a0ad35adfe963a62164f8f105cbc88e88af6e7e0a6c6dc4e124f79 |
|||
size 475 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:bb0afbff82dc67937b7c3411b11eb8ed73033bf68194c01bf947750258ea00b1 |
|||
size 6000 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:e16477e1ce47f0b4c30c21168c0e33e1bfb3c48d67e37ea4cddee8a2c0478457 |
|||
size 229 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:6433d14c6b863eca1b244d1e3834832de1cc231e2f3733633c88aef3cb326132 |
|||
size 9000 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:c1b6dc8bba3a944a9abd041e3f8f253806f4689412ffab8fb71b74bc99281480 |
|||
size 9000 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:aba642e2258e7da69ce54f333bd9a30037e22cbca191ad7674e5fc341bb154df |
|||
size 379 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0f082a3cbbe9f4d16ba8735ae48d95fa9a06fdca56658dd9e937a1c661d0e45b |
|||
size 381 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:b86740f4707d30d3835504b5b39622ed8dd88418bb052a88e3edd84258a6230a |
|||
size 567 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:ae77f08e0c30ada187e7a630b4b502cd8349227f9128d449b5d1e3badcc5dd1a |
|||
size 153 |
|||
Loading…
Reference in new issue