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