Browse Source

Merge branch 'main' into js/accumulative-memory-limit

pull/3056/head
James Jackson-South 1 month ago
committed by GitHub
parent
commit
4eb19aa7f4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      src/ImageSharp/Advanced/ParallelExecutionSettings.cs
  2. 86
      src/ImageSharp/Advanced/ParallelRowIterator.cs
  3. 16
      src/ImageSharp/Common/Helpers/Numerics.cs
  4. 45
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  5. 22
      src/ImageSharp/Common/Helpers/Vector256Utilities.cs
  6. 15
      src/ImageSharp/Common/Helpers/Vector512Utilities.cs
  7. 1
      src/ImageSharp/Configuration.cs
  8. 2
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  9. 32
      src/ImageSharp/ImageInfo.cs
  10. 17056
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  11. 156
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  12. 1568
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs
  13. 172
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt
  14. 299
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  15. 23
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs
  16. 115
      tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
  17. 30
      tests/ImageSharp.Tests/ImageInfoTests.cs
  18. 2
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
  19. 152
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs
  20. 108
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs
  21. 18
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  22. 2
      tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

11
src/ImageSharp/Advanced/ParallelExecutionSettings.cs

@ -18,7 +18,10 @@ public readonly struct ParallelExecutionSettings
/// <summary>
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
/// </summary>
/// <param name="maxDegreeOfParallelism">The value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.</param>
/// <param name="maxDegreeOfParallelism">
/// The value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.
/// Set to <c>-1</c> to leave the degree of parallelism unbounded.
/// </param>
/// <param name="minimumPixelsProcessedPerTask">The value for <see cref="MinimumPixelsProcessedPerTask"/>.</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
public ParallelExecutionSettings(
@ -44,7 +47,10 @@ public readonly struct ParallelExecutionSettings
/// <summary>
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
/// </summary>
/// <param name="maxDegreeOfParallelism">The value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.</param>
/// <param name="maxDegreeOfParallelism">
/// The value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.
/// Set to <c>-1</c> to leave the degree of parallelism unbounded.
/// </param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator)
: this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator)
@ -58,6 +64,7 @@ public readonly struct ParallelExecutionSettings
/// <summary>
/// Gets the value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.
/// A value of <c>-1</c> leaves the degree of parallelism unbounded.
/// </summary>
public int MaxDegreeOfParallelism { get; }

86
src/ImageSharp/Advanced/ParallelRowIterator.cs

