Skip to content

Commit 1835475

Browse files
Merge pull request #1108 from SixLabors/sp/single-row-parallel-value-delegate
WIP: single row value delegates for parallel iterator
2 parents dd7bd7b + b545ea0 commit 1835475

24 files changed

+664
-606
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
7+
namespace SixLabors.ImageSharp.Advanced
8+
{
9+
/// <summary>
10+
/// Defines the contract for an action that operates on a row.
11+
/// </summary>
12+
public interface IRowAction
13+
{
14+
/// <summary>
15+
/// Invokes the method passing the row y coordinate.
16+
/// </summary>
17+
/// <param name="y">The row y coordinate.</param>
18+
void Invoke(int y);
19+
}
20+
21+
/// <summary>
22+
/// A <see langword="struct"/> that wraps a value delegate of a specified type, and info on the memory areas to process
23+
/// </summary>
24+
/// <typeparam name="T">The type of value delegate to invoke</typeparam>
25+
internal readonly struct WrappingRowAction<T>
26+
where T : struct, IRowAction
27+
{
28+
public readonly int MinY;
29+
public readonly int MaxY;
30+
public readonly int StepY;
31+
public readonly int MaxX;
32+
33+
private readonly T action;
34+
35+
[MethodImpl(InliningOptions.ShortMethod)]
36+
public WrappingRowAction(int minY, int maxY, int stepY, in T action)
37+
: this(minY, maxY, stepY, 0, action)
38+
{
39+
}
40+
41+
[MethodImpl(InliningOptions.ShortMethod)]
42+
public WrappingRowAction(int minY, int maxY, int stepY, int maxX, in T action)
43+
{
44+
this.MinY = minY;
45+
this.MaxY = maxY;
46+
this.StepY = stepY;
47+
this.MaxX = maxX;
48+
this.action = action;
49+
}
50+
51+
[MethodImpl(InliningOptions.ShortMethod)]
52+
public void Invoke(int i)
53+
{
54+
int yMin = this.MinY + (i * this.StepY);
55+
56+
if (yMin >= this.MaxY)
57+
{
58+
return;
59+
}
60+
61+
int yMax = Math.Min(yMin + this.StepY, this.MaxY);
62+
63+
for (int y = yMin; y < yMax; y++)
64+
{
65+
// Skip the safety copy when invoking a potentially impure method on a readonly field
66+
Unsafe.AsRef(this.action).Invoke(y);
67+
}
68+
}
69+
}
70+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Runtime.CompilerServices;
7+
using SixLabors.ImageSharp.Memory;
8+
9+
namespace SixLabors.ImageSharp.Advanced
10+
{
11+
/// <summary>
12+
/// Defines the contract for an action that operates on a row with a temporary buffer.
13+
/// </summary>
14+
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
15+
public interface IRowAction<TBuffer>
16+
where TBuffer : unmanaged
17+
{
18+
/// <summary>
19+
/// Invokes the method passing the row and a buffer.
20+
/// </summary>
21+
/// <param name="y">The row y coordinate.</param>
22+
/// <param name="span">The contiguous region of memory.</param>
23+
void Invoke(int y, Span<TBuffer> span);
24+
}
25+
26+
internal readonly struct WrappingRowAction<T, TBuffer>
27+
where T : struct, IRowAction<TBuffer>
28+
where TBuffer : unmanaged
29+
{
30+
public readonly int MinY;
31+
public readonly int MaxY;
32+
public readonly int StepY;
33+
public readonly int MaxX;
34+
35+
private readonly MemoryAllocator allocator;
36+
private readonly T action;
37+
38+
[MethodImpl(InliningOptions.ShortMethod)]
39+
public WrappingRowAction(
40+
int minY,
41+
int maxY,
42+
int stepY,
43+
MemoryAllocator allocator,
44+
in T action)
45+
: this(minY, maxY, stepY, 0, allocator, action)
46+
{
47+
}
48+
49+
[MethodImpl(InliningOptions.ShortMethod)]
50+
public WrappingRowAction(
51+
int minY,
52+
int maxY,
53+
int stepY,
54+
int maxX,
55+
MemoryAllocator allocator,
56+
in T action)
57+
{
58+
this.MinY = minY;
59+
this.MaxY = maxY;
60+
this.StepY = stepY;
61+
this.MaxX = maxX;
62+
this.allocator = allocator;
63+
this.action = action;
64+
}
65+
66+
[MethodImpl(InliningOptions.ShortMethod)]
67+
public void Invoke(int i)
68+
{
69+
int yMin = this.MinY + (i * this.StepY);
70+
71+
if (yMin >= this.MaxY)
72+
{
73+
return;
74+
}
75+
76+
int yMax = Math.Min(yMin + this.StepY, this.MaxY);
77+
78+
using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.MaxX);
79+
80+
Span<TBuffer> span = buffer.Memory.Span;
81+
82+
for (int y = yMin; y < yMax; y++)
83+
{
84+
Unsafe.AsRef(this.action).Invoke(y, span);
85+
}
86+
}
87+
}
88+
}

