Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single row value delegates for parallel iterator #1108

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/ImageSharp/Advanced/IRowAction.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Defines the contract for an action that operates on a row.
/// </summary>
public interface IRowAction
{
/// <summary>
/// Invokes the method passing the row y coordinate.
/// </summary>
/// <param name="y">The row y coordinate.</param>
void Invoke(int y);
}

/// <summary>
/// A <see langword="struct"/> that wraps a value delegate of a specified type, and info on the memory areas to process
/// </summary>
/// <typeparam name="T">The type of value delegate to invoke</typeparam>
internal readonly struct WrappingRowAction<T>
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);
}
}
}
}
88 changes: 88 additions & 0 deletions src/ImageSharp/Advanced/IRowAction{TBuffer}.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Defines the contract for an action that operates on a row with a temporary buffer.
/// </summary>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
public interface IRowAction<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Invokes the method passing the row and a buffer.
/// </summary>
/// <param name="y">The row y coordinate.</param>
/// <param name="span">The contiguous region of memory.</param>
void Invoke(int y, Span<TBuffer> span);
}

internal readonly struct WrappingRowAction<T, TBuffer>
where T : struct, IRowAction<TBuffer>
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<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.MaxX);

Span<TBuffer> span = buffer.Memory.Span;

for (int y = yMin; y < yMax; y++)
{
Unsafe.AsRef(this.action).Invoke(y, span);
}
}
}
}
37 changes: 21 additions & 16 deletions src/ImageSharp/Advanced/ParallelRowIterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,32 @@ namespace SixLabors.ImageSharp.Advanced
public static class ParallelRowIterator
{
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// Iterate through the rows of a rectangle in optimized batches.
/// </summary>
/// <typeparam name="T">The type of row action to perform.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single row.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void IterateRows<T>(Rectangle rectangle, Configuration configuration, in T body)
where T : struct, IRowIntervalAction
where T : struct, IRowAction
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, in body);
}

/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// Iterate through the rows of a rectangle in optimized batches.
/// </summary>
/// <typeparam name="T">The type of row action to perform.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single row.</param>
public static void IterateRows<T>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
in T body)
where T : struct, IRowIntervalAction
where T : struct, IRowAction
{
ValidateRectangle(rectangle);

Expand All @@ -59,15 +59,17 @@ public static void IterateRows<T>(
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
var rows = new RowInterval(top, bottom);
Unsafe.AsRef(body).Invoke(in rows);
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 rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep);
var rowAction = new WrappingRowIntervalAction<T>(in rowInfo, in body);
var rowAction = new WrappingRowAction<T>(top, bottom, verticalStep, in body);

Parallel.For(
0,
Expand All @@ -86,7 +88,7 @@ public static void IterateRows<T>(
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows<T, TBuffer>(Rectangle rectangle, Configuration configuration, in T body)
where T : struct, IRowIntervalAction<TBuffer>
where T : struct, IRowAction<TBuffer>
where TBuffer : unmanaged
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
Expand All @@ -101,7 +103,7 @@ internal static void IterateRows<T, TBuffer>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
in T body)
where T : struct, IRowIntervalAction<TBuffer>
where T : struct, IRowAction<TBuffer>
where TBuffer : unmanaged
{
ValidateRectangle(rectangle);
Expand All @@ -118,19 +120,22 @@ internal static void IterateRows<T, TBuffer>(
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
var rows = new RowInterval(top, bottom);
using (IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(width))
{
Unsafe.AsRef(body).Invoke(rows, buffer.Memory);
Span<TBuffer> 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 rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width);
var rowAction = new WrappingRowIntervalBufferAction<T, TBuffer>(in rowInfo, allocator, in body);
var rowAction = new WrappingRowAction<T, TBuffer>(top, bottom, verticalStep, width, allocator, in body);

Parallel.For(
0,
Expand Down
17 changes: 7 additions & 10 deletions src/ImageSharp/ImageFrame{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ internal ImageFrame<TPixel2> CloneAs<TPixel2>(Configuration configuration)
ParallelRowIterator.IterateRows(
this.Bounds(),
configuration,
new RowIntervalAction<TPixel2>(this, target, configuration));
new RowAction<TPixel2>(this, target, configuration));

return target;
}
Expand All @@ -289,15 +289,15 @@ internal void Clear(TPixel value)
/// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="ImageFrame{TPixel}"/>.
/// </summary>
private readonly struct RowIntervalAction<TPixel2> : IRowIntervalAction
private readonly struct RowAction<TPixel2> : IRowAction
where TPixel2 : struct, IPixel<TPixel2>
{
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel2> target;
private readonly Configuration configuration;

[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
ImageFrame<TPixel> source,
ImageFrame<TPixel2> target,
Configuration configuration)
Expand All @@ -309,14 +309,11 @@ public RowIntervalAction(

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel2> targetRow = this.target.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.To(this.configuration, sourceRow, targetRow);
}
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<TPixel2> targetRow = this.target.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.To(this.configuration, sourceRow, targetRow);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;

namespace SixLabors.ImageSharp.Processing.Processors.Binarization
Expand Down Expand Up @@ -54,13 +53,13 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
ParallelRowIterator.IterateRows(
workingRect,
configuration,
new RowIntervalAction(source, upper, lower, threshold, startX, endX, isAlphaOnly));
new RowAction(source, upper, lower, threshold, startX, endX, isAlphaOnly));
}

/// <summary>
/// A <see langword="struct"/> implementing the clone logic for <see cref="BinaryThresholdProcessor{TPixel}"/>.
/// </summary>
private readonly struct RowIntervalAction : IRowIntervalAction
private readonly struct RowAction : IRowAction
{
private readonly ImageFrame<TPixel> source;
private readonly TPixel upper;
Expand All @@ -71,7 +70,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
private readonly bool isAlphaOnly;

[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalAction(
public RowAction(
ImageFrame<TPixel> source,
TPixel upper,
TPixel lower,
Expand All @@ -91,22 +90,20 @@ public RowIntervalAction(

/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
public void Invoke(int y)
{
Rgba32 rgba = default;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> row = this.source.GetPixelRowSpan(y);

for (int x = this.startX; x < this.endX; x++)
{
ref TPixel color = ref row[x];
color.ToRgba32(ref rgba);
Span<TPixel> row = this.source.GetPixelRowSpan(y);

for (int x = this.startX; x < this.endX; x++)
{
ref TPixel color = ref row[x];
color.ToRgba32(ref rgba);

// Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
color = luminance >= this.threshold ? this.upper : this.lower;
}
// Convert to grayscale using ITU-R Recommendation BT.709 if required
byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
color = luminance >= this.threshold ? this.upper : this.lower;
}
}
}
Expand Down
Loading