@ -44,14 +44,14 @@ public static partial class ParallelRowIterator
where T : struct, IRowOperation
{
ValidateRectangle(rectangle);
ValidateSettings(parallelSettings);
int top = rectangle.Top;
int bottom = rectangle.Bottom;
int width = rectangle.Width;
int height = rectangle.Height;
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
int numOfSteps = GetNumberOfSteps(width, height, parallelSettings);
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
@ -65,7 +65,7 @@ public static partial class ParallelRowIterator
}
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps);
RowOperationWrapper<T> wrappingOperation = new(top, bottom, verticalStep, in operation);
_ = Parallel.For(
@ -109,14 +109,14 @@ public static partial class ParallelRowIterator
where TBuffer : unmanaged
{
ValidateRectangle(rectangle);
ValidateSettings(parallelSettings);
int top = rectangle.Top;
int bottom = rectangle.Bottom;
int width = rectangle.Width;
int height = rectangle.Height;
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
int numOfSteps = GetNumberOfSteps(width, height, parallelSettings);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle);
@ -135,7 +135,7 @@ public static partial class ParallelRowIterator
}
int verticalStep = DivideCeil(height, numOfSteps);
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps);
RowOperationWrapper<T, TBuffer> wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation);
_ = Parallel.For(
@ -174,14 +174,14 @@ public static partial class ParallelRowIterator
where T : struct, IRowIntervalOperation
{
ValidateRectangle(rectangle);
ValidateSettings(parallelSettings);
int top = rectangle.Top;
int bottom = rectangle.Bottom;
int width = rectangle.Width;
int height = rectangle.Height;
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
int numOfSteps = GetNumberOfSteps(width, height, parallelSettings);
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
@ -192,7 +192,7 @@ public static partial class ParallelRowIterator
}
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps);
RowIntervalOperationWrapper<T> wrappingOperation = new(top, bottom, verticalStep, in operation);
_ = Parallel.For(
@ -236,14 +236,14 @@ public static partial class ParallelRowIterator
where TBuffer : unmanaged
{
ValidateRectangle(rectangle);
ValidateSettings(parallelSettings);
int top = rectangle.Top;
int bottom = rectangle.Bottom;
int width = rectangle.Width;
int height = rectangle.Height;
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
int numOfSteps = GetNumberOfSteps(width, height, parallelSettings);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle);
@ -259,7 +259,7 @@ public static partial class ParallelRowIterator
}
int verticalStep = DivideCeil(height, numOfSteps);
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
ParallelOptions parallelOptions = CreateParallelOptions(parallelSettings, numOfSteps);
RowIntervalOperationWrapper<T, TBuffer> wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation);
_ = Parallel.For(
@ -272,6 +272,37 @@ public static partial class ParallelRowIterator
[MethodImpl(InliningOptions.ShortMethod)]
private static int DivideCeil(long dividend, int divisor) => (int)Math.Min(1 + ((dividend - 1) / divisor), int.MaxValue);
/// <summary>
/// Creates the <see cref="ParallelOptions"/> for the current iteration.
/// </summary>
/// <param name="parallelSettings">The execution settings.</param>
/// <param name="numOfSteps">The number of row partitions to execute.</param>
/// <returns>The <see cref="ParallelOptions"/> instance.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private static ParallelOptions CreateParallelOptions(in ParallelExecutionSettings parallelSettings, int numOfSteps)
=> new() { MaxDegreeOfParallelism = parallelSettings.MaxDegreeOfParallelism == -1 ? -1 : numOfSteps };
/// <summary>
/// Calculates the number of row partitions to execute for the given region.
/// </summary>
/// <param name="width">The width of the region.</param>
/// <param name="height">The height of the region.</param>
/// <param name="parallelSettings">The execution settings.</param>
/// <returns>The number of row partitions to execute.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetNumberOfSteps(int width, int height, in ParallelExecutionSettings parallelSettings)
{
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
if (parallelSettings.MaxDegreeOfParallelism == -1)
{
// Row batching cannot produce more useful partitions than the number of rows available.
return Math.Min(height, maxSteps);
}
return Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
}
private static void ValidateRectangle(Rectangle rectangle)
{
Guard.MustBeGreaterThan(
@ -284,4 +315,35 @@ public static partial class ParallelRowIterator
0,
$"{nameof(rectangle)}.{nameof(rectangle.Height)}");
}
/// <summary>
/// Validates the supplied <see cref="ParallelExecutionSettings"/>.
/// </summary>
/// <param name="parallelSettings">The execution settings.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <see cref="ParallelExecutionSettings.MaxDegreeOfParallelism"/> or
/// <see cref="ParallelExecutionSettings.MinimumPixelsProcessedPerTask"/> is invalid.
/// </exception>
/// <exception cref="ArgumentNullException">
/// Thrown when <see cref="ParallelExecutionSettings.MemoryAllocator"/> is null.
/// This also guards the public <see cref="ParallelExecutionSettings"/> default value, which bypasses constructor validation.
/// </exception>
private static void ValidateSettings(in ParallelExecutionSettings parallelSettings)
{
// ParallelExecutionSettings is a public struct, so callers can pass default and bypass constructor validation.
if (parallelSettings.MaxDegreeOfParallelism is 0 or < -1)
{
throw new ArgumentOutOfRangeException(
$"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MaxDegreeOfParallelism)}");
}
Guard.MustBeGreaterThan(
parallelSettings.MinimumPixelsProcessedPerTask,
0,
$"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MinimumPixelsProcessedPerTask)}");
Guard.NotNull(
parallelSettings.MemoryAllocator,
$"{nameof(parallelSettings)}.{nameof(ParallelExecutionSettings.MemoryAllocator)}");
}
}

16
src/ImageSharp/Common/Helpers/Numerics.cs

@ -643,6 +643,20 @@ internal static class Numerics
return Avx.Blend(result, alpha, BlendAlphaControl);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> UnPremultiply(Vector512<float> source, Vector512<float> alpha)
{
// Check if alpha is zero to avoid division by zero
Vector512<float> zeroMask = Vector512.Equals(alpha, Vector512<float>.Zero);
// Divide source by alpha if alpha is nonzero, otherwise set all components to match the source value
Vector512<float> result = Vector512.ConditionalSelect(zeroMask, source, source / alpha);
// Blend the result with the alpha vector to ensure that the alpha component is unchanged
Vector512<float> alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle();
return Vector512.ConditionalSelect(alphaMask, alpha, result);
}
/// <summary>
/// Permutes the given vector return a new instance with all the values set to <see cref="Vector4.W"/>.
/// </summary>
@ -690,7 +704,7 @@ internal static class Numerics
/// </summary>
/// <param name="vectors">The span of vectors</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void CubePowOnXYZ(Span<Vector4> vectors)
public static void CubePowOnXYZ(Span<Vector4> vectors)
{
ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
ref Vector4 endRef = ref Unsafe.Add(ref baseRef, (uint)vectors.Length);

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

@ -601,51 +601,6 @@ internal static partial class SimdUtils
}
}
/// <summary>
/// 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>
/// <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.AlwaysInline)]
public static Vector256<float> MultiplyAdd(
Vector256<float> va,
Vector256<float> vm0,
Vector256<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(vm1, vm0, va);
}
return va + (vm0 * vm1);
}
/// <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(
Vector256<float> a,
Vector256<float> b,
Vector256<float> c)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAddNegated(a, b, c);
}
return Avx.Subtract(c, Avx.Multiply(a, b));
}
/// <summary>
/// Blend packed 8-bit integers from <paramref name="left"/> and <paramref name="right"/> using <paramref name="mask"/>.
/// The high bit of each corresponding <paramref name="mask"/> byte determines the selection.

