diff --git a/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs b/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs new file mode 100644 index 000000000..4bf0d1fe4 --- /dev/null +++ b/src/ImageSharp/Advanced/IRowAction{TBuffer}.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowAction + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row and a buffer. + /// + /// The row y coordinate. + /// The contiguous region of memory. + void Invoke(int y, Span span); + } + + internal readonly struct WrappingRowAction + where T : struct, IRowAction + where TBuffer : unmanaged + { + public readonly int MinY; + public readonly int MaxY; + public readonly int StepY; + public readonly int MaxX; + + private readonly MemoryAllocator allocator; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction( + int minY, + int maxY, + int stepY, + MemoryAllocator allocator, + in T action) + : this(minY, maxY, stepY, 0, allocator, action) + { + } + + [MethodImpl(InliningOptions.ShortMethod)] + public WrappingRowAction( + int minY, + int maxY, + int stepY, + int maxX, + MemoryAllocator allocator, + in T action) + { + this.MinY = minY; + this.MaxY = maxY; + this.StepY = stepY; + this.MaxX = maxX; + this.allocator = allocator; + 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); + + using IMemoryOwner buffer = this.allocator.Allocate(this.MaxX); + + Span span = buffer.Memory.Span; + + for (int y = yMin; y < yMax; y++) + { + Unsafe.AsRef(this.action).Invoke(y, span); + } + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 7802d9653..8bfda431a 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -141,6 +141,72 @@ namespace SixLabors.ImageSharp.Advanced rowAction.Invoke); } + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row action to perform. + /// The type of buffer elements. + /// The . + /// The to get the parallel settings from. + /// The method body defining the iteration logic on a single . + public static void IterateRows2(Rectangle rectangle, Configuration configuration, in T body) + where T : struct, IRowAction + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows2(rectangle, in parallelSettings, in body); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + internal static void IterateRows2( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T body) + where T : struct, IRowAction + where TBuffer : unmanaged + { + 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); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + using (IMemoryOwner buffer = allocator.Allocate(width)) + { + Span span = buffer.Memory.Span; + + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(body).Invoke(y, span); + } + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var rowAction = new WrappingRowAction(top, bottom, verticalStep, width, allocator, in body); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + rowAction.Invoke); + } + /// /// Iterate through the rows of a rectangle in optimized batches defined by -s. ///