mirror of https://github.com/SixLabors/ImageSharp
9 changed files with 8095 additions and 168892 deletions
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,46 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines a Porter-Duff equation that can be applied by the shared pixel-blending traversal.
|
||||
|
/// </summary>
|
||||
|
internal interface IPixelBlenderOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Blends one pixel represented by four RGBA lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="background">The background RGBA lanes.</param>
|
||||
|
/// <param name="source">The source RGBA lanes.</param>
|
||||
|
/// <param name="amount">The source opacity in the range 0 through 1.</param>
|
||||
|
/// <returns>The blended RGBA lanes.</returns>
|
||||
|
static abstract Vector4 Invoke(Vector4 background, Vector4 source, float amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends two pixels represented by two consecutive groups of four RGBA lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="background">The background RGBA lanes.</param>
|
||||
|
/// <param name="source">The source RGBA lanes.</param>
|
||||
|
/// <param name="amount">The source opacity repeated across each pixel's four lanes.</param>
|
||||
|
/// <returns>The blended RGBA lanes.</returns>
|
||||
|
static abstract Vector256<float> Invoke( |
||||
|
Vector256<float> background, |
||||
|
Vector256<float> source, |
||||
|
Vector256<float> amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends four pixels represented by four consecutive groups of four RGBA lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="background">The background RGBA lanes.</param>
|
||||
|
/// <param name="source">The source RGBA lanes.</param>
|
||||
|
/// <param name="amount">The source opacity repeated across each pixel's four lanes.</param>
|
||||
|
/// <returns>The blended RGBA lanes.</returns>
|
||||
|
static abstract Vector512<float> Invoke( |
||||
|
Vector512<float> background, |
||||
|
Vector512<float> source, |
||||
|
Vector512<float> amount); |
||||
|
} |
||||
@ -0,0 +1,706 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using System.Runtime.Intrinsics.X86; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a statically selected Porter-Duff equation across pixel-vector rows.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The destination pixel format.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The Porter-Duff equation.</typeparam>
|
||||
|
internal abstract class PixelBlender<TPixel, TOperator> : PixelBlender<TPixel> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
where TOperator : struct, IPixelBlenderOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendFunction( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
ReadOnlySpan<Vector4> source, |
||||
|
float amount) |
||||
|
{ |
||||
|
// Public entry points validate the row lengths, so all three references can advance in lockstep.
|
||||
|
int scalarStart = 0; |
||||
|
amount = Numerics.Clamp(amount, 0, 1F); |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
|
||||
|
if (Avx512F.IsSupported && destination.Length >= 4) |
||||
|
{ |
||||
|
// A 512-bit register holds four complete RGBA pixels in [R,G,B,A] groups.
|
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> amountVector = Vector512.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundBase, i), |
||||
|
Unsafe.Add(ref sourceBase, i), |
||||
|
amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Avx2.IsSupported && destination.Length >= 2) |
||||
|
{ |
||||
|
// A 256-bit register holds two complete RGBA pixels in [R,G,B,A] groups.
|
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> amountVector = Vector256.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundBase, i), |
||||
|
Unsafe.Add(ref sourceBase, i), |
||||
|
amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
// Vector4 is both the scalar pixel representation and the portable SIMD fallback.
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundRef, (uint)i), |
||||
|
Unsafe.Add(ref sourceRef, (uint)i), |
||||
|
amount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendFunction( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
Vector4 source, |
||||
|
float amount) |
||||
|
{ |
||||
|
// Public entry points validate the row lengths, so the destination and background advance together.
|
||||
|
int scalarStart = 0; |
||||
|
amount = Numerics.Clamp(amount, 0, 1F); |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
|
||||
|
if (Avx512F.IsSupported && destination.Length >= 4) |
||||
|
{ |
||||
|
// Repeat one [R,G,B,A] source group four times to match the four background pixels.
|
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> sourceVector = CreateVector512(source); |
||||
|
Vector512<float> amountVector = Vector512.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundBase, i), |
||||
|
sourceVector, |
||||
|
amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Avx2.IsSupported && destination.Length >= 2) |
||||
|
{ |
||||
|
// Repeat one [R,G,B,A] source group twice to match the two background pixels.
|
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> sourceVector = CreateVector256(source); |
||||
|
Vector256<float> amountVector = Vector256.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundBase, i), |
||||
|
sourceVector, |
||||
|
amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
// The remaining pixel count is at most three after AVX-512 or one after AVX2.
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundRef, (uint)i), |
||||
|
source, |
||||
|
amount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendFunction( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
ReadOnlySpan<Vector4> source, |
||||
|
ReadOnlySpan<float> amount) |
||||
|
{ |
||||
|
// Each amount belongs to one pixel and must be repeated across that pixel's four RGBA lanes.
|
||||
|
int scalarStart = 0; |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
ref float amountRef = ref MemoryMarshal.GetReference(amount); |
||||
|
|
||||
|
if (Avx512F.IsSupported && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
// Four scalar amounts become four contiguous [a,a,a,a] groups before clamping.
|
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); |
||||
|
Vector512<float> amountVector = CreateClampedVector512(ref amountBase); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundBase, i), |
||||
|
Unsafe.Add(ref sourceBase, i), |
||||
|
amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Avx2.IsSupported && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
// Two scalar amounts become two contiguous [a,a,a,a] groups before clamping.
|
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); |
||||
|
Vector256<float> amountVector = CreateClampedVector256(ref amountBase); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundBase, i), |
||||
|
Unsafe.Add(ref sourceBase, i), |
||||
|
amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundRef, (uint)i), |
||||
|
Unsafe.Add(ref sourceRef, (uint)i), |
||||
|
Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendFunction( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
Vector4 source, |
||||
|
ReadOnlySpan<float> amount) |
||||
|
{ |
||||
|
// The source is invariant, while each background pixel has its own independently clamped amount.
|
||||
|
int scalarStart = 0; |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref float amountRef = ref MemoryMarshal.GetReference(amount); |
||||
|
|
||||
|
if (Avx512F.IsSupported && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> sourceVector = CreateVector512(source); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); |
||||
|
Vector512<float> amountVector = CreateClampedVector512(ref amountBase); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundBase, i), |
||||
|
sourceVector, |
||||
|
amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Avx2.IsSupported && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> sourceVector = CreateVector256(source); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); |
||||
|
Vector256<float> amountVector = CreateClampedVector256(ref amountBase); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundBase, i), |
||||
|
sourceVector, |
||||
|
amountVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke( |
||||
|
Unsafe.Add(ref backgroundRef, (uint)i), |
||||
|
source, |
||||
|
Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendWithCoverageFunction( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
ReadOnlySpan<Vector4> source, |
||||
|
float amount, |
||||
|
ReadOnlySpan<float> coverage) |
||||
|
{ |
||||
|
// Coverage mixes the composed result back toward the original background, so it is fused into this pass.
|
||||
|
int scalarStart = 0; |
||||
|
amount = Numerics.Clamp(amount, 0, 1F); |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
ref float coverageRef = ref MemoryMarshal.GetReference(coverage); |
||||
|
|
||||
|
if (Avx512F.IsSupported && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> amountVector = Vector512.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector512<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); |
||||
|
Vector512<float> coverageVector = CreateClampedVector512(ref coverageBase); |
||||
|
Vector512<float> blended = TOperator.Invoke( |
||||
|
backgroundVector, |
||||
|
Unsafe.Add(ref sourceBase, i), |
||||
|
amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundVector, |
||||
|
blended, |
||||
|
coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Avx2.IsSupported && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> amountVector = Vector256.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector256<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); |
||||
|
Vector256<float> coverageVector = CreateClampedVector256(ref coverageBase); |
||||
|
Vector256<float> blended = TOperator.Invoke( |
||||
|
backgroundVector, |
||||
|
Unsafe.Add(ref sourceBase, i), |
||||
|
amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundVector, |
||||
|
blended, |
||||
|
coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); |
||||
|
Vector4 blended = TOperator.Invoke( |
||||
|
backgroundPixel, |
||||
|
Unsafe.Add(ref sourceRef, (uint)i), |
||||
|
amount); |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundPixel, |
||||
|
blended, |
||||
|
Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendWithCoverageFunction( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
Vector4 source, |
||||
|
float amount, |
||||
|
ReadOnlySpan<float> coverage) |
||||
|
{ |
||||
|
// The constant source is expanded once per selected width and reused for the complete row.
|
||||
|
int scalarStart = 0; |
||||
|
amount = Numerics.Clamp(amount, 0, 1F); |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref float coverageRef = ref MemoryMarshal.GetReference(coverage); |
||||
|
|
||||
|
if (Avx512F.IsSupported && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> sourceVector = CreateVector512(source); |
||||
|
Vector512<float> amountVector = Vector512.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector512<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); |
||||
|
Vector512<float> coverageVector = CreateClampedVector512(ref coverageBase); |
||||
|
Vector512<float> blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundVector, |
||||
|
blended, |
||||
|
coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Avx2.IsSupported && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> sourceVector = CreateVector256(source); |
||||
|
Vector256<float> amountVector = Vector256.Create(amount); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector256<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); |
||||
|
Vector256<float> coverageVector = CreateClampedVector256(ref coverageBase); |
||||
|
Vector256<float> blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundVector, |
||||
|
blended, |
||||
|
coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); |
||||
|
Vector4 blended = TOperator.Invoke(backgroundPixel, source, amount); |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundPixel, |
||||
|
blended, |
||||
|
Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendWithCoverageFunction( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
ReadOnlySpan<Vector4> source, |
||||
|
ReadOnlySpan<float> amount, |
||||
|
ReadOnlySpan<float> coverage) |
||||
|
{ |
||||
|
// Amount controls composition while coverage controls the final mix with the untouched background.
|
||||
|
int scalarStart = 0; |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); |
||||
|
ref float amountRef = ref MemoryMarshal.GetReference(amount); |
||||
|
ref float coverageRef = ref MemoryMarshal.GetReference(coverage); |
||||
|
|
||||
|
if (Avx512F.IsSupported && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector512<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); |
||||
|
Vector512<float> amountVector = CreateClampedVector512(ref amountBase); |
||||
|
Vector512<float> coverageVector = CreateClampedVector512(ref coverageBase); |
||||
|
Vector512<float> blended = TOperator.Invoke( |
||||
|
backgroundVector, |
||||
|
Unsafe.Add(ref sourceBase, i), |
||||
|
amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundVector, |
||||
|
blended, |
||||
|
coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Avx2.IsSupported && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref sourceRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector256<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); |
||||
|
Vector256<float> amountVector = CreateClampedVector256(ref amountBase); |
||||
|
Vector256<float> coverageVector = CreateClampedVector256(ref coverageBase); |
||||
|
Vector256<float> blended = TOperator.Invoke( |
||||
|
backgroundVector, |
||||
|
Unsafe.Add(ref sourceBase, i), |
||||
|
amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundVector, |
||||
|
blended, |
||||
|
coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); |
||||
|
Vector4 blended = TOperator.Invoke( |
||||
|
backgroundPixel, |
||||
|
Unsafe.Add(ref sourceRef, (uint)i), |
||||
|
Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundPixel, |
||||
|
blended, |
||||
|
Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected sealed override void BlendWithCoverageFunction( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
Vector4 source, |
||||
|
ReadOnlySpan<float> amount, |
||||
|
ReadOnlySpan<float> coverage) |
||||
|
{ |
||||
|
// The invariant source is expanded once; only amount and coverage are gathered for each vector batch.
|
||||
|
int scalarStart = 0; |
||||
|
|
||||
|
ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); |
||||
|
ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); |
||||
|
ref float amountRef = ref MemoryMarshal.GetReference(amount); |
||||
|
ref float coverageRef = ref MemoryMarshal.GetReference(coverage); |
||||
|
|
||||
|
if (Avx512F.IsSupported && destination.Length >= 4) |
||||
|
{ |
||||
|
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref destinationRef); |
||||
|
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 4; |
||||
|
Vector512<float> sourceVector = CreateVector512(source); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector512<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); |
||||
|
Vector512<float> amountVector = CreateClampedVector512(ref amountBase); |
||||
|
Vector512<float> coverageVector = CreateClampedVector512(ref coverageBase); |
||||
|
Vector512<float> blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundVector, |
||||
|
blended, |
||||
|
coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 4; |
||||
|
} |
||||
|
else if (Avx2.IsSupported && destination.Length >= 2) |
||||
|
{ |
||||
|
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref destinationRef); |
||||
|
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref backgroundRef); |
||||
|
int vectorCount = destination.Length / 2; |
||||
|
Vector256<float> sourceVector = CreateVector256(source); |
||||
|
|
||||
|
for (nuint i = 0; i < (uint)vectorCount; i++) |
||||
|
{ |
||||
|
ref Vector256<float> backgroundVector = ref Unsafe.Add(ref backgroundBase, i); |
||||
|
ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); |
||||
|
ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); |
||||
|
Vector256<float> amountVector = CreateClampedVector256(ref amountBase); |
||||
|
Vector256<float> coverageVector = CreateClampedVector256(ref coverageBase); |
||||
|
Vector256<float> blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); |
||||
|
|
||||
|
Unsafe.Add(ref destinationBase, i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundVector, |
||||
|
blended, |
||||
|
coverageVector); |
||||
|
} |
||||
|
|
||||
|
scalarStart = vectorCount * 2; |
||||
|
} |
||||
|
|
||||
|
for (int i = scalarStart; i < destination.Length; i++) |
||||
|
{ |
||||
|
Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); |
||||
|
Vector4 blended = TOperator.Invoke( |
||||
|
backgroundPixel, |
||||
|
source, |
||||
|
Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); |
||||
|
|
||||
|
Unsafe.Add(ref destinationRef, (uint)i) = PorterDuffFunctions.BlendWithCoverage( |
||||
|
backgroundPixel, |
||||
|
blended, |
||||
|
Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Repeats one RGBA pixel twice to fill a 256-bit register.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pixel">The pixel represented by ordered RGBA lanes.</param>
|
||||
|
/// <returns>Two consecutive copies of the pixel.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<float> CreateVector256(Vector4 pixel) |
||||
|
=> Vector256.Create(pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Repeats one RGBA pixel four times to fill a 512-bit register.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pixel">The pixel represented by ordered RGBA lanes.</param>
|
||||
|
/// <returns>Four consecutive copies of the pixel.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<float> CreateVector512(Vector4 pixel) |
||||
|
=> Vector512.Create( |
||||
|
pixel.X, |
||||
|
pixel.Y, |
||||
|
pixel.Z, |
||||
|
pixel.W, |
||||
|
pixel.X, |
||||
|
pixel.Y, |
||||
|
pixel.Z, |
||||
|
pixel.W, |
||||
|
pixel.X, |
||||
|
pixel.Y, |
||||
|
pixel.Z, |
||||
|
pixel.W, |
||||
|
pixel.X, |
||||
|
pixel.Y, |
||||
|
pixel.Z, |
||||
|
pixel.W); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Expands and clamps two per-pixel scalar values for two packed RGBA pixels.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The first of two contiguous scalar values.</param>
|
||||
|
/// <returns>Two consecutive groups containing one clamped value in each group's four lanes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<float> CreateClampedVector256(ref float values) |
||||
|
{ |
||||
|
Vector256<float> result = Vector256.Create( |
||||
|
Vector128.Create(values), |
||||
|
Vector128.Create(Unsafe.Add(ref values, 1))); |
||||
|
|
||||
|
// Amount and coverage share the same public 0..1 contract and therefore the same packed clamp.
|
||||
|
return Avx.Min(Avx.Max(Vector256<float>.Zero, result), Vector256.Create(1F)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Expands and clamps four per-pixel scalar values for four packed RGBA pixels.
|
||||
|
/// </summary>
|
||||
|
/// <param name="values">The first of four contiguous scalar values.</param>
|
||||
|
/// <returns>Four consecutive groups containing one clamped value in each group's four lanes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<float> CreateClampedVector512(ref float values) |
||||
|
{ |
||||
|
Vector512<float> result = Vector512.Create( |
||||
|
values, |
||||
|
values, |
||||
|
values, |
||||
|
values, |
||||
|
Unsafe.Add(ref values, 1), |
||||
|
Unsafe.Add(ref values, 1), |
||||
|
Unsafe.Add(ref values, 1), |
||||
|
Unsafe.Add(ref values, 1), |
||||
|
Unsafe.Add(ref values, 2), |
||||
|
Unsafe.Add(ref values, 2), |
||||
|
Unsafe.Add(ref values, 2), |
||||
|
Unsafe.Add(ref values, 2), |
||||
|
Unsafe.Add(ref values, 3), |
||||
|
Unsafe.Add(ref values, 3), |
||||
|
Unsafe.Add(ref values, 3), |
||||
|
Unsafe.Add(ref values, 3)); |
||||
|
|
||||
|
return Vector512.Min(Vector512.Max(Vector512<float>.Zero, result), Vector512.Create(1F)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a statically selected Porter-Duff equation to straight-alpha pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The straight-alpha pixel format.</typeparam>
|
||||
|
/// <typeparam name="TOperator">The Porter-Duff equation.</typeparam>
|
||||
|
internal abstract class DefaultPixelBlender<TPixel, TOperator> : PixelBlender<TPixel, TOperator> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
where TOperator : struct, IPixelBlenderOperator |
||||
|
{ |
||||
|
/// <inheritdoc />
|
||||
|
public sealed override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
// The operator consumes the same unassociated, scaled representation used by the bulk conversion path.
|
||||
|
Vector4 result = TOperator.Invoke( |
||||
|
background.ToUnassociatedScaledVector4(), |
||||
|
source.ToUnassociatedScaledVector4(), |
||||
|
Numerics.Clamp(amount, 0, 1F)); |
||||
|
|
||||
|
return TPixel.FromUnassociatedScaledVector4(result); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,327 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes every shared pixel-blender traversal shape for assembly inspection.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Seven pixels leave three scalar pixels after AVX-512 or one scalar pixel after AVX2, so each
|
||||
|
/// hardware job contains both its widest supported loop and the portable Vector4 remainder.
|
||||
|
/// </remarks>
|
||||
|
[Config(typeof(Config.Analysis))] |
||||
|
public class PixelBlenderTraversalAssembly |
||||
|
{ |
||||
|
private const int Count = 7; |
||||
|
private const float Amount = .625F; |
||||
|
|
||||
|
private readonly ExposedNormalSrcOverBlender blender = new(); |
||||
|
private readonly Vector4[] destination = new Vector4[Count]; |
||||
|
private readonly Vector4[] background = new Vector4[Count]; |
||||
|
private readonly Vector4[] source = new Vector4[Count]; |
||||
|
private readonly float[] amounts = new float[Count]; |
||||
|
private readonly float[] coverage = new float[Count]; |
||||
|
private Vector4 constantSource; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Populates all lanes with deterministic, non-constant values.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
Random random = new(42); |
||||
|
|
||||
|
for (int i = 0; i < Count; i++) |
||||
|
{ |
||||
|
// Distinct RGBA lanes make incorrect pixel grouping visible in both results and assembly.
|
||||
|
this.background[i] = CreatePixel(random); |
||||
|
this.source[i] = CreatePixel(random); |
||||
|
this.amounts[i] = random.NextSingle(); |
||||
|
this.coverage[i] = random.NextSingle(); |
||||
|
} |
||||
|
|
||||
|
this.constantSource = CreatePixel(random); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends a source row with one shared amount.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final destination pixel.</returns>
|
||||
|
[Benchmark] |
||||
|
public Vector4 SourceSpanScalarAmount() |
||||
|
{ |
||||
|
this.blender.BlendSourceSpanScalarAmount( |
||||
|
this.destination, |
||||
|
this.background, |
||||
|
this.source, |
||||
|
Amount); |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends a constant source with one shared amount.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final destination pixel.</returns>
|
||||
|
[Benchmark] |
||||
|
public Vector4 ConstantSourceScalarAmount() |
||||
|
{ |
||||
|
this.blender.BlendConstantSourceScalarAmount( |
||||
|
this.destination, |
||||
|
this.background, |
||||
|
this.constantSource, |
||||
|
Amount); |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends a source row with per-pixel amounts.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final destination pixel.</returns>
|
||||
|
[Benchmark] |
||||
|
public Vector4 SourceSpanAmountSpan() |
||||
|
{ |
||||
|
this.blender.BlendSourceSpanAmountSpan( |
||||
|
this.destination, |
||||
|
this.background, |
||||
|
this.source, |
||||
|
this.amounts); |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends a constant source with per-pixel amounts.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final destination pixel.</returns>
|
||||
|
[Benchmark] |
||||
|
public Vector4 ConstantSourceAmountSpan() |
||||
|
{ |
||||
|
this.blender.BlendConstantSourceAmountSpan( |
||||
|
this.destination, |
||||
|
this.background, |
||||
|
this.constantSource, |
||||
|
this.amounts); |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends a source row with one shared amount and per-pixel coverage.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final destination pixel.</returns>
|
||||
|
[Benchmark] |
||||
|
public Vector4 SourceSpanScalarAmountCoverage() |
||||
|
{ |
||||
|
this.blender.BlendSourceSpanScalarAmountCoverage( |
||||
|
this.destination, |
||||
|
this.background, |
||||
|
this.source, |
||||
|
Amount, |
||||
|
this.coverage); |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends a constant source with one shared amount and per-pixel coverage.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final destination pixel.</returns>
|
||||
|
[Benchmark] |
||||
|
public Vector4 ConstantSourceScalarAmountCoverage() |
||||
|
{ |
||||
|
this.blender.BlendConstantSourceScalarAmountCoverage( |
||||
|
this.destination, |
||||
|
this.background, |
||||
|
this.constantSource, |
||||
|
Amount, |
||||
|
this.coverage); |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends a source row with per-pixel amounts and coverage.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final destination pixel.</returns>
|
||||
|
[Benchmark] |
||||
|
public Vector4 SourceSpanAmountSpanCoverage() |
||||
|
{ |
||||
|
this.blender.BlendSourceSpanAmountSpanCoverage( |
||||
|
this.destination, |
||||
|
this.background, |
||||
|
this.source, |
||||
|
this.amounts, |
||||
|
this.coverage); |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends a constant source with per-pixel amounts and coverage.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final destination pixel.</returns>
|
||||
|
[Benchmark] |
||||
|
public Vector4 ConstantSourceAmountSpanCoverage() |
||||
|
{ |
||||
|
this.blender.BlendConstantSourceAmountSpanCoverage( |
||||
|
this.destination, |
||||
|
this.background, |
||||
|
this.constantSource, |
||||
|
this.amounts, |
||||
|
this.coverage); |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates one non-constant RGBA sample.
|
||||
|
/// </summary>
|
||||
|
/// <param name="random">The deterministic value source.</param>
|
||||
|
/// <returns>The sample pixel.</returns>
|
||||
|
private static Vector4 CreatePixel(Random random) |
||||
|
=> new(random.NextSingle(), random.NextSingle(), random.NextSingle(), random.NextSingle()); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes the protected shared traversal overloads without adding benchmark hooks to production APIs.
|
||||
|
/// </summary>
|
||||
|
private sealed class ExposedNormalSrcOverBlender : |
||||
|
DefaultPixelBlender<RgbaVector, DefaultPixelBlenderOperators.NormalSrcOver> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Invokes the source-span, scalar-amount traversal.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination vectors.</param>
|
||||
|
/// <param name="background">The background vectors.</param>
|
||||
|
/// <param name="source">The source vectors.</param>
|
||||
|
/// <param name="amount">The shared source amount.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void BlendSourceSpanScalarAmount( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
ReadOnlySpan<Vector4> source, |
||||
|
float amount) |
||||
|
=> this.BlendFunction(destination, background, source, amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the constant-source, scalar-amount traversal.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination vectors.</param>
|
||||
|
/// <param name="background">The background vectors.</param>
|
||||
|
/// <param name="source">The constant source vector.</param>
|
||||
|
/// <param name="amount">The shared source amount.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void BlendConstantSourceScalarAmount( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
Vector4 source, |
||||
|
float amount) |
||||
|
=> this.BlendFunction(destination, background, source, amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the source-span, amount-span traversal.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination vectors.</param>
|
||||
|
/// <param name="background">The background vectors.</param>
|
||||
|
/// <param name="source">The source vectors.</param>
|
||||
|
/// <param name="amount">The per-pixel source amounts.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void BlendSourceSpanAmountSpan( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
ReadOnlySpan<Vector4> source, |
||||
|
ReadOnlySpan<float> amount) |
||||
|
=> this.BlendFunction(destination, background, source, amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the constant-source, amount-span traversal.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination vectors.</param>
|
||||
|
/// <param name="background">The background vectors.</param>
|
||||
|
/// <param name="source">The constant source vector.</param>
|
||||
|
/// <param name="amount">The per-pixel source amounts.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void BlendConstantSourceAmountSpan( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
Vector4 source, |
||||
|
ReadOnlySpan<float> amount) |
||||
|
=> this.BlendFunction(destination, background, source, amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the source-span, scalar-amount traversal with coverage.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination vectors.</param>
|
||||
|
/// <param name="background">The background vectors.</param>
|
||||
|
/// <param name="source">The source vectors.</param>
|
||||
|
/// <param name="amount">The shared source amount.</param>
|
||||
|
/// <param name="coverage">The per-pixel coverage values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void BlendSourceSpanScalarAmountCoverage( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
ReadOnlySpan<Vector4> source, |
||||
|
float amount, |
||||
|
ReadOnlySpan<float> coverage) |
||||
|
=> this.BlendWithCoverageFunction(destination, background, source, amount, coverage); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the constant-source, scalar-amount traversal with coverage.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination vectors.</param>
|
||||
|
/// <param name="background">The background vectors.</param>
|
||||
|
/// <param name="source">The constant source vector.</param>
|
||||
|
/// <param name="amount">The shared source amount.</param>
|
||||
|
/// <param name="coverage">The per-pixel coverage values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void BlendConstantSourceScalarAmountCoverage( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
Vector4 source, |
||||
|
float amount, |
||||
|
ReadOnlySpan<float> coverage) |
||||
|
=> this.BlendWithCoverageFunction(destination, background, source, amount, coverage); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the source-span, amount-span traversal with coverage.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination vectors.</param>
|
||||
|
/// <param name="background">The background vectors.</param>
|
||||
|
/// <param name="source">The source vectors.</param>
|
||||
|
/// <param name="amount">The per-pixel source amounts.</param>
|
||||
|
/// <param name="coverage">The per-pixel coverage values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void BlendSourceSpanAmountSpanCoverage( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
ReadOnlySpan<Vector4> source, |
||||
|
ReadOnlySpan<float> amount, |
||||
|
ReadOnlySpan<float> coverage) |
||||
|
=> this.BlendWithCoverageFunction(destination, background, source, amount, coverage); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invokes the constant-source, amount-span traversal with coverage.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination vectors.</param>
|
||||
|
/// <param name="background">The background vectors.</param>
|
||||
|
/// <param name="source">The constant source vector.</param>
|
||||
|
/// <param name="amount">The per-pixel source amounts.</param>
|
||||
|
/// <param name="coverage">The per-pixel coverage values.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void BlendConstantSourceAmountSpanCoverage( |
||||
|
Span<Vector4> destination, |
||||
|
ReadOnlySpan<Vector4> background, |
||||
|
Vector4 source, |
||||
|
ReadOnlySpan<float> amount, |
||||
|
ReadOnlySpan<float> coverage) |
||||
|
=> this.BlendWithCoverageFunction(destination, background, source, amount, coverage); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue