Browse Source

Merge pull request #2359 from SixLabors/js/avx2-porter-duff

Enable Avx2 optimizations on Porter-Duff operations.
pull/2367/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
ae0a51cb99
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp/Common/Constants.cs
  2. 40
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs
  4. 8112
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  5. 79
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  6. 1602
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs
  7. 174
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt
  8. 366
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  9. 24
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
  10. 68
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs
  11. 73
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
  12. 135
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs
  13. 17
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  14. 46
      tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

2
src/ImageSharp/Common/Constants.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp;

40
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -532,7 +532,8 @@ internal static partial class SimdUtils
}
/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector256{T}"/>.
/// Performs a multiplication and an addition of the <see cref="Vector256{Single}"/>.
/// TODO: Fix. The arguments are in a different order to the FMA intrinsic.
/// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param>
@ -549,22 +550,21 @@ internal static partial class SimdUtils
{
return Fma.MultiplyAdd(vm1, vm0, va);
}
else
{
return Avx.Add(Avx.Multiply(vm0, vm1), va);
}
return Avx.Add(Avx.Multiply(vm0, vm1), va);
}
/// <summary>
/// Performs a multiplication and a substraction of the <see cref="Vector256{T}"/>.
/// Performs a multiplication and a subtraction of the <see cref="Vector256{Single}"/>.
/// TODO: Fix. The arguments are in a different order to the FMA intrinsic.
/// </summary>
/// <remarks>ret = (vm0 * vm1) - vs</remarks>
/// <param name="vs">The vector to substract from the intermediate result.</param>
/// <param name="vs">The vector to subtract from the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector256<float> MultiplySubstract(
public static Vector256<float> MultiplySubtract(
in Vector256<float> vs,
in Vector256<float> vm0,
in Vector256<float> vm1)
@ -573,10 +573,30 @@ internal static partial class SimdUtils
{
return Fma.MultiplySubtract(vm1, vm0, vs);
}
else
return Avx.Subtract(Avx.Multiply(vm0, vm1), vs);
}
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector256{Single}"/>.
/// </summary>
/// <remarks>ret = c - (a * b)</remarks>
/// <param name="a">The first vector to multiply.</param>
/// <param name="b">The second vector to multiply.</param>
/// <param name="c">The vector to add negated to the intermediate result.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector256<float> MultiplyAddNegated(
in Vector256<float> a,
in Vector256<float> b,
in Vector256<float> c)
{
if (Fma.IsSupported)
{
return Avx.Subtract(Avx.Multiply(vm0, vm1), vs);
return Fma.MultiplyAddNegated(a, b, c);
}
return Avx.Subtract(c, Avx.Multiply(a, b));
}
/// <summary>

2
src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs

@ -99,7 +99,7 @@ internal static partial class FloatingPointDCT
var mm256_F_1_4142 = Vector256.Create(1.414213562f);
Vector256<float> tmp13 = Avx.Add(tmp1, tmp3);
Vector256<float> tmp12 = SimdUtils.HwIntrinsics.MultiplySubstract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142);
Vector256<float> tmp12 = SimdUtils.HwIntrinsics.MultiplySubtract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142);
tmp0 = Avx.Add(tmp10, tmp13);
tmp3 = Avx.Subtract(tmp10, tmp13);

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

File diff suppressed because it is too large

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

@ -13,6 +13,10 @@
// <auto-generated />
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;
@ -86,18 +90,85 @@ var blenders = new []{
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)
{
amount = Numerics.Clamp(amount, 0, 1);
for (int i = 0; i < destination.Length; i++)
if (Avx2.IsSupported && destination.Length >= 2)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
// 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, (IntPtr)((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 />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
for (int i = 0; i < destination.Length; i++)
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, (IntPtr)((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
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1));
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
}

1602
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs

File diff suppressed because it is too large

174
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt

@ -15,6 +15,8 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -31,11 +33,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return source;
}
/// <summary>
/// Returns the result of the "<#=blender#>Src compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Src(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
/// <summary>
/// Returns the result of the "<#=blender#>SrcAtop" compositing equation.
/// </summary>
@ -46,7 +59,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Atop(backdrop, source, <#=blender#>(backdrop, source));
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcAtop" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcAtop(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
return Atop(backdrop, source, <#=blender#>(backdrop, source));
}
@ -61,7 +89,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Over(backdrop, source, <#=blender#>(backdrop, source));
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcOver" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcOver(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
return Over(backdrop, source, <#=blender#>(backdrop, source));
}
@ -76,11 +119,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return In(backdrop, source);
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcIn" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcIn(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>SrcOut" compositing equation.
/// </summary>
@ -91,11 +145,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Out(backdrop, source);
}
/// <summary>
/// Returns the result of the "<#=blender#>SrcOut" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>SrcOut(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>Dest" compositing equation.
/// </summary>
@ -109,6 +174,19 @@ internal static partial class PorterDuffFunctions
return backdrop;
}
/// <summary>
/// Returns the result of the "<#=blender#>Dest" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Dest(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
return backdrop;
}
/// <summary>
/// Returns the result of the "<#=blender#>DestAtop" compositing equation.
/// </summary>
@ -119,7 +197,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Atop(source, backdrop, <#=blender#>(source, backdrop));
}
/// <summary>
/// Returns the result of the "<#=blender#>DestAtop" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestAtop(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
return Atop(source, backdrop, <#=blender#>(source, backdrop));
}
@ -134,7 +227,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Over(source, backdrop, <#=blender#>(source, backdrop));
}
/// <summary>
/// Returns the result of the "<#=blender#>DestOver" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestOver(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
{
source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl);
return Over(source, backdrop, <#=blender#>(source, backdrop));
}
@ -149,11 +257,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return In(source, backdrop);
}
/// <summary>
/// Returns the result of the "<#=blender#>DestIn" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestIn(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop);
/// <summary>
/// Returns the result of the "<#=blender#>DestOut" compositing equation.
/// </summary>
@ -164,11 +283,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Out(source, backdrop);
}
/// <summary>
/// Returns the result of the "<#=blender#>DestOut" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>DestOut(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop);
/// <summary>
/// Returns the result of the "<#=blender#>Xor" compositing equation.
/// </summary>
@ -179,11 +309,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Xor(backdrop, source);
}
/// <summary>
/// Returns the result of the "<#=blender#>Xor" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Xor(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl));
/// <summary>
/// Returns the result of the "<#=blender#>Clear" compositing equation.
/// </summary>
@ -194,11 +335,22 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
source = WithW(source, source * opacity);
return Clear(backdrop, source);
}
/// <summary>
/// Returns the result of the "<#=blender#>Clear" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> <#=blender#>Clear(Vector256<float> backdrop, Vector256<float> source, Vector256<float> opacity)
=> Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl));
<#} #>
<# void GenerateGenericPixelBlender(string blender, string composer) { #>

366
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -3,6 +3,8 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -19,6 +21,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
/// </remarks>
internal static partial class PorterDuffFunctions
{
private const int BlendAlphaControl = 0b_10_00_10_00;
private const int ShuffleAlphaControl = 0b_11_11_11_11;
/// <summary>
/// Returns the result of the "Normal" compositing equation.
/// </summary>
@ -27,9 +32,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Normal(Vector4 backdrop, Vector4 source)
{
return source;
}
=> source;
/// <summary>
/// Returns the result of the "Normal" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Normal(Vector256<float> backdrop, Vector256<float> source)
=> source;
/// <summary>
/// Returns the result of the "Multiply" compositing equation.
@ -39,9 +52,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Multiply(Vector4 backdrop, Vector4 source)
{
return backdrop * source;
}
=> backdrop * source;
/// <summary>
/// Returns the result of the "Multiply" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Multiply(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Multiply(backdrop, source);
/// <summary>
/// Returns the result of the "Add" compositing equation.
@ -51,9 +72,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Add(Vector4 backdrop, Vector4 source)
{
return Vector4.Min(Vector4.One, backdrop + source);
}
=> Vector4.Min(Vector4.One, backdrop + source);
/// <summary>
/// Returns the result of the "Add" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Add(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Min(Vector256.Create(1F), Avx.Add(backdrop, source));
/// <summary>
/// Returns the result of the "Subtract" compositing equation.
@ -63,9 +92,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Subtract(Vector4 backdrop, Vector4 source)
{
return Vector4.Max(Vector4.Zero, backdrop - source);
}
=> Vector4.Max(Vector4.Zero, backdrop - source);
/// <summary>
/// Returns the result of the "Subtract" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Subtract(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Max(Vector256<float>.Zero, Avx.Subtract(backdrop, source));
/// <summary>
/// Returns the result of the "Screen" compositing equation.
@ -75,8 +112,19 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Screen(Vector4 backdrop, Vector4 source)
=> Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source));
/// <summary>
/// Returns the result of the "Screen" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Screen(Vector256<float> backdrop, Vector256<float> source)
{
return Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source));
Vector256<float> vOne = Vector256.Create(1F);
return SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Subtract(vOne, backdrop), Avx.Subtract(vOne, source), vOne);
}
/// <summary>
@ -87,9 +135,17 @@ internal static partial class PorterDuffFunctions
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Darken(Vector4 backdrop, Vector4 source)
{
return Vector4.Min(backdrop, source);
}
=> Vector4.Min(backdrop, source);
/// <summary>
/// Returns the result of the "Darken" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Darken(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Min(backdrop, source);
/// <summary>
/// Returns the result of the "Lighten" compositing equation.
@ -98,10 +154,17 @@ internal static partial class PorterDuffFunctions
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Lighten(Vector4 backdrop, Vector4 source)
{
return Vector4.Max(backdrop, source);
}
public static Vector4 Lighten(Vector4 backdrop, Vector4 source) => Vector4.Max(backdrop, source);
/// <summary>
/// Returns the result of the "Lighten" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Lighten(Vector256<float> backdrop, Vector256<float> source)
=> Avx.Max(backdrop, source);
/// <summary>
/// Returns the result of the "Overlay" compositing equation.
@ -119,6 +182,19 @@ internal static partial class PorterDuffFunctions
return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0));
}
/// <summary>
/// Returns the result of the "Overlay" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Overlay(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> color = OverlayValueFunction(backdrop, source);
return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl));
}
/// <summary>
/// Returns the result of the "HardLight" compositing equation.
/// </summary>
@ -136,15 +212,44 @@ internal static partial class PorterDuffFunctions
}
/// <summary>
/// Helper function for Overlay andHardLight modes
/// Returns the result of the "HardLight" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> HardLight(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> color = OverlayValueFunction(source, backdrop);
return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl));
}
/// <summary>
/// Helper function for Overlay and HardLight modes
/// </summary>
/// <param name="backdrop">Backdrop color element</param>
/// <param name="source">Source color element</param>
/// <returns>Overlay value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float OverlayValueFunction(float backdrop, float source)
=> backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop));
/// <summary>
/// Helper function for Overlay and HardLight modes
/// </summary>
/// <param name="backdrop">Backdrop color element</param>
/// <param name="source">Source color element</param>
/// <returns>Overlay value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> OverlayValueFunction(Vector256<float> backdrop, Vector256<float> source)
{
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop));
Vector256<float> vOne = Vector256.Create(1F);
Vector256<float> left = Avx.Multiply(Avx.Add(backdrop, backdrop), source);
Vector256<float> vOneMinusSource = Avx.Subtract(vOne, source);
Vector256<float> right = SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Add(vOneMinusSource, vOneMinusSource), Avx.Subtract(vOne, backdrop), vOne);
Vector256<float> cmp = Avx.CompareGreaterThan(backdrop, Vector256.Create(.5F));
return Avx.BlendVariable(left, right, cmp);
}
/// <summary>
@ -158,21 +263,53 @@ internal static partial class PorterDuffFunctions
public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 blend)
{
// calculate weights
float blendW = destination.W * source.W;
float dstW = destination.W - blendW;
float srcW = source.W - blendW;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 blendW = sW * dW;
Vector4 dstW = dW - blendW;
Vector4 srcW = sW - blendW;
// calculate final alpha
float alpha = dstW + source.W;
Vector4 alpha = dstW + sW;
// calculate final color
Vector4 color = (destination * dstW) + (source * srcW) + (blend * blendW);
// unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon);
color.W = alpha;
color /= Vector4.Max(alpha, new(Constants.Epsilon));
return WithW(color, alpha);
}
/// <summary>
/// Returns the result of the "Over" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="blend">The amount to blend. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Over(Vector256<float> destination, Vector256<float> source, Vector256<float> blend)
{
// calculate weights
Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> dW = Avx.Permute(destination, ShuffleAlphaControl);
Vector256<float> blendW = Avx.Multiply(sW, dW);
Vector256<float> dstW = Avx.Subtract(dW, blendW);
Vector256<float> srcW = Avx.Subtract(sW, blendW);
// calculate final alpha
Vector256<float> alpha = Avx.Add(dstW, sW);
// calculate final color
Vector256<float> color = Avx.Multiply(destination, dstW);
color = SimdUtils.HwIntrinsics.MultiplyAdd(color, source, srcW);
color = SimdUtils.HwIntrinsics.MultiplyAdd(color, blend, blendW);
return color;
// unpremultiply
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
/// <summary>
@ -186,20 +323,47 @@ internal static partial class PorterDuffFunctions
public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 blend)
{
// calculate weights
float blendW = destination.W * source.W;
float dstW = destination.W - blendW;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 blendW = sW * dW;
Vector4 dstW = dW - blendW;
// calculate final alpha
float alpha = destination.W;
Vector4 alpha = dW;
// calculate final color
Vector4 color = (destination * dstW) + (blend * blendW);
// unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon);
color.W = alpha;
color /= Vector4.Max(alpha, new(Constants.Epsilon));
return WithW(color, alpha);
}
return color;
/// <summary>
/// Returns the result of the "Atop" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="blend">The amount to blend. Range 0..1</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Atop(Vector256<float> destination, Vector256<float> source, Vector256<float> blend)
{
// calculate final alpha
Vector256<float> alpha = Avx.Permute(destination, ShuffleAlphaControl);
// calculate weights
Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> blendW = Avx.Multiply(sW, alpha);
Vector256<float> dstW = Avx.Subtract(alpha, blendW);
// calculate final color
Vector256<float> color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW);
// unpremultiply
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
/// <summary>
@ -211,13 +375,33 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 In(Vector4 destination, Vector4 source)
{
float alpha = destination.W * source.W;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 alpha = dW * sW;
Vector4 color = source * alpha; // premultiply
color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply
color.W = alpha;
color /= Vector4.Max(alpha, new(Constants.Epsilon)); // unpremultiply
return WithW(color, alpha);
}
return color;
/// <summary>
/// Returns the result of the "In" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> In(Vector256<float> destination, Vector256<float> source)
{
// calculate alpha
Vector256<float> alpha = Avx.Permute(Avx.Multiply(source, destination), ShuffleAlphaControl);
// premultiply
Vector256<float> color = Avx.Multiply(source, alpha);
// unpremultiply
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
/// <summary>
@ -229,13 +413,33 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Out(Vector4 destination, Vector4 source)
{
float alpha = (1 - destination.W) * source.W;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 alpha = (Vector4.One - dW) * sW;
Vector4 color = source * alpha; // premultiply
color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply
color.W = alpha;
color /= Vector4.Max(alpha, new(Constants.Epsilon)); // unpremultiply
return WithW(color, alpha);
}
return color;
/// <summary>
/// Returns the result of the "Out" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Out(Vector256<float> destination, Vector256<float> source)
{
// calculate alpha
Vector256<float> alpha = Avx.Permute(Avx.Multiply(source, Avx.Subtract(Vector256.Create(1F), destination)), ShuffleAlphaControl);
// premultiply
Vector256<float> color = Avx.Multiply(source, alpha);
// unpremultiply
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
/// <summary>
@ -247,22 +451,80 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Xor(Vector4 destination, Vector4 source)
{
float srcW = 1 - destination.W;
float dstW = 1 - source.W;
Vector4 sW = PermuteW(source);
Vector4 dW = PermuteW(destination);
Vector4 srcW = Vector4.One - dW;
Vector4 dstW = Vector4.One - sW;
Vector4 alpha = (sW * srcW) + (dW * dstW);
Vector4 color = (sW * source * srcW) + (dW * destination * dstW);
// unpremultiply
color /= Vector4.Max(alpha, new(Constants.Epsilon));
return WithW(color, alpha);
}
/// <summary>
/// Returns the result of the "XOr" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Xor(Vector256<float> destination, Vector256<float> source)
{
// calculate weights
Vector256<float> sW = Avx.Shuffle(source, source, ShuffleAlphaControl);
Vector256<float> dW = Avx.Shuffle(destination, destination, ShuffleAlphaControl);
Vector256<float> vOne = Vector256.Create(1F);
Vector256<float> srcW = Avx.Subtract(vOne, dW);
Vector256<float> dstW = Avx.Subtract(vOne, sW);
float alpha = (source.W * srcW) + (destination.W * dstW);
Vector4 color = (source.W * source * srcW) + (destination.W * destination * dstW);
// calculate alpha
Vector256<float> alpha = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW);
Vector256<float> color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW);
// unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon);
color.W = alpha;
color = Avx.Divide(color, Avx.Max(alpha, Vector256.Create(Constants.Epsilon)));
return Avx.Blend(color, alpha, BlendAlphaControl);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Clear(Vector4 backdrop, Vector4 source) => Vector4.Zero;
return color;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> Clear(Vector256<float> backdrop, Vector256<float> source) => Vector256<float>.Zero;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 WithW(Vector4 value, Vector4 w)
{
if (Sse41.IsSupported)
{
return Sse41.Insert(value.AsVector128(), w.AsVector128(), 0b11_11_0000).AsVector4();
}
if (Sse.IsSupported)
{
// Create tmp as <w[3], w[0], value[2], value[0]>
// Then return <value[0], value[1], tmp[2], tmp[0]> (which is <value[0], value[1], value[2], w[3]>)
Vector128<float> tmp = Sse.Shuffle(w.AsVector128(), value.AsVector128(), 0b00_10_00_11);
return Sse.Shuffle(value.AsVector128(), tmp, 0b00_10_01_00).AsVector4();
}
value.W = w.W;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Clear(Vector4 backdrop, Vector4 source)
private static Vector4 PermuteW(Vector4 value)
{
return Vector4.Zero;
if (Sse.IsSupported)
{
return Sse.Shuffle(value.AsVector128(), value.AsVector128(), 0b11111111).AsVector4();
}
return new(value.W);
}
}

24
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs

@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Benchmarks;
public class PorterDuffBulkVsPixel
{
private Configuration Configuration => Configuration.Default;
private static Configuration Configuration => Configuration.Default;
private void BulkVectorConvert<TPixel>(
private static void BulkVectorConvert<TPixel>(
Span<TPixel> destination,
Span<TPixel> background,
Span<TPixel> source,
@ -31,18 +31,18 @@ public class PorterDuffBulkVsPixel
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, background, backgroundSpan);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, source, sourceSpan);
PixelOperations<TPixel>.Instance.ToVector4(Configuration, background, backgroundSpan);
PixelOperations<TPixel>.Instance.ToVector4(Configuration, source, sourceSpan);
for (int i = 0; i < destination.Length; i++)
{
destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination);
PixelOperations<TPixel>.Instance.FromVector4Destructive(Configuration, destinationSpan, destination);
}
private void BulkPixelConvert<TPixel>(
private static void BulkPixelConvert<TPixel>(
Span<TPixel> destination,
Span<TPixel> background,
Span<TPixel> source,
@ -60,9 +60,9 @@ public class PorterDuffBulkVsPixel
}
[Benchmark(Description = "ImageSharp BulkVectorConvert")]
public Size BulkVectorConvert()
public static Size BulkVectorConvert()
{
using var image = new Image<Rgba32>(800, 800);
using Image<Rgba32> image = new(800, 800);
using IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width);
amounts.GetSpan().Fill(1);
@ -70,23 +70,23 @@ public class PorterDuffBulkVsPixel
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.DangerousGetRowSpan(y);
this.BulkVectorConvert(span, span, span, amounts.GetSpan());
BulkVectorConvert(span, span, span, amounts.GetSpan());
}
return new Size(image.Width, image.Height);
}
[Benchmark(Description = "ImageSharp BulkPixelConvert")]
public Size BulkPixelConvert()
public static Size BulkPixelConvert()
{
using var image = new Image<Rgba32>(800, 800);
using Image<Rgba32> image = new(800, 800);
using IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width);
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.DangerousGetRowSpan(y);
this.BulkPixelConvert(span, span, span, amounts.GetSpan());
BulkPixelConvert(span, span, span, amounts.GetSpan());
}
return new Size(image.Width, image.Height);

68
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs

@ -0,0 +1,68 @@
// 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 BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders;
public class PorterDuffBulkVsSingleVector
{
private Vector4[] backdrop;
private Vector4[] source;
[GlobalSetup]
public void Setup()
{
this.backdrop = new Vector4[8 * 20];
this.source = new Vector4[8 * 20];
FillRandom(this.backdrop);
FillRandom(this.source);
}
private static void FillRandom(Vector4[] arr)
{
Random rng = new();
for (int i = 0; i < arr.Length; i++)
{
arr[i].X = rng.NextSingle();
arr[i].Y = rng.NextSingle();
arr[i].Z = rng.NextSingle();
arr[i].W = rng.NextSingle();
}
}
[Benchmark(Description = "Scalar", Baseline = true)]
public Vector4 OverlayValueFunction_Scalar()
{
Vector4 result = default;
for (int i = 0; i < this.backdrop.Length; i++)
{
result = PorterDuffFunctions.NormalSrcOver(this.backdrop[i], this.source[i], .5F);
}
return result;
}
[Benchmark(Description = "Avx")]
public Vector256<float> OverlayValueFunction_Avx()
{
ref Vector256<float> backdrop = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference<Vector4>(this.backdrop));
ref Vector256<float> source = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference<Vector4>(this.source));
Vector256<float> result = default;
Vector256<float> opacity = Vector256.Create(.5F);
int count = this.backdrop.Length / 2;
for (int i = 0; i < count; i++)
{
result = PorterDuffFunctions.NormalSrcOver(Unsafe.Add(ref backdrop, i), Unsafe.Add(ref source, i), opacity);
}
return result;
}
}

73
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs

@ -1,59 +1,66 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders;
public class PorterDuffCompositorTests
{
// TODO: Add other modes to compare.
public static readonly TheoryData<PixelAlphaCompositionMode> CompositingOperators =
new TheoryData<PixelAlphaCompositionMode>
{
PixelAlphaCompositionMode.Src,
PixelAlphaCompositionMode.SrcAtop,
PixelAlphaCompositionMode.SrcOver,
PixelAlphaCompositionMode.SrcIn,
PixelAlphaCompositionMode.SrcOut,
PixelAlphaCompositionMode.Dest,
PixelAlphaCompositionMode.DestAtop,
PixelAlphaCompositionMode.DestOver,
PixelAlphaCompositionMode.DestIn,
PixelAlphaCompositionMode.DestOut,
PixelAlphaCompositionMode.Clear,
PixelAlphaCompositionMode.Xor
};
new()
{
PixelAlphaCompositionMode.Src,
PixelAlphaCompositionMode.SrcAtop,
PixelAlphaCompositionMode.SrcOver,
PixelAlphaCompositionMode.SrcIn,
PixelAlphaCompositionMode.SrcOut,
PixelAlphaCompositionMode.Dest,
PixelAlphaCompositionMode.DestAtop,
PixelAlphaCompositionMode.DestOver,
PixelAlphaCompositionMode.DestIn,
PixelAlphaCompositionMode.DestOut,
PixelAlphaCompositionMode.Clear,
PixelAlphaCompositionMode.Xor
};
[Theory]
[WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)]
public void PorterDuffOutputIsCorrect(TestImageProvider<Rgba32> provider, PixelAlphaCompositionMode mode)
{
var srcFile = TestFile.Create(TestImages.Png.PDSrc);
using (Image<Rgba32> src = srcFile.CreateRgba32Image())
using (Image<Rgba32> dest = provider.GetImage())
static void RunTest(string providerDump, string alphaMode)
{
var options = new GraphicsOptions
TestImageProvider<Rgba32> provider
= BasicSerializer.Deserialize<TestImageProvider<Rgba32>>(providerDump);
TestFile srcFile = TestFile.Create(TestImages.Png.PDSrc);
using Image<Rgba32> src = srcFile.CreateRgba32Image();
using Image<Rgba32> dest = provider.GetImage();
GraphicsOptions options = new()
{
Antialias = false,
AlphaCompositionMode = mode
AlphaCompositionMode = Enum.Parse<PixelAlphaCompositionMode>(alphaMode)
};
using (Image<Rgba32> res = dest.Clone(x => x.DrawImage(src, options)))
{
string combinedMode = mode.ToString();
if (combinedMode != "Src" && combinedMode.StartsWith("Src"))
{
combinedMode = combinedMode.Substring(3);
}
using Image<Rgba32> res = dest.Clone(x => x.DrawImage(src, options));
string combinedMode = alphaMode;
res.DebugSave(provider, combinedMode);
res.CompareToReferenceOutput(provider, combinedMode);
if (combinedMode != "Src" && combinedMode.StartsWith("Src", StringComparison.OrdinalIgnoreCase))
{
combinedMode = combinedMode[3..];
}
res.DebugSave(provider, combinedMode);
res.CompareToReferenceOutput(provider, combinedMode);
}
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX,
provider,
mode.ToString());
}
}

135
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.ImageSharp.Tests.TestUtilities;
@ -9,7 +10,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders;
public class PorterDuffFunctionsTests
{
public static TheoryData<TestVector4, TestVector4, float, TestVector4> NormalBlendFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
private static readonly ApproximateFloatComparer FloatComparer = new(.000001F);
public static TheoryData<TestVector4, TestVector4, float, TestVector4> NormalBlendFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }
@ -23,7 +26,19 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected, actual);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> MultiplyFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(NormalBlendFunctionData))]
public void NormalBlendFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.NormalSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> MultiplyFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) },
@ -38,22 +53,46 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> AddFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(MultiplyFunctionData))]
public void MultiplyFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.MultiplySrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> AddFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) },
{ new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2075676f, .2075676f, .2075676f, .37f) }
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(0.24324325f, 0.24324325f, 0.24324325f, .37f) }
};
[Theory]
[MemberData(nameof(AddFunctionData))]
public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount);
Vector4 actual = PorterDuffFunctions.AddSrcOver((Vector4)back, source, amount);
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> SubtractFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(AddFunctionData))]
public void AddFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.AddSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> SubtractFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) },
@ -68,7 +107,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> ScreenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(SubtractFunctionData))]
public void SubtractFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.SubtractSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> ScreenFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) },
@ -83,7 +134,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> DarkenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(ScreenFunctionData))]
public void ScreenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.ScreenSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> DarkenFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) },
@ -98,7 +161,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> LightenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(DarkenFunctionData))]
public void DarkenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.DarkenSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> LightenFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) },
@ -113,7 +188,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> OverlayFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(LightenFunctionData))]
public void LightenFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.LightenSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> OverlayFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) },
@ -128,7 +215,19 @@ public class PorterDuffFunctionsTests
VectorAssert.Equal(expected, actual, 5);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> HardLightFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>
[Theory]
[MemberData(nameof(OverlayFunctionData))]
public void OverlayFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.OverlaySrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
public static TheoryData<TestVector4, TestVector4, float, TestVector4> HardLightFunctionData { get; } = new()
{
{ new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) },
{ new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1f) },
@ -142,4 +241,16 @@ public class PorterDuffFunctionsTests
Vector4 actual = PorterDuffFunctions.HardLightSrcOver((Vector4)back, source, amount);
VectorAssert.Equal(expected, actual, 5);
}
[Theory]
[MemberData(nameof(HardLightFunctionData))]
public void HardLightFunction256(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
Vector256<float> back256 = Vector256.Create(back.X, back.Y, back.Z, back.W, back.X, back.Y, back.Z, back.W);
Vector256<float> source256 = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256<float> expected256 = Vector256.Create(expected.X, expected.Y, expected.Z, expected.W, expected.X, expected.Y, expected.Z, expected.W);
Vector256<float> actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
}

