Browse Source

Normalize pixel blending SIMD traversal

pull/3161/head
James Jackson-South 3 weeks ago
parent
commit
873c64ec12
  1. 87036
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs
  2. 850
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt
  3. 16
      src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs
  4. 87044
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  5. 858
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  6. 46
      src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs
  7. 706
      src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs
  8. 327
      tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs
  9. 104
      tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs

87036
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs

File diff suppressed because it is too large

850
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt

@ -4,31 +4,19 @@
#> #>
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #> <#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #> <#@ output extension=".cs" #>
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
// <auto-generated /> // <auto-generated />
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// <summary>
/// Provides generated Porter-Duff blenders for associated-alpha pixel formats.
/// </summary>
internal static partial class AssociatedAlphaPixelBlenders<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
<# <#
var composers = new []{ var composers = new []
{
"Src", "Src",
"SrcAtop", "SrcAtop",
"SrcOver", "SrcOver",
@ -43,7 +31,8 @@ var composers = new []{
"Xor", "Xor",
}; };
var blenders = new []{ var blenders = new []
{
"Normal", "Normal",
"Multiply", "Multiply",
"Add", "Add",
@ -52,796 +41,79 @@ var blenders = new []{
"Darken", "Darken",
"Lighten", "Lighten",
"Overlay", "Overlay",
"HardLight" "HardLight",
}; };
#>
foreach(var composer in composers) { /// <summary>
foreach(var blender in blenders) { /// Provides the associated-alpha equations consumed by the shared pixel-blending traversal.
/// </summary>
var blender_composer= $"{blender}{composer}"; internal static class AssociatedAlphaPixelBlenderOperators
{
<#
foreach (var composer in composers)
{
foreach (var blender in blenders)
{
var blenderComposer = $"{blender}{composer}";
#> #>
/// <summary> /// <summary>
/// A pixel blender that implements the "<#= blender_composer#>" composition equation. /// Applies the "<#= blenderComposer #>" associated-alpha composition equation.
/// </summary> /// </summary>
public sealed class <#= blender_composer#> : AssociatedAlphaPixelBlender<TPixel> public readonly struct <#= blenderComposer #> : IPixelBlenderOperator
{ {
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>();
/// <inheritdoc /> /// <inheritdoc />
public override TPixel Blend(TPixel background, TPixel source, float amount) public static Vector4 Invoke(Vector4 background, Vector4 source, float amount)
{ => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount);
return TPixel.FromAssociatedScaledVector4(AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background.ToAssociatedScaledVector4(), source.ToAssociatedScaledVector4(), Numerics.Clamp(amount, 0, 1)));
}
/// <inheritdoc /> /// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount) public static Vector256<float> Invoke(
{ Vector256<float> background,
amount = Numerics.Clamp(amount, 0, 1); Vector256<float> source,
Vector256<float> amount)
if (Avx512F.IsSupported && destination.Length >= 4) => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount);
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
Vector512<float> opacity = Vector512.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
Vector256<float> opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
}
/// <inheritdoc /> /// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount) public static Vector512<float> Invoke(
{ Vector512<float> background,
amount = Numerics.Clamp(amount, 0, 1); Vector512<float> source,
Vector512<float> amount)
if (Avx512F.IsSupported && destination.Length >= 4) => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount);
{ }
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> opacity = Vector512.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount)
{
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
destinationBase = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount, ReadOnlySpan<float> coverage)
{
amount = Numerics.Clamp(amount, 0, 1);
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> opacity = Vector512.Create(amount);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> opacity = Vector256.Create(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount, ReadOnlySpan<float> coverage)
{
amount = Numerics.Clamp(amount, 0, 1);
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> opacity = Vector512.Create(amount);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> opacity = Vector256.Create(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage)
{
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage)
{
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); <#
destinationBase = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); }
destinationBase = ref Unsafe.Add(ref destinationBase, 1); }
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); #>
amountBase = ref Unsafe.Add(ref amountBase, 2); }
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0) /// <summary>
{ /// Provides associated-alpha pixel blenders for each color and alpha composition pair.
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. /// </summary>
int i = destination.Length - 1; /// <typeparam name="TPixel">The associated-alpha destination pixel format.</typeparam>
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); internal static partial class AssociatedAlphaPixelBlenders<TPixel>
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); where TPixel : unmanaged, IPixel<TPixel>
} {
} <#
else foreach (var composer in composers)
{ {
for (int i = 0; i < destination.Length; i++) foreach (var blender in blenders)
{ {
Vector4 blended = AssociatedAlphaPorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); var blenderComposer = $"{blender}{composer}";
destination[i] = AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); #>
} /// <summary>
} /// Blends pixels with the "<#= blenderComposer #>" composition equation.
} /// </summary>
public sealed class <#= blenderComposer #> :
AssociatedAlphaPixelBlender<TPixel, AssociatedAlphaPixelBlenderOperators.<#= blenderComposer #>>
{
/// <summary>
/// Gets the shared blender instance.
/// </summary>
public static <#= blenderComposer #> Instance { get; } = new();
} }
<# <#
} }
} }
#> #>
} }

16
src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel}.cs → src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlender{TPixel,TOperator}.cs

@ -9,11 +9,25 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// Provides the vector representation used to blend pixels that store associated alpha. /// Provides the vector representation used to blend pixels that store associated alpha.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The associated-alpha pixel format.</typeparam> /// <typeparam name="TPixel">The associated-alpha pixel format.</typeparam>
internal abstract class AssociatedAlphaPixelBlender<TPixel> : PixelBlender<TPixel> /// <typeparam name="TOperator">The Porter-Duff equation.</typeparam>
internal abstract class AssociatedAlphaPixelBlender<TPixel, TOperator> : PixelBlender<TPixel, TOperator>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
where TOperator : struct, IPixelBlenderOperator
{ {
private static readonly PixelOperations<TPixel> Operations = PixelOperations<TPixel>.Instance; private static readonly PixelOperations<TPixel> Operations = PixelOperations<TPixel>.Instance;
/// <inheritdoc />
public sealed override TPixel Blend(TPixel background, TPixel source, float amount)
{
// Associated RGB and alpha must remain in their stored representation throughout composition.
Vector4 result = TOperator.Invoke(
background.ToAssociatedScaledVector4(),
source.ToAssociatedScaledVector4(),
Numerics.Clamp(amount, 0, 1F));
return TPixel.FromAssociatedScaledVector4(result);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void ToBlendVector4<TPixelSource>( protected override void ToBlendVector4<TPixelSource>(
Configuration configuration, Configuration configuration,

87044
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

File diff suppressed because it is too large

858
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -4,39 +4,19 @@
#> #>
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #> <#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #> <#@ output extension=".cs" #>
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
// <auto-generated /> // <auto-generated />
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// <summary>
/// Collection of Porter Duff alpha blending functions applying different composition models.
/// </summary>
/// <remarks>
/// These functions are designed to be a general solution for all color cases,
/// that is, they take in account the alpha value of both the backdrop
/// and source, and there's no need to alpha-premultiply neither the backdrop
/// nor the source.
/// Note there are faster functions for when the backdrop color is known
/// to be opaque
/// </remarks>
internal static class DefaultPixelBlenders<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
<# <#
var composers = new []{ var composers = new []
{
"Src", "Src",
"SrcAtop", "SrcAtop",
"SrcOver", "SrcOver",
@ -51,7 +31,8 @@ var composers = new []{
"Xor", "Xor",
}; };
var blenders = new []{ var blenders = new []
{
"Normal", "Normal",
"Multiply", "Multiply",
"Add", "Add",
@ -60,796 +41,79 @@ var blenders = new []{
"Darken", "Darken",
"Lighten", "Lighten",
"Overlay", "Overlay",
"HardLight" "HardLight",
}; };
#>
foreach(var composer in composers) { /// <summary>
foreach(var blender in blenders) { /// Provides the straight-alpha equations consumed by the shared pixel-blending traversal.
/// </summary>
var blender_composer= $"{blender}{composer}"; internal static class DefaultPixelBlenderOperators
{
<#
foreach (var composer in composers)
{
foreach (var blender in blenders)
{
var blenderComposer = $"{blender}{composer}";
#> #>
/// <summary> /// <summary>
/// A pixel blender that implements the "<#= blender_composer#>" composition equation. /// Applies the "<#= blenderComposer #>" straight-alpha composition equation.
/// </summary> /// </summary>
public class <#= blender_composer#> : PixelBlender<TPixel> public readonly struct <#= blenderComposer #> : IPixelBlenderOperator
{ {
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>();
/// <inheritdoc /> /// <inheritdoc />
public override TPixel Blend(TPixel background, TPixel source, float amount) public static Vector4 Invoke(Vector4 background, Vector4 source, float amount)
{ => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount);
return TPixel.FromUnassociatedScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), Numerics.Clamp(amount, 0, 1)));
}
/// <inheritdoc /> /// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount) public static Vector256<float> Invoke(
{ Vector256<float> background,
amount = Numerics.Clamp(amount, 0, 1); Vector256<float> source,
Vector256<float> amount)
if (Avx512F.IsSupported && destination.Length >= 4) => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount);
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
Vector512<float> opacity = Vector512.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
Vector256<float> opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
}
/// <inheritdoc /> /// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount) public static Vector512<float> Invoke(
{ Vector512<float> background,
amount = Numerics.Clamp(amount, 0, 1); Vector512<float> source,
Vector512<float> amount)
if (Avx512F.IsSupported && destination.Length >= 4) => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount);
{ }
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> opacity = Vector512.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount)
{
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount, ReadOnlySpan<float> coverage)
{
amount = Numerics.Clamp(amount, 0, 1);
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> opacity = Vector512.Create(amount);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> opacity = Vector256.Create(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, float amount, ReadOnlySpan<float> coverage)
{
amount = Numerics.Clamp(amount, 0, 1);
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> opacity = Vector512.Create(amount);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> opacity = Vector256.Create(amount);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, amount);
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage)
{
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref Vector512<float> sourceBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref Vector256<float> sourceBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
/// <inheritdoc />
protected override void BlendWithCoverageFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount, ReadOnlySpan<float> coverage)
{
if (Avx512F.IsSupported && destination.Length >= 4)
{
// Divide by 4 as 4 elements per Vector4 and 16 per Vector512<float>
ref Vector512<float> destinationBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector512<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 4u);
ref Vector512<float> backgroundBase = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector512<float> sourceBase = Vector512.Create(
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W,
source.X, source.Y, source.Z, source.W);
Vector512<float> vOne = Vector512.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
float amount0 = amountBase;
float amount1 = Unsafe.Add(ref amountBase, 1);
float amount2 = Unsafe.Add(ref amountBase, 2);
float amount3 = Unsafe.Add(ref amountBase, 3);
// We need to create a Vector512<float> containing the current four amount values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> opacity = Vector512.Create(
amount0, amount0, amount0, amount0,
amount1, amount1, amount1, amount1,
amount2, amount2, amount2, amount2,
amount3, amount3, amount3, amount3);
opacity = Vector512.Min(Vector512.Max(Vector512<float>.Zero, opacity), vOne);
float coverage0 = coverageBase;
float coverage1 = Unsafe.Add(ref coverageBase, 1);
float coverage2 = Unsafe.Add(ref coverageBase, 2);
float coverage3 = Unsafe.Add(ref coverageBase, 3);
// We need to create a Vector512<float> containing the current four coverage values
// taking up each quarter of the Vector512<float> and then clamp them.
Vector512<float> coverageVector = Vector512.Create(
coverage0, coverage0, coverage0, coverage0,
coverage1, coverage1, coverage1, coverage1,
coverage2, coverage2, coverage2, coverage2,
coverage3, coverage3, coverage3, coverage3);
coverageVector = Vector512.Min(Vector512.Max(Vector512<float>.Zero, coverageVector), vOne);
Vector512<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity);
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 4);
coverageBase = ref Unsafe.Add(ref coverageBase, 4);
}
int remainder = Numerics.Modulo4(destination.Length);
if (remainder != 0)
{
for (int i = destination.Length - remainder; i < destination.Length; i++)
{
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F));
}
}
}
else if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> destinationBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(destination));
ref Vector256<float> destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256<float> backgroundBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(background));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
ref float coverageBase = ref MemoryMarshal.GetReference(coverage);
Vector256<float> sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
// We need to create a Vector256<float> containing the current and next amount values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> opacity = Vector256.Create(
Vector128.Create(amountBase),
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256<float>.Zero, opacity), vOne);
// We need to create a Vector256<float> containing the current and next coverage values
// taking up each half of the Vector256<float> and then clamp them.
Vector256<float> coverageVector = Vector256.Create(
Vector128.Create(coverageBase),
Vector128.Create(Unsafe.Add(ref coverageBase, 1)));
coverageVector = Avx.Min(Avx.Max(Vector256<float>.Zero, coverageVector), vOne);
Vector256<float> blended = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); <#
destinationBase = PorterDuffFunctions.BlendWithCoverage(backgroundBase, blended, coverageVector); }
destinationBase = ref Unsafe.Add(ref destinationBase, 1); }
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); #>
amountBase = ref Unsafe.Add(ref amountBase, 2); }
coverageBase = ref Unsafe.Add(ref coverageBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0) /// <summary>
{ /// Provides straight-alpha pixel blenders for each color and alpha composition pair.
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1. /// </summary>
int i = destination.Length - 1; /// <typeparam name="TPixel">The destination pixel format.</typeparam>
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); internal static class DefaultPixelBlenders<TPixel>
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); where TPixel : unmanaged, IPixel<TPixel>
} {
} <#
else foreach (var composer in composers)
{ {
for (int i = 0; i < destination.Length; i++) foreach (var blender in blenders)
{ {
Vector4 blended = PorterDuffFunctions.<#=blender_composer#>(background[i], source, Numerics.Clamp(amount[i], 0, 1F)); var blenderComposer = $"{blender}{composer}";
destination[i] = PorterDuffFunctions.BlendWithCoverage(background[i], blended, Numerics.Clamp(coverage[i], 0, 1F)); #>
} /// <summary>
} /// Blends pixels with the "<#= blenderComposer #>" composition equation.
} /// </summary>
public sealed class <#= blenderComposer #> :
DefaultPixelBlender<TPixel, DefaultPixelBlenderOperators.<#= blenderComposer #>>
{
/// <summary>
/// Gets the shared blender instance.
/// </summary>
public static <#= blenderComposer #> Instance { get; } = new();
} }
<# <#
} }
} }
#> #>
} }

46
src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs

@ -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);
}

706
src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs

@ -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);
}
}

327
tests/ImageSharp.Benchmarks/PixelBlenders/PixelBlenderTraversalAssembly.cs

@ -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);
}
}

104
tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs

@ -791,42 +791,104 @@ public class PixelBlenderTests
ExerciseBlender(blender, background, source); ExerciseBlender(blender, background, source);
} }
/// <summary>
/// Exercises every bulk overload across vector-width boundaries and compares it with single-pixel composition.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="blender">The blender under test.</param>
/// <param name="background">The background pixel.</param>
/// <param name="source">The source pixel.</param>
private static void ExerciseBlender<TPixel>(PixelBlender<TPixel> blender, TPixel background, TPixel source) private static void ExerciseBlender<TPixel>(PixelBlender<TPixel> blender, TPixel background, TPixel source)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
float[] amount = [1F, 1F, 1F, 1F]; const float amount = .625F;
float[] coverage = [1F, 1F, 1F, 1F];
TPixel expected = blender.Blend(background, source, 1F); float[] amounts = [0F, .125F, .375F, .625F, .875F, 1F, .5F];
float[] coverage = [1F, .8F, .6F, .4F, .2F, 0F, .5F];
TPixel[] destination = new TPixel[4]; // Seven pixels exercise one AVX-512 batch plus three tails, or three AVX2 batches plus one tail.
TPixel[] backgroundSpan = [background, background, background, background]; TPixel[] destination = new TPixel[7];
TPixel[] sourceSpan = [source, source, source, source]; TPixel[] expected = new TPixel[7];
TPixel[] backgroundSpan = [background, background, background, background, background, background, background];
TPixel[] sourceSpan = [source, source, source, source, source, source, source];
Vector4[] sourceSpanBuffer = new Vector4[destination.Length * 3]; Vector4[] sourceSpanBuffer = new Vector4[destination.Length * 3];
Vector4[] constantSourceBuffer = new Vector4[destination.Length * 2]; Vector4[] constantSourceBuffer = new Vector4[destination.Length * 2];
blender.Blend<TPixel>(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, sourceSpanBuffer); Array.Fill(expected, blender.Blend(background, source, amount));
Assert.All(destination, x => Assert.Equal(expected, x));
blender.Blend(Configuration.Default, destination, backgroundSpan, source, 1F, constantSourceBuffer);
Assert.All(destination, x => Assert.Equal(expected, x));
blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, sourceSpanBuffer); blender.Blend<TPixel>(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, sourceSpanBuffer);
Assert.All(destination, x => Assert.Equal(expected, x)); Assert.Equal(expected, destination);
blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer); blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer);
Assert.All(destination, x => Assert.Equal(expected, x)); Assert.Equal(expected, destination);
blender.BlendWithCoverage<TPixel>(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, coverage, sourceSpanBuffer); for (int i = 0; i < expected.Length; i++)
Assert.All(destination, x => Assert.Equal(expected, x)); {
expected[i] = blender.Blend(background, source, amounts[i]);
}
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, 1F, coverage, constantSourceBuffer); blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amounts, sourceSpanBuffer);
Assert.All(destination, x => Assert.Equal(expected, x)); Assert.Equal(expected, destination);
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverage, sourceSpanBuffer); blender.Blend(Configuration.Default, destination, backgroundSpan, source, amounts, constantSourceBuffer);
Assert.All(destination, x => Assert.Equal(expected, x)); Assert.Equal(expected, destination);
for (int i = 0; i < expected.Length; i++)
{
expected[i] = BlendWithCoverageScalar(blender, background, source, amount, coverage[i]);
}
blender.BlendWithCoverage<TPixel>(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverage, sourceSpanBuffer);
Assert.Equal(expected, destination);
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amount, coverage, constantSourceBuffer); blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amount, coverage, constantSourceBuffer);
Assert.All(destination, x => Assert.Equal(expected, x)); Assert.Equal(expected, destination);
for (int i = 0; i < expected.Length; i++)
{
expected[i] = BlendWithCoverageScalar(blender, background, source, amounts[i], coverage[i]);
}
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amounts, coverage, sourceSpanBuffer);
Assert.Equal(expected, destination);
blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amounts, coverage, constantSourceBuffer);
Assert.Equal(expected, destination);
}
/// <summary>
/// Computes a one-pixel coverage result through the scalar remainder path.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="blender">The blender under test.</param>
/// <param name="background">The background pixel.</param>
/// <param name="source">The source pixel.</param>
/// <param name="amount">The source opacity.</param>
/// <param name="coverage">The pixel coverage.</param>
/// <returns>The blended pixel.</returns>
private static TPixel BlendWithCoverageScalar<TPixel>(
PixelBlender<TPixel> blender,
TPixel background,
TPixel source,
float amount,
float coverage)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> destination = stackalloc TPixel[1];
Span<TPixel> backgroundSpan = stackalloc TPixel[1] { background };
Span<TPixel> sourceSpan = stackalloc TPixel[1] { source };
Span<float> coverageSpan = stackalloc float[1] { coverage };
Span<Vector4> buffer = stackalloc Vector4[3];
blender.BlendWithCoverage<TPixel>(
Configuration.Default,
destination,
backgroundSpan,
sourceSpan,
amount,
coverageSpan,
buffer);
return destination[0];
} }
} }

Loading…
Cancel
Save