22
src/ImageSharp/Common/Helpers/Vector256Utilities.cs

@ -115,6 +115,28 @@ internal static class Vector256_
return va + (vm0 * vm1);
}
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector256{Single}"/>.
/// </summary>
/// <remarks>ret = va - (vm0 * vm1)</remarks>
/// <param name="va">The vector to add to the negated 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> MultiplyAddNegated(
Vector256<float> va,
Vector256<float> vm0,
Vector256<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplyAddNegated(vm0, vm1, va);
}
return va - (vm0 * vm1);
}
/// <summary>
/// Performs a multiplication and a subtraction of the <see cref="Vector256{Single}"/>.
/// </summary>

15
src/ImageSharp/Common/Helpers/Vector512Utilities.cs

@ -87,6 +87,21 @@ internal static class Vector512_
Vector512<float> vm1)
=> Avx512F.FusedMultiplyAdd(vm0, vm1, va);
/// <summary>
/// Performs a multiplication and a negated addition of the <see cref="Vector512{Single}"/>.
/// </summary>
/// <remarks>ret = va - (vm0 * vm1)</remarks>
/// <param name="va">The vector to add to the negated 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="Vector512{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> MultiplyAddNegated(
Vector512<float> va,
Vector512<float> vm0,
Vector512<float> vm1)
=> Avx512F.FusedMultiplyAddNegated(vm0, vm1, va);
/// <summary>
/// Restricts a vector between a minimum and a maximum value.
/// </summary>

1
src/ImageSharp/Configuration.cs

@ -64,6 +64,7 @@ public sealed class Configuration
/// <summary>
/// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms
/// configured with this <see cref="Configuration"/> instance.
/// Set to <c>-1</c> to leave the degree of parallelism unbounded.
/// Initialized with <see cref="Environment.ProcessorCount"/> by default.
/// </summary>
public int MaxDegreeOfParallelism

2
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -468,7 +468,7 @@ internal sealed class GifDecoderCore : ImageDecoderCore
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
rawColorTable = this.currentLocalColorTable!.GetSpan()[..length];
rawColorTable = this.currentLocalColorTable.GetSpan()[..length];
}
else if (this.globalColorTable != null)
{

32
src/ImageSharp/ImageInfo.cs

@ -63,8 +63,12 @@ public class ImageInfo
public int Height => this.Size.Height;
/// <summary>
/// Gets the number of frames in the image.
/// Gets the number of frame metadata entries available for the image.
/// </summary>
/// <remarks>
/// This value is the same as <see cref="FrameMetadataCollection"/> count and may be <c>0</c> when frame
/// metadata was not populated by the decoder.
/// </remarks>
public int FrameCount => this.FrameMetadataCollection.Count;
/// <summary>
@ -73,8 +77,12 @@ public class ImageInfo
public ImageMetadata Metadata { get; }
/// <summary>
/// Gets the collection of metadata associated with individual image frames.
/// Gets the metadata associated with the decoded image frames, if available.
/// </summary>
/// <remarks>
/// For multi-frame formats, decoders populate one entry per decoded frame. For single-frame formats, this
/// collection is typically empty.
/// </remarks>
public IReadOnlyList<ImageFrameMetadata> FrameMetadataCollection { get; }
/// <summary>
@ -86,4 +94,24 @@ public class ImageInfo
/// Gets the bounds of the image.
/// </summary>
public Rectangle Bounds => new(Point.Empty, this.Size);
/// <summary>
/// Gets the total number of bytes required to store the image pixels in memory.
/// </summary>
/// <remarks>
/// This reports the in-memory size of the pixel data represented by this <see cref="ImageInfo"/>, not the
/// encoded size of the image file. The value is computed from the image dimensions and
/// <see cref="PixelType"/>. When <see cref="FrameMetadataCollection"/> contains decoded frame metadata, the
/// per-frame size is multiplied by that count. Otherwise, the value is the in-memory size of the single
/// image frame represented by this <see cref="ImageInfo"/>.
/// </remarks>
/// <returns>The total number of bytes required to store the image pixels in memory.</returns>
public long GetPixelMemorySize()
{
int count = this.FrameMetadataCollection.Count > 0
? this.FrameMetadataCollection.Count
: 1;
return (long)this.Size.Width * this.Size.Height * (this.PixelType.BitsPerPixel / 8) * count;
}
}

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

File diff suppressed because it is too large

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

@ -89,7 +89,34 @@ var blenders = new []{
{
amount = Numerics.Clamp(amount, 0, 1);
if (Avx2.IsSupported && destination.Length >= 2)
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));
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));
@ -128,7 +155,37 @@ var blenders = new []{
{
amount = Numerics.Clamp(amount, 0, 1);
if (Avx2.IsSupported && destination.Length >= 2)
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));
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));
@ -164,7 +221,51 @@ var blenders = new []{
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
if (Avx2.IsSupported && destination.Length >= 2)
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));
@ -211,7 +312,54 @@ var blenders = new []{
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, Vector4 source, ReadOnlySpan<float> amount)
{
if (Avx2.IsSupported && destination.Length >= 2)
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));

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

File diff suppressed because it is too large

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

@ -47,7 +47,18 @@ internal static partial class PorterDuffFunctions
/// <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);
=> Avx.Blend(source, source * opacity, BlendAlphaControl);
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>Src(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
/// <summary>
/// Returns the result of the "<#=blender#>SrcAtop" compositing equation.
@ -74,7 +85,22 @@ internal static partial class PorterDuffFunctions
[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);
source = Avx.Blend(source, source * opacity, BlendAlphaControl);
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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>SrcAtop(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
return Atop(backdrop, source, <#=blender#>(backdrop, source));
}
@ -104,7 +130,22 @@ internal static partial class PorterDuffFunctions
[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);
source = Avx.Blend(source, source * opacity, BlendAlphaControl);
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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>SrcOver(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
return Over(backdrop, source, <#=blender#>(backdrop, source));
}
@ -133,7 +174,18 @@ internal static partial class PorterDuffFunctions
/// <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));
=> In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl));
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>SrcIn(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512()));
/// <summary>
/// Returns the result of the "<#=blender#>SrcOut" compositing equation.
@ -159,7 +211,18 @@ internal static partial class PorterDuffFunctions
/// <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));
=> Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl));
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>SrcOut(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512()));
/// <summary>
/// Returns the result of the "<#=blender#>Dest" compositing equation.
@ -187,6 +250,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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>Dest(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
return backdrop;
}
/// <summary>
/// Returns the result of the "<#=blender#>DestAtop" compositing equation.
/// </summary>
@ -212,7 +288,22 @@ internal static partial class PorterDuffFunctions
[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);
source = Avx.Blend(source, source * opacity, BlendAlphaControl);
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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>DestAtop(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
return Atop(source, backdrop, <#=blender#>(source, backdrop));
}
@ -242,7 +333,22 @@ internal static partial class PorterDuffFunctions
[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);
source = Avx.Blend(source, source * opacity, BlendAlphaControl);
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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>DestOver(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
{
source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512());
return Over(source, backdrop, <#=blender#>(source, backdrop));
}
@ -271,7 +377,18 @@ internal static partial class PorterDuffFunctions
/// <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);
=> In(Avx.Blend(source, source * opacity, BlendAlphaControl), 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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>DestIn(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop);
/// <summary>
/// Returns the result of the "<#=blender#>DestOut" compositing equation.
@ -297,7 +414,18 @@ internal static partial class PorterDuffFunctions
/// <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);
=> Out(Avx.Blend(source, source * opacity, BlendAlphaControl), 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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>DestOut(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop);
/// <summary>
/// Returns the result of the "<#=blender#>Xor" compositing equation.
@ -323,7 +451,18 @@ internal static partial class PorterDuffFunctions
/// <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));
=> Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl));
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>Xor(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512()));
/// <summary>
/// Returns the result of the "<#=blender#>Clear" compositing equation.
@ -349,7 +488,18 @@ internal static partial class PorterDuffFunctions
/// <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));
=> Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl));
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> <#=blender#>Clear(Vector512<float> backdrop, Vector512<float> source, Vector512<float> opacity)
=> Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512()));
<#} #>

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

@ -5,6 +5,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -44,6 +45,16 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> Normal(Vector256<float> backdrop, Vector256<float> 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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Normal(Vector512<float> backdrop, Vector512<float> source)
=> source;
/// <summary>
/// Returns the result of the "Multiply" compositing equation.
/// </summary>
@ -62,7 +73,17 @@ internal static partial class PorterDuffFunctions
/// <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);
=> 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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Multiply(Vector512<float> backdrop, Vector512<float> source)
=> backdrop * source;
/// <summary>
/// Returns the result of the "Add" compositing equation.
@ -82,7 +103,17 @@ internal static partial class PorterDuffFunctions
/// <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));
=> Vector256.Min(Vector256.Create(1F), 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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Add(Vector512<float> backdrop, Vector512<float> source)
=> Vector512.Min(Vector512.Create(1F), backdrop + source);
/// <summary>
/// Returns the result of the "Subtract" compositing equation.
@ -102,7 +133,17 @@ internal static partial class PorterDuffFunctions
/// <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));
=> Vector256.Max(Vector256<float>.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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Subtract(Vector512<float> backdrop, Vector512<float> source)
=> Vector512.Max(Vector512<float>.Zero, backdrop - source);
/// <summary>
/// Returns the result of the "Screen" compositing equation.
@ -124,7 +165,20 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> Screen(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> vOne = Vector256.Create(1F);
return SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Subtract(vOne, backdrop), Avx.Subtract(vOne, source), vOne);
return Vector256_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - 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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Screen(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> vOne = Vector512.Create(1F);
return Vector512_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - source);
}
/// <summary>
@ -145,7 +199,17 @@ internal static partial class PorterDuffFunctions
/// <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);
=> Vector256.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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Darken(Vector512<float> backdrop, Vector512<float> source)
=> Vector512.Min(backdrop, source);
/// <summary>
/// Returns the result of the "Lighten" compositing equation.
@ -164,7 +228,17 @@ internal static partial class PorterDuffFunctions
/// <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);
=> Vector256.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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Lighten(Vector512<float> backdrop, Vector512<float> source)
=> Vector512.Max(backdrop, source);
/// <summary>
/// Returns the result of the "Overlay" compositing equation.
@ -192,7 +266,20 @@ internal static partial class PorterDuffFunctions
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));
return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl));
}
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Overlay(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> color = OverlayValueFunction(backdrop, source);
return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512<float>.Zero, color));
}
/// <summary>
@ -221,7 +308,20 @@ internal static partial class PorterDuffFunctions
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));
return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256<float>.Zero, BlendAlphaControl));
}
/// <summary>
/// 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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> HardLight(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> color = OverlayValueFunction(source, backdrop);
return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512<float>.Zero, color));
}
/// <summary>
@ -244,14 +344,32 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> OverlayValueFunction(Vector256<float> backdrop, Vector256<float> source)
{
Vector256<float> vOne = Vector256.Create(1F);
Vector256<float> left = Avx.Multiply(Avx.Add(backdrop, backdrop), source);
Vector256<float> left = (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> right = Vector256_.MultiplyAddNegated(vOne, vOneMinusSource + vOneMinusSource, vOne - backdrop);
Vector256<float> cmp = Avx.CompareGreaterThan(backdrop, Vector256.Create(.5F));
return Avx.BlendVariable(left, right, cmp);
}
/// <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 Vector512<float> OverlayValueFunction(Vector512<float> backdrop, Vector512<float> source)
{
Vector512<float> vOne = Vector512.Create(1F);
Vector512<float> left = (backdrop + backdrop) * source;
Vector512<float> vOneMinusSource = vOne - source;
Vector512<float> right = Vector512_.MultiplyAddNegated(vOne, vOneMinusSource + vOneMinusSource, vOne - backdrop);
Vector512<float> cmp = Avx512F.CompareGreaterThan(backdrop, Vector512.Create(.5F));
return Vector512.ConditionalSelect(cmp, right, left);
}
/// <summary>
/// Returns the result of the "Over" compositing equation.
/// </summary>
@ -295,17 +413,47 @@ internal static partial class PorterDuffFunctions
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);
Vector256<float> blendW = sW * dW;
Vector256<float> dstW = dW - blendW;
Vector256<float> srcW = sW - blendW;
// calculate final alpha
Vector256<float> alpha = dstW + sW;
// calculate final color
Vector256<float> color = destination * dstW;
color = Vector256_.MultiplyAdd(color, source, srcW);
color = Vector256_.MultiplyAdd(color, blend, blendW);
// unpremultiply
return Numerics.UnPremultiply(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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Over(Vector512<float> destination, Vector512<float> source, Vector512<float> blend)
{
// calculate weights
Vector512<float> sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> dW = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
Vector512<float> blendW = sW * dW;
Vector512<float> dstW = dW - blendW;
Vector512<float> srcW = sW - blendW;
// calculate final alpha
Vector256<float> alpha = Avx.Add(dstW, sW);
Vector512<float> alpha = 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);
Vector512<float> color = destination * dstW;
color = Vector512_.MultiplyAdd(color, source, srcW);
color = Vector512_.MultiplyAdd(color, blend, blendW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -354,11 +502,36 @@ internal static partial class PorterDuffFunctions
// calculate weights
Vector256<float> sW = Avx.Permute(source, ShuffleAlphaControl);
Vector256<float> blendW = Avx.Multiply(sW, alpha);
Vector256<float> dstW = Avx.Subtract(alpha, blendW);
Vector256<float> blendW = sW * alpha;
Vector256<float> dstW = alpha - blendW;
// calculate final color
Vector256<float> color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW);
Vector256<float> color = Vector256_.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
}
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Atop(Vector512<float> destination, Vector512<float> source, Vector512<float> blend)
{
// calculate final alpha
Vector512<float> alpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
// calculate weights
Vector512<float> sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> blendW = sW * alpha;
Vector512<float> dstW = alpha - blendW;
// calculate final color
Vector512<float> color = Vector512_.MultiplyAdd(blend * blendW, destination, dstW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -392,10 +565,29 @@ internal static partial class PorterDuffFunctions
public static Vector256<float> In(Vector256<float> destination, Vector256<float> source)
{
// calculate alpha
Vector256<float> alpha = Avx.Permute(Avx.Multiply(source, destination), ShuffleAlphaControl);
Vector256<float> alpha = Avx.Permute(source * destination, ShuffleAlphaControl);
// premultiply
Vector256<float> color = Avx.Multiply(source, alpha);
Vector256<float> color = source * alpha;
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
}
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> In(Vector512<float> destination, Vector512<float> source)
{
// calculate alpha
Vector512<float> alpha = Vector512_.ShuffleNative(source * destination, ShuffleAlphaControl);
// premultiply
Vector512<float> color = source * alpha;
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -429,10 +621,29 @@ internal static partial class PorterDuffFunctions
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);
Vector256<float> alpha = Avx.Permute(source * (Vector256.Create(1F) - destination), ShuffleAlphaControl);
// premultiply
Vector256<float> color = source * alpha;
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
}
/// <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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Out(Vector512<float> destination, Vector512<float> source)
{
// calculate alpha
Vector512<float> alpha = Vector512_.ShuffleNative(source * (Vector512.Create(1F) - destination), ShuffleAlphaControl);
// premultiply
Vector256<float> color = Avx.Multiply(source, alpha);
Vector512<float> color = source * alpha;
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -475,12 +686,37 @@ internal static partial class PorterDuffFunctions
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);
Vector256<float> srcW = vOne - dW;
Vector256<float> dstW = vOne - sW;
// 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);
Vector256<float> alpha = Vector256_.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW);
Vector256<float> color = Vector256_.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW);
// unpremultiply
return Numerics.UnPremultiply(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="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Xor(Vector512<float> destination, Vector512<float> source)
{
// calculate weights
Vector512<float> sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl);
Vector512<float> dW = Vector512_.ShuffleNative(destination, ShuffleAlphaControl);
Vector512<float> vOne = Vector512.Create(1F);
Vector512<float> srcW = vOne - dW;
Vector512<float> dstW = vOne - sW;
// calculate alpha
Vector512<float> alpha = Vector512_.MultiplyAdd(dW * dstW, sW, srcW);
Vector512<float> color = Vector512_.MultiplyAdd((dW * destination) * dstW, sW * source, srcW);
// unpremultiply
return Numerics.UnPremultiply(color, alpha);
@ -491,4 +727,11 @@ internal static partial class PorterDuffFunctions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> Clear(Vector256<float> backdrop, Vector256<float> source) => Vector256<float>.Zero;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<float> Clear(Vector512<float> backdrop, Vector512<float> source) => Vector512<float>.Zero;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<float> AlphaMask512()
=> Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle();
}

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

@ -18,8 +18,8 @@ public class PorterDuffBulkVsSingleVector
[GlobalSetup]
public void Setup()
{
this.backdrop = new Vector4[8 * 20];
this.source = new Vector4[8 * 20];
this.backdrop = new Vector4[8 * 40];
this.source = new Vector4[8 * 40];
FillRandom(this.backdrop);
FillRandom(this.source);
@ -49,7 +49,7 @@ public class PorterDuffBulkVsSingleVector
return result;
}
[Benchmark(Description = "Avx")]
[Benchmark(Description = "Avx2")]
public Vector256<float> OverlayValueFunction_Avx()
{
ref Vector256<float> backdrop = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference<Vector4>(this.backdrop));
@ -65,4 +65,21 @@ public class PorterDuffBulkVsSingleVector
return result;
}
[Benchmark(Description = "Avx512")]
public Vector512<float> OverlayValueFunction_Avx512()
{
ref Vector512<float> backdrop = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference<Vector4>(this.backdrop));
ref Vector512<float> source = ref Unsafe.As<Vector4, Vector512<float>>(ref MemoryMarshal.GetReference<Vector4>(this.source));
Vector512<float> result = default;
Vector512<float> opacity = Vector512.Create(.5F);
int count = this.backdrop.Length / 4;
for (nuint i = 0; i < (uint)count; i++)
{
result = PorterDuffFunctions.NormalSrcOver(Unsafe.Add(ref backdrop, i), Unsafe.Add(ref source, i), opacity);
}
return result;
}
}

115
tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs

@ -13,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers;
public class ParallelRowIteratorTests
{
public delegate void BufferedRowAction<T>(int y, Span<T> span);
public delegate void RowIntervalAction<T>(RowInterval rows, Span<T> span);
private readonly ITestOutputHelper output;
@ -200,6 +201,47 @@ public class ParallelRowIteratorTests
Assert.Equal(expectedData, actualData);
}
[Fact]
public void IterateRows_MaxDegreeOfParallelismMinusOne_ShouldVisitAllRows()
{
ParallelExecutionSettings parallelSettings = new(
-1,
10,
Configuration.Default.MemoryAllocator);
Rectangle rectangle = new(0, 0, 10, 10);
int[] actualData = new int[rectangle.Height];
void RowAction(int y) => actualData[y]++;
TestRowActionOperation operation = new(RowAction);
ParallelRowIterator.IterateRows(
rectangle,
in parallelSettings,
in operation);
Assert.Equal(Enumerable.Repeat(1, rectangle.Height), actualData);
}
[Fact]
public void IterateRowsWithTempBuffer_DefaultSettingsRequireInitialization()
{
ParallelExecutionSettings parallelSettings = default;
Rectangle rect = new(0, 0, 10, 10);
void RowAction(int y, Span<Rgba32> memory)
{
}
TestRowOperation<Rgba32> operation = new(RowAction);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
() => ParallelRowIterator.IterateRows<TestRowOperation<Rgba32>, Rgba32>(rect, in parallelSettings, in operation));
Assert.Contains(nameof(ParallelExecutionSettings.MaxDegreeOfParallelism), ex.Message);
}
public static TheoryData<int, int, int, int, int, int, int> IterateRows_WithEffectiveMinimumPixelsLimit_Data =
new()
{
@ -296,6 +338,53 @@ public class ParallelRowIteratorTests
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
}
[Fact]
public void IterateRowIntervalsWithTempBuffer_MaxDegreeOfParallelismMinusOne_ShouldVisitAllRows()
{
ParallelExecutionSettings parallelSettings = new(
-1,
10,
Configuration.Default.MemoryAllocator);
Rectangle rectangle = new(0, 0, 10, 10);
int[] actualData = new int[rectangle.Height];
void RowAction(RowInterval rows, Span<Vector4> buffer)
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y]++;
}
}
TestRowIntervalOperation<Vector4> operation = new(RowAction);
ParallelRowIterator.IterateRowIntervals<TestRowIntervalOperation<Vector4>, Vector4>(
rectangle,
in parallelSettings,
in operation);
Assert.Equal(Enumerable.Repeat(1, rectangle.Height), actualData);
}
[Fact]
public void IterateRows_DefaultSettingsRequireInitialization()
{
ParallelExecutionSettings parallelSettings = default;
Rectangle rect = new(0, 0, 10, 10);
void RowAction(int y)
{
}
TestRowActionOperation operation = new(RowAction);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
() => ParallelRowIterator.IterateRows(rect, in parallelSettings, in operation));
Assert.Contains(nameof(ParallelExecutionSettings.MaxDegreeOfParallelism), ex.Message);
}
public static readonly TheoryData<int, int, int, int, int, int, int> IterateRectangularBuffer_Data =
new()
{
@ -445,6 +534,32 @@ public class ParallelRowIteratorTests
}
}
private readonly struct TestRowActionOperation : IRowOperation
{
private readonly Action<int> action;
public TestRowActionOperation(Action<int> action)
=> this.action = action;
public void Invoke(int y)
=> this.action(y);
}
private readonly struct TestRowOperation<TBuffer> : IRowOperation<TBuffer>
where TBuffer : unmanaged
{
private readonly BufferedRowAction<TBuffer> action;
public TestRowOperation(BufferedRowAction<TBuffer> action)
=> this.action = action;
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
public void Invoke(int y, Span<TBuffer> span)
=> this.action(y, span);
}
private readonly struct TestRowIntervalOperation : IRowIntervalOperation
{
private readonly Action<RowInterval> action;

30
tests/ImageSharp.Tests/ImageInfoTests.cs

@ -54,4 +54,34 @@ public class ImageInfoTests
Assert.Equal(meta, info.Metadata);
Assert.Equal(frameMetadata.Count, info.FrameMetadataCollection.Count);
}
[Fact]
public void GetPixelMemorySize_UsesSingleFrameWhenFrameMetadataIsEmpty()
{
const int width = 10;
const int height = 20;
ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance };
meta.GetPngMetadata();
ImageInfo info = new(new Size(width, height), meta);
Assert.Equal(width * height * 4, info.GetPixelMemorySize());
}
[Fact]
public void GetPixelMemorySize_UsesFrameMetadataCountWhenAvailable()
{
const int width = 10;
const int height = 20;
IReadOnlyList<ImageFrameMetadata> frameMetadata = [new(), new(), new()];
ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance };
meta.GetPngMetadata();
ImageInfo info = new(new Size(width, height), meta, frameMetadata);
Assert.Equal(width * height * 4 * frameMetadata.Count, info.GetPixelMemorySize());
}
}

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

@ -59,7 +59,7 @@ public class PorterDuffCompositorTests
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX,
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX,
provider,
mode.ToString());
}

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

@ -4,7 +4,6 @@
using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using Castle.Components.DictionaryAdapter;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.ImageSharp.Tests.TestUtilities;
@ -45,6 +44,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(NormalBlendFunctionData))]
public void NormalBlendFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.NormalSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, 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) },
@ -77,6 +92,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(MultiplyFunctionData))]
public void MultiplyFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.MultiplySrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, 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) },
@ -109,6 +140,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(AddFunctionData))]
public void AddFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.AddSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, 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) },
@ -141,6 +188,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(SubtractFunctionData))]
public void SubtractFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.SubtractSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, 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) },
@ -173,6 +236,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(ScreenFunctionData))]
public void ScreenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.ScreenSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, 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) },
@ -205,6 +284,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(DarkenFunctionData))]
public void DarkenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.DarkenSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, 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) },
@ -237,6 +332,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(LightenFunctionData))]
public void LightenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.LightenSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, 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) },
@ -269,6 +380,22 @@ public class PorterDuffFunctionsTests
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(OverlayFunctionData))]
public void OverlayFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.OverlaySrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, 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) },
@ -300,4 +427,27 @@ public class PorterDuffFunctionsTests
Vector256<float> actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount));
Assert.Equal(expected256, actual, FloatComparer);
}
[Theory]
[MemberData(nameof(HardLightFunctionData))]
public void HardLightFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected)
{
if (!Avx512F.IsSupported)
{
return;
}
Vector512<float> back512 = CreateVector512(back);
Vector512<float> source512 = CreateVector512(source);
Vector512<float> expected512 = CreateVector512(expected);
Vector512<float> actual = PorterDuffFunctions.HardLightSrcOver(back512, source512, Vector512.Create(amount));
Assert.Equal(expected512, actual, FloatComparer);
}
private static Vector512<float> CreateVector512(TestVector4 vector)
=> Vector512.Create(
vector.X, vector.Y, vector.Z, vector.W,
vector.X, vector.Y, vector.Z, vector.W,
vector.X, vector.Y, vector.Z, vector.W,
vector.X, vector.Y, vector.Z, vector.W);
}

