diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs
index b178434ce..830fcf736 100644
--- a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs
+++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction.cs
@@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Advanced.ParallelUtils
{
///
- /// Defines the contract for.
+ /// Defines the contract for an action that operates on a row interval.
///
public interface IRowIntervalAction
{
@@ -21,15 +21,22 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
internal readonly struct WrappingRowIntervalInfo
{
- public readonly int Min;
- public readonly int Max;
- public readonly int Step;
+ public readonly int MinY;
+ public readonly int MaxY;
+ public readonly int StepY;
+ public readonly int MaxX;
- public WrappingRowIntervalInfo(int min, int max, int step)
+ public WrappingRowIntervalInfo(int minY, int maxY, int stepY)
+ : this(minY, maxY, stepY, 0)
{
- this.Min = min;
- this.Max = max;
- this.Step = step;
+ }
+
+ public WrappingRowIntervalInfo(int minY, int maxY, int stepY, int maxX)
+ {
+ this.MinY = minY;
+ this.MaxY = maxY;
+ this.StepY = stepY;
+ this.MaxX = maxX;
}
}
@@ -39,7 +46,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
private readonly WrappingRowIntervalInfo info;
private readonly T action;
- public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, ref T action)
+ public WrappingRowIntervalAction(in WrappingRowIntervalInfo info, in T action)
{
this.info = info;
this.action = action;
@@ -48,14 +55,14 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int i)
{
- int yMin = this.info.Min + (i * this.info.Step);
+ int yMin = this.info.MinY + (i * this.info.StepY);
- if (yMin >= this.info.Max)
+ if (yMin >= this.info.MaxY)
{
return;
}
- int yMax = Math.Min(yMin + this.info.Step, this.info.Max);
+ int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY);
var rows = new RowInterval(yMin, yMax);
diff --git a/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs
new file mode 100644
index 000000000..c0899ad3a
--- /dev/null
+++ b/src/ImageSharp/Advanced/ParallelUtils/IRowIntervalAction{TBuffer}.cs
@@ -0,0 +1,67 @@
+// 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.ParallelUtils
+{
+ ///
+ /// Defines the contract for an action that operates on a row interval with a temporary buffer.
+ ///
+ /// The type of buffer elements.
+ public interface IRowIntervalAction
+ where TBuffer : unmanaged
+ {
+ ///
+ /// Invokes the method passing the row interval and a buffer.
+ ///
+ /// The row interval.
+ /// The contiguous region of memory.
+ void Invoke(in RowInterval rows, Memory memory);
+ }
+
+ internal readonly struct WrappingRowIntervalAction : IRowIntervalAction
+ where T : struct, IRowIntervalAction
+ where TBuffer : unmanaged
+ {
+ private readonly WrappingRowIntervalInfo info;
+ private readonly MemoryAllocator allocator;
+ private readonly T action;
+
+ public WrappingRowIntervalAction(
+ in WrappingRowIntervalInfo info,
+ MemoryAllocator allocator,
+ in T action)
+ {
+ this.info = info;
+ this.allocator = allocator;
+ this.action = action;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int i)
+ {
+ int yMin = this.info.MinY + (i * this.info.StepY);
+
+ if (yMin >= this.info.MaxY)
+ {
+ return;
+ }
+
+ int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY);
+
+ var rows = new RowInterval(yMin, yMax);
+
+ using (IMemoryOwner buffer = this.allocator.Allocate(this.info.MaxX))
+ {
+ this.Invoke(in rows, buffer.Memory);
+ }
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(in RowInterval rows, Memory memory) => this.action.Invoke(in rows, memory);
+ }
+}
diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
index 58f3169d1..92c2ff20f 100644
--- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
+++ b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
@@ -21,14 +21,16 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
///
/// Iterate through the rows of a rectangle in optimized batches defined by -s.
///
+ /// 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 .
- public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body)
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void IterateRows(Rectangle rectangle, Configuration configuration, in T body)
+ where T : struct, IRowIntervalAction
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
-
- IterateRows(rectangle, in parallelSettings, body);
+ IterateRows(rectangle, in parallelSettings, in body);
}
///
@@ -36,52 +38,120 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
///
/// The type of row action to perform.
/// The .
- /// The to get the parallel settings from.
+ /// The .
/// The method body defining the iteration logic on a single .
- public static void IterateRowsFast(Rectangle rectangle, Configuration configuration, ref T body)
- where T : struct, IRowIntervalAction
- {
- var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
-
- IterateRowsFast(rectangle, in parallelSettings, ref body);
- }
-
- internal static void IterateRowsFast(
+ public static void IterateRows(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
- ref T body)
+ in T body)
where T : struct, IRowIntervalAction
{
ValidateRectangle(rectangle);
- int maxSteps = DivideCeil(
- rectangle.Width * rectangle.Height,
- parallelSettings.MinimumPixelsProcessedPerTask);
+ 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)
{
- var rows = new RowInterval(rectangle.Top, rectangle.Bottom);
+ var rows = new RowInterval(top, bottom);
body.Invoke(in rows);
return;
}
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
-
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+ var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep);
+ var rowAction = new WrappingRowIntervalAction(in rowInfo, in body);
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ i => rowAction.Invoke(i));
+ }
+
+ ///
+ /// 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 IterateRows(Rectangle rectangle, Configuration configuration, in T body)
+ where T : struct, IRowIntervalAction
+ where TBuffer : unmanaged
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ IterateRows(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 IterateRows(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ in T body)
+ where T : struct, IRowIntervalAction
+ where TBuffer : unmanaged
+ {
+ ValidateRectangle(rectangle);
int top = rectangle.Top;
int bottom = rectangle.Bottom;
- var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep);
- var rowAction = new WrappingRowIntervalAction(in rowInfo, ref body);
+ 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)
+ {
+ var rows = new RowInterval(top, bottom);
+ using (IMemoryOwner buffer = allocator.Allocate(width))
+ {
+ body.Invoke(rows, buffer.Memory);
+ }
+
+ return;
+ }
+
+ int verticalStep = DivideCeil(height, numOfSteps);
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+ var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width);
+ var rowAction = new WrappingRowIntervalAction(in rowInfo, allocator, in body);
Parallel.For(
0,
numOfSteps,
parallelOptions,
- i => rowAction.Invoke(i));
+ i =>
+ rowAction.Invoke(i));
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ /// The .
+ /// The to get the parallel settings from.
+ /// The method body defining the iteration logic on a single .
+ // [Obsolete("Use non-allocating generic versions instead.")]
+ public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body)
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+
+ IterateRows(rectangle, in parallelSettings, body);
}
///
@@ -90,10 +160,11 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
/// The .
/// The .
/// The method body defining the iteration logic on a single .
+ // [Obsolete("Use non-allocating generic versions instead.")]
public static void IterateRows(
- Rectangle rectangle,
- in ParallelExecutionSettings parallelSettings,
- Action body)
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ Action body)
{
ValidateRectangle(rectangle);
@@ -143,6 +214,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
/// Iterate through the rows of a rectangle in optimized batches defined by -s
/// instantiating a temporary buffer for each invocation.
///
+ // [Obsolete("Use non-allocating generic versions instead.")]
internal static void IterateRowsWithTempBuffer(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
@@ -204,6 +276,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
/// Iterate through the rows of a rectangle in optimized batches defined by -s
/// instantiating a temporary buffer for each invocation.
///
+ // [Obsolete("Use non-allocating generic versions instead.")]
internal static void IterateRowsWithTempBuffer(
Rectangle rectangle,
Configuration configuration,
@@ -213,7 +286,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body);
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
private static void ValidateRectangle(Rectangle rectangle)
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
index 59ef75b6e..8d3fec97d 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
@@ -54,10 +54,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
var rowAction = new RowAction(ref bounds, source, destination);
- ParallelHelper.IterateRowsFast(
+ ParallelHelper.IterateRows(
bounds,
in parallelSettings,
- ref rowAction);
+ in rowAction);
}
private readonly struct RowAction : IRowIntervalAction