diff --git a/src/ImageSharp/Advanced/IRowAction.cs b/src/ImageSharp/Advanced/IRowAction.cs new file mode 100644 index 000000000..74498eb0b --- /dev/null +++ b/src/ImageSharp/Advanced/IRowAction.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row. + /// + public interface IRowAction + { + /// + /// Invokes the method passing the row y coordinate. + /// + /// The row y coordinate. + void Invoke(int y); + } + + /// + /// A that wraps a value delegate of a specified type, and info on the memory areas to process + /// + /// The type of value delegate to invoke + internal readonly struct WrappingRowAction + where T : struct, IRowAction + { + public readonly int MinY; + public readonly int MaxY; + public readonly int StepY; + public readonly int MaxX; + + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction(int minY, int maxY, int stepY, in T action) + : this(minY, maxY, stepY, 0, action) + { + } + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction(int minY, int maxY, int stepY, int maxX, in T action) + { + this.MinY = minY; + this.MaxY = maxY; + this.StepY = stepY; + this.MaxX = maxX; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.MinY + (i * this.StepY); + + if (yMin >= this.MaxY) + { + return; + } + + int yMax = Math.Min(yMin + this.StepY, this.MaxY); + + for (int y = yMin; y < yMax; y++) + { + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(this.action).Invoke(y); + } + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 5c8a30f68..275c3d10e 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -76,6 +76,66 @@ namespace SixLabors.ImageSharp.Advanced rowAction.Invoke); } + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row action to perform. + /// The . + /// The to get the parallel settings from. + /// The method body defining the iteration logic on a single row. + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRows2(Rectangle rectangle, Configuration configuration, in T body) + where T : struct, IRowAction + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows2(rectangle, in parallelSettings, in body); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row action to perform. + /// The . + /// The . + /// The method body defining the iteration logic on a single row. + public static void IterateRows2( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T body) + where T : struct, IRowAction + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(body).Invoke(y); + } + + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var rowAction = new WrappingRowAction(top, bottom, verticalStep, in body); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + rowAction.Invoke); + } + /// /// Iterate through the rows of a rectangle in optimized batches defined by -s /// instantiating a temporary buffer for each invocation.