108
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs

@ -9,12 +9,21 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders;
public class PorterDuffFunctionsTestsTPixel
{
private const int BulkBlendCount = 4;
private static Span<T> AsSpan<T>(T value)
where T : struct
{
return new Span<T>(new[] { value });
}
private static T[] CreateFilledArray<T>(T value)
{
T[] values = new T[BulkBlendCount];
values.AsSpan().Fill(value);
return values;
}
public static TheoryData<object, object, float, object> NormalBlendFunctionData = new()
{
{ new TestPixel<Rgba32>(1, 1, 1, 1), new TestPixel<Rgba32>(1, 1, 1, 1), 1, new TestPixel<Rgba32>(1, 1, 1, 1) },
@ -46,9 +55,14 @@ public class PorterDuffFunctionsTestsTPixel
public void NormalBlendFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.NormalSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
public static TheoryData<object, object, float, object> MultiplyFunctionData = new()
@ -86,9 +100,14 @@ public class PorterDuffFunctionsTestsTPixel
public void MultiplyFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.MultiplySrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
public static TheoryData<object, object, float, object> AddFunctionData = new()
@ -136,9 +155,14 @@ public class PorterDuffFunctionsTestsTPixel
public void AddFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.AddSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
public static TheoryData<object, object, float, object> SubtractFunctionData = new()
@ -176,9 +200,14 @@ public class PorterDuffFunctionsTestsTPixel
public void SubtractFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.SubtractSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
public static TheoryData<object, object, float, object> ScreenFunctionData = new()
@ -216,9 +245,14 @@ public class PorterDuffFunctionsTestsTPixel
public void ScreenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.ScreenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
public static TheoryData<object, object, float, object> DarkenFunctionData = new()
@ -256,9 +290,14 @@ public class PorterDuffFunctionsTestsTPixel
public void DarkenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.DarkenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
public static TheoryData<object, object, float, object> LightenFunctionData = new()
@ -296,9 +335,14 @@ public class PorterDuffFunctionsTestsTPixel
public void LightenFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.LightenSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
public static TheoryData<object, object, float, object> OverlayFunctionData = new()
@ -336,9 +380,14 @@ public class PorterDuffFunctionsTestsTPixel
public void OverlayFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.OverlaySrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
public static TheoryData<object, object, float, object> HardLightFunctionData = new()
@ -376,8 +425,13 @@ public class PorterDuffFunctionsTestsTPixel
public void HardLightFunctionBlenderBulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> dest = new(new TPixel[1]);
new DefaultPixelBlenders<TPixel>.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount));
VectorAssert.Equal(expected.AsPixel(), dest[0], 2);
TPixel[] dest = new TPixel[BulkBlendCount];
new DefaultPixelBlenders<TPixel>.HardLightSrcOver().Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount));
TPixel expectedPixel = expected.AsPixel();
foreach (TPixel pixel in dest)
{
VectorAssert.Equal(expectedPixel, pixel, 2);
}
}
}

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

@ -15,7 +15,8 @@ internal readonly struct ApproximateFloatComparer :
IEqualityComparer<Vector2>,
IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>,
IEqualityComparer<Vector256<float>>
IEqualityComparer<Vector256<float>>,
IEqualityComparer<Vector512<float>>
{
private readonly float epsilon;
@ -78,4 +79,19 @@ internal readonly struct ApproximateFloatComparer :
&& this.Equals(x.GetElement(7), y.GetElement(7));
public int GetHashCode([DisallowNull] Vector256<float> obj) => obj.GetHashCode();
public bool Equals(Vector512<float> x, Vector512<float> y)
{
for (int i = 0; i < Vector512<float>.Count; i++)
{
if (!this.Equals(x.GetElement(i), y.GetElement(i)))
{
return false;
}
}
return true;
}
public int GetHashCode([DisallowNull] Vector512<float> obj) => obj.GetHashCode();
}

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

@ -455,6 +455,7 @@ public enum HwIntrinsics : long
DisableVAES = 1L << 17,
DisableWAITPKG = 1L << 18,
DisableX86Serialize = 1 << 19,
// Arm64
DisableArm64Aes = 1L << 20,
DisableArm64Atomics = 1L << 21,
@ -466,6 +467,7 @@ public enum HwIntrinsics : long
DisableArm64Sha256 = 1L << 27,
DisableArm64Sve = 1L << 28,
DisableArm64Sve2 = 1L << 29,
// RISC-V64
DisableRiscV64Zba = 1L << 30,
DisableRiscV64Zbb = 1L << 31,

Loading…
Cancel
Save