17
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests;
@ -14,7 +16,8 @@ internal readonly struct ApproximateFloatComparer :
IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>
IEqualityComparer<ColorMatrix>,
IEqualityComparer<Vector256<float>>
{
private readonly float epsilon;
@ -72,4 +75,16 @@ internal readonly struct ApproximateFloatComparer :
/// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();
public bool Equals(Vector256<float> x, Vector256<float> y)
=> this.Equals(x.GetElement(0), y.GetElement(0))
&& this.Equals(x.GetElement(1), y.GetElement(1))
&& this.Equals(x.GetElement(2), y.GetElement(2))
&& this.Equals(x.GetElement(3), y.GetElement(3))
&& this.Equals(x.GetElement(4), y.GetElement(4))
&& this.Equals(x.GetElement(5), y.GetElement(5))
&& this.Equals(x.GetElement(6), y.GetElement(6))
&& this.Equals(x.GetElement(7), y.GetElement(7));
public int GetHashCode([DisallowNull] Vector256<float> obj) => obj.GetHashCode();
}

46
tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

@ -257,6 +257,52 @@ public static class FeatureTestRunner
}
}
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
/// where the given <paramref name="intrinsics"/> features.
/// </summary>
/// <param name="action">The test action to run.</param>
/// <param name="intrinsics">The intrinsics features.</param>
/// <param name="arg1">The value to pass as a parameter to the test action.</param>
/// <param name="arg2">The second value to pass as a parameter to the test action.</param>
public static void RunWithHwIntrinsicsFeature<T>(
Action<string, string> action,
HwIntrinsics intrinsics,
T arg1,
string arg2)
where T : IXunitSerializable
{
if (!RemoteExecutor.IsSupported)
{
return;
}
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
RemoteExecutor.Invoke(
action,
BasicSerializer.Serialize(arg1),
arg2,
new RemoteInvokeOptions
{
StartInfo = processStartInfo
})
.Dispose();
}
else
{
// Since we are running using the default architecture there is no
// point creating the overhead of running the action in a separate process.
action(BasicSerializer.Serialize(arg1), arg2);
}
}
}
/// <summary>
/// Runs the given test <paramref name="action"/> within an environment
/// where the given <paramref name="intrinsics"/> features.

Loading…
Cancel
Save