src/ImageSharp/Advanced/ParallelRowIterator.cs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,32 @@ namespace SixLabors.ImageSharp.Advanced
1919
public static class ParallelRowIterator
2020
{
2121
/// <summary>
22-
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
22+
/// Iterate through the rows of a rectangle in optimized batches.
2323
/// </summary>
2424
/// <typeparam name="T">The type of row action to perform.</typeparam>
2525
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
2626
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
27-
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
27+
/// <param name="body">The method body defining the iteration logic on a single row.</param>
2828
[MethodImpl(InliningOptions.ShortMethod)]
2929
public static void IterateRows<T>(Rectangle rectangle, Configuration configuration, in T body)
30-
where T : struct, IRowIntervalAction
30+
where T : struct, IRowAction
3131
{
3232
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
3333
IterateRows(rectangle, in parallelSettings, in body);
3434
}
3535

3636
/// <summary>
37-
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
37+
/// Iterate through the rows of a rectangle in optimized batches.
3838
/// </summary>
3939
/// <typeparam name="T">The type of row action to perform.</typeparam>
4040
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
4141
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
42-
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
42+
/// <param name="body">The method body defining the iteration logic on a single row.</param>
4343
public static void IterateRows<T>(
4444
Rectangle rectangle,
4545
in ParallelExecutionSettings parallelSettings,
4646
in T body)
47-
where T : struct, IRowIntervalAction
47+
where T : struct, IRowAction
4848
{
4949
ValidateRectangle(rectangle);
5050

@@ -59,15 +59,17 @@ public static void IterateRows<T>(
5959
// Avoid TPL overhead in this trivial case:
6060
if (numOfSteps == 1)
6161
{
62-
var rows = new RowInterval(top, bottom);
63-
Unsafe.AsRef(body).Invoke(in rows);
62+
for (int y = top; y < bottom; y++)
63+
{
64+
Unsafe.AsRef(body).Invoke(y);
65+
}
66+
6467
return;
6568
}
6669

6770
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
6871
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
69-
var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep);
70-
var rowAction = new WrappingRowIntervalAction<T>(in rowInfo, in body);
72+
var rowAction = new WrappingRowAction<T>(top, bottom, verticalStep, in body);
7173

7274
Parallel.For(
7375
0,
@@ -86,7 +88,7 @@ public static void IterateRows<T>(
8688
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
8789
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
8890
public static void IterateRows<T, TBuffer>(Rectangle rectangle, Configuration configuration, in T body)
89-
where T : struct, IRowIntervalAction<TBuffer>
91+
where T : struct, IRowAction<TBuffer>
9092
where TBuffer : unmanaged
9193
{
9294
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
@@ -101,7 +103,7 @@ internal static void IterateRows<T, TBuffer>(
101103
Rectangle rectangle,
102104
in ParallelExecutionSettings parallelSettings,
103105
in T body)
104-
where T : struct, IRowIntervalAction<TBuffer>
106+
where T : struct, IRowAction<TBuffer>
105107
where TBuffer : unmanaged
106108
{
107109
ValidateRectangle(rectangle);
@@ -118,19 +120,22 @@ internal static void IterateRows<T, TBuffer>(
118120
// Avoid TPL overhead in this trivial case:
119121
if (numOfSteps == 1)
120122
{
121-
var rows = new RowInterval(top, bottom);
122123
using (IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(width))
123124
{
124-
Unsafe.AsRef(body).Invoke(rows, buffer.Memory);
125+
Span<TBuffer> span = buffer.Memory.Span;
126+
127+
for (int y = top; y < bottom; y++)
128+
{
129+
Unsafe.AsRef(body).Invoke(y, span);
130+
}
125131
}
126132

127133
return;
128134
}
129135

130136
int verticalStep = DivideCeil(height, numOfSteps);
131137
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
132-
var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width);
133-
var rowAction = new WrappingRowIntervalBufferAction<T, TBuffer>(in rowInfo, allocator, in body);
138+
var rowAction = new WrappingRowAction<T, TBuffer>(top, bottom, verticalStep, width, allocator, in body);
134139

135140
Parallel.For(
136141
0,

src/ImageSharp/ImageFrame{TPixel}.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ internal ImageFrame<TPixel2> CloneAs<TPixel2>(Configuration configuration)
263263
ParallelRowIterator.IterateRows(
264264
this.Bounds(),
265265
configuration,
266-
new RowIntervalAction<TPixel2>(this, target, configuration));
266+
new RowAction<TPixel2>(this, target, configuration));
267267

268268
return target;
269269
}
@@ -289,15 +289,15 @@ internal void Clear(TPixel value)
289289
/// <summary>
290290
/// A <see langword="struct"/> implementing the clone logic for <see cref="ImageFrame{TPixel}"/>.
291291
/// </summary>
292-
private readonly struct RowIntervalAction<TPixel2> : IRowIntervalAction
292+
private readonly struct RowAction<TPixel2> : IRowAction
293293
where TPixel2 : struct, IPixel<TPixel2>
294294
{
295295
private readonly ImageFrame<TPixel> source;
296296
private readonly ImageFrame<TPixel2> target;
297297
private readonly Configuration configuration;
298298

299299
[MethodImpl(InliningOptions.ShortMethod)]
300-
public RowIntervalAction(
300+
public RowAction(
301301
ImageFrame<TPixel> source,
302302
ImageFrame<TPixel2> target,
303303
Configuration configuration)
@@ -309,14 +309,11 @@ public RowIntervalAction(
309309

310310
/// <inheritdoc/>
311311
[MethodImpl(InliningOptions.ShortMethod)]
312-
public void Invoke(in RowInterval rows)
312+
public void Invoke(int y)
313313
{
314-
for (int y = rows.Min; y < rows.Max; y++)
315-
{
316-
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
317-
Span<TPixel2> targetRow = this.target.GetPixelRowSpan(y);
318-
PixelOperations<TPixel>.Instance.To(this.configuration, sourceRow, targetRow);
319-
}
314+
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
315+
Span<TPixel2> targetRow = this.target.GetPixelRowSpan(y);
316+
PixelOperations<TPixel>.Instance.To(this.configuration, sourceRow, targetRow);
320317
}
321318
}
322319
}

src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Runtime.CompilerServices;
66
using SixLabors.ImageSharp.Advanced;
7-
using SixLabors.ImageSharp.Memory;
87
using SixLabors.ImageSharp.PixelFormats;
98

109
namespace SixLabors.ImageSharp.Processing.Processors.Binarization
@@ -54,13 +53,13 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
5453
ParallelRowIterator.IterateRows(
5554
workingRect,
5655
configuration,
57-
new RowIntervalAction(source, upper, lower, threshold, startX, endX, isAlphaOnly));
56+
new RowAction(source, upper, lower, threshold, startX, endX, isAlphaOnly));
5857
}
5958

6059
/// <summary>
6160
/// A <see langword="struct"/> implementing the clone logic for <see cref="BinaryThresholdProcessor{TPixel}"/>.
6261
/// </summary>
63-
private readonly struct RowIntervalAction : IRowIntervalAction
62+
private readonly struct RowAction : IRowAction
6463
{
6564
private readonly ImageFrame<TPixel> source;
6665
private readonly TPixel upper;
@@ -71,7 +70,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
7170
private readonly bool isAlphaOnly;
7271

7372
[MethodImpl(InliningOptions.ShortMethod)]
74-
public RowIntervalAction(
73+
public RowAction(
7574
ImageFrame<TPixel> source,
7675
TPixel upper,
7776
TPixel lower,
@@ -91,22 +90,20 @@ public RowIntervalAction(
9190

9291
/// <inheritdoc/>
9392
[MethodImpl(InliningOptions.ShortMethod)]
94-
public void Invoke(in RowInterval rows)
93+
public void Invoke(int y)
9594
{
9695
Rgba32 rgba = default;
97-
for (int y = rows.Min; y < rows.Max; y++)
98-
{
99-
Span<TPixel> row = this.source.GetPixelRowSpan(y);
10096

101-
for (int x = this.startX; x < this.endX; x++)
102-
{
103-
ref TPixel color = ref row[x];
104-
color.ToRgba32(ref rgba);
97+
Span<TPixel> row = this.source.GetPixelRowSpan(y);
98+
99+
for (int x = this.startX; x < this.endX; x++)
100+
{
101+
ref TPixel color = ref row[x];
102+
color.ToRgba32(ref rgba);
105103

106-
// Convert to grayscale using ITU-R Recommendation BT.709 if required
107-
byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
108-
color = luminance >= this.threshold ? this.upper : this.lower;
109-
}
104+
// Convert to grayscale using ITU-R Recommendation BT.709 if required
105+
byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
106+
color = luminance >= this.threshold ? this.upper : this.lower;
110107
}
111108
}
112109
}

0 commit comments

Comments
 (0)