Skip to content

Commit b10a7ed

Browse files
Merge pull request #3102 from SixLabors/js/blend-perf-fixes
Add working-buffer blending and adjust row APIs
2 parents bd19151 + b33f288 commit b10a7ed

File tree

5 files changed

+208
-31
lines changed

5 files changed

+208
-31
lines changed

src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ public interface IRowOperation<TBuffer>
1515
/// </summary>
1616
/// <param name="bounds">The bounds of the operation.</param>
1717
/// <returns>The required buffer length.</returns>
18-
int GetRequiredBufferLength(Rectangle bounds);
18+
public int GetRequiredBufferLength(Rectangle bounds);
1919

2020
/// <summary>
2121
/// Invokes the method passing the row and a buffer.
2222
/// </summary>
2323
/// <param name="y">The row y coordinate.</param>
2424
/// <param name="span">The contiguous region of memory.</param>
25-
void Invoke(int y, Span<TBuffer> span);
25+
public void Invoke(int y, Span<TBuffer> span);
2626
}

src/ImageSharp/Advanced/ParallelRowIterator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public static void IterateRows<T>(
6868
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
6969
RowOperationWrapper<T> wrappingOperation = new(top, bottom, verticalStep, in operation);
7070

71-
Parallel.For(
71+
_ = Parallel.For(
7272
0,
7373
numOfSteps,
7474
parallelOptions,
@@ -138,7 +138,7 @@ public static void IterateRows<T, TBuffer>(
138138
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
139139
RowOperationWrapper<T, TBuffer> wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation);
140140

141-
Parallel.For(
141+
_ = Parallel.For(
142142
0,
143143
numOfSteps,
144144
parallelOptions,
@@ -195,7 +195,7 @@ public static void IterateRowIntervals<T>(
195195
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
196196
RowIntervalOperationWrapper<T> wrappingOperation = new(top, bottom, verticalStep, in operation);
197197

198-
Parallel.For(
198+
_ = Parallel.For(
199199
0,
200200
numOfSteps,
201201
parallelOptions,
@@ -262,7 +262,7 @@ public static void IterateRowIntervals<T, TBuffer>(
262262
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
263263
RowIntervalOperationWrapper<T, TBuffer> wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation);
264264

265-
Parallel.For(
265+
_ = Parallel.For(
266266
0,
267267
numOfSteps,
268268
parallelOptions,

src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers;
5+
using System.Numerics;
56
using SixLabors.ImageSharp.Formats.Webp.Chunks;
67
using SixLabors.ImageSharp.Formats.Webp.Lossless;
78
using SixLabors.ImageSharp.Formats.Webp.Lossy;
@@ -456,15 +457,21 @@ private static void DrawDecodedImageFrameOnCanvas<TPixel>(
456457
// The destination frame has already been prepopulated with the pixel data from the previous frame
457458
// so blending will leave the desired result which takes into consideration restoration to the
458459
// background color within the restore area.
459-
PixelBlender<TPixel> blender =
460-
PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
460+
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(
461+
PixelColorBlendingMode.Normal,
462+
PixelAlphaCompositionMode.SrcOver);
463+
464+
// By using a dedicated vector span we can avoid per-row pool allocations in PixelBlender.Blend
465+
// We need 3 Vector4 values per pixel to store the background, foreground, and result pixels for blending.
466+
using IMemoryOwner<Vector4> workingBufferOwner = imageFrame.Configuration.MemoryAllocator.Allocate<Vector4>(restoreArea.Width * 3);
467+
Span<Vector4> workingBuffer = workingBufferOwner.GetSpan();
461468

462469
for (int y = 0; y < restoreArea.Height; y++)
463470
{
464471
Span<TPixel> framePixelRow = imageFramePixels.DangerousGetRowSpan(y);
465472
Span<TPixel> decodedPixelRow = decodedImageFrame.DangerousGetRowSpan(y)[..restoreArea.Width];
466473

467-
blender.Blend<TPixel>(imageFrame.Configuration, framePixelRow, framePixelRow, decodedPixelRow, 1f);
474+
blender.Blend<TPixel>(imageFrame.Configuration, framePixelRow, framePixelRow, decodedPixelRow, 1f, workingBuffer);
468475
}
469476

470477
return;

src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs

Lines changed: 180 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Buffers;
55
using System.Numerics;
6-
using SixLabors.ImageSharp.Memory;
76

87
namespace SixLabors.ImageSharp.PixelFormats;
98

@@ -52,16 +51,53 @@ public void Blend<TPixelSrc>(
5251
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
5352

5453
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 3);
55-
Span<Vector4> destinationVectors = buffer.Slice(0, maxLength);
56-
Span<Vector4> backgroundVectors = buffer.Slice(maxLength, maxLength);
57-
Span<Vector4> sourceVectors = buffer.Slice(maxLength * 2, maxLength);
54+
this.Blend(
55+
configuration,
56+
destination,
57+
background,
58+
source,
59+
amount,
60+
buffer.Memory.Span[..(maxLength * 3)]);
61+
}
62+
63+
/// <summary>
64+
/// Blends 2 rows together using caller-provided temporary vector scratch.
65+
/// </summary>
66+
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
67+
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
68+
/// <param name="destination">the destination span</param>
69+
/// <param name="background">the background span</param>
70+
/// <param name="source">the source span</param>
71+
/// <param name="amount">
72+
/// A value between 0 and 1 indicating the weight of the second source vector.
73+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
74+
/// </param>
75+
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 3 rows.</param>
76+
public void Blend<TPixelSrc>(
77+
Configuration configuration,
78+
Span<TPixel> destination,
79+
ReadOnlySpan<TPixel> background,
80+
ReadOnlySpan<TPixelSrc> source,
81+
float amount,
82+
Span<Vector4> workingBuffer)
83+
where TPixelSrc : unmanaged, IPixel<TPixelSrc>
84+
{
85+
int maxLength = destination.Length;
86+
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
87+
Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length));
88+
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
89+
Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length));
90+
91+
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
92+
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
93+
Span<Vector4> sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength);
5894

5995
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
6096
PixelOperations<TPixelSrc>.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale);
6197

6298
this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount);
6399

64-
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
100+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
65101
}
66102

67103
/// <summary>
@@ -87,14 +123,48 @@ public void Blend(
87123
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
88124

89125
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
90-
Span<Vector4> destinationVectors = buffer.Slice(0, maxLength);
91-
Span<Vector4> backgroundVectors = buffer.Slice(maxLength, maxLength);
126+
this.Blend(
127+
configuration,
128+
destination,
129+
background,
130+
source,
131+
amount,
132+
buffer.Memory.Span[..(maxLength * 2)]);
133+
}
134+
135+
/// <summary>
136+
/// Blends a row against a constant source color using caller-provided temporary vector scratch.
137+
/// </summary>
138+
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
139+
/// <param name="destination">the destination span</param>
140+
/// <param name="background">the background span</param>
141+
/// <param name="source">the source color</param>
142+
/// <param name="amount">
143+
/// A value between 0 and 1 indicating the weight of the second source vector.
144+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
145+
/// </param>
146+
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 2 rows.</param>
147+
public void Blend(
148+
Configuration configuration,
149+
Span<TPixel> destination,
150+
ReadOnlySpan<TPixel> background,
151+
TPixel source,
152+
float amount,
153+
Span<Vector4> workingBuffer)
154+
{
155+
int maxLength = destination.Length;
156+
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
157+
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
158+
Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length));
159+
160+
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
161+
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
92162

93163
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
94164

95165
this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount);
96166

97-
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
167+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
98168
}
99169

100170
/// <summary>
@@ -116,6 +186,27 @@ public void Blend(
116186
ReadOnlySpan<float> amount)
117187
=> this.Blend<TPixel>(configuration, destination, background, source, amount);
118188

189+
/// <summary>
190+
/// Blends 2 rows together using caller-provided temporary vector scratch.
191+
/// </summary>
192+
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
193+
/// <param name="destination">the destination span</param>
194+
/// <param name="background">the background span</param>
195+
/// <param name="source">the source span</param>
196+
/// <param name="amount">
197+
/// A span with values between 0 and 1 indicating the weight of the second source vector.
198+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
199+
/// </param>
200+
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 3 rows.</param>
201+
public void Blend(
202+
Configuration configuration,
203+
Span<TPixel> destination,
204+
ReadOnlySpan<TPixel> background,
205+
ReadOnlySpan<TPixel> source,
206+
ReadOnlySpan<float> amount,
207+
Span<Vector4> workingBuffer)
208+
=> this.Blend<TPixel>(configuration, destination, background, source, amount, workingBuffer);
209+
119210
/// <summary>
120211
/// Blends 2 rows together
121212
/// </summary>
@@ -142,20 +233,89 @@ public void Blend<TPixelSrc>(
142233
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
143234

144235
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 3);
145-
Span<Vector4> destinationVectors = buffer.Slice(0, maxLength);
146-
Span<Vector4> backgroundVectors = buffer.Slice(maxLength, maxLength);
147-
Span<Vector4> sourceVectors = buffer.Slice(maxLength * 2, maxLength);
236+
this.Blend(
237+
configuration,
238+
destination,
239+
background,
240+
source,
241+
amount,
242+
buffer.Memory.Span[..(maxLength * 3)]);
243+
}
244+
245+
/// <summary>
246+
/// Blends a row against a constant source color.
247+
/// </summary>
248+
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
249+
/// <param name="destination">the destination span</param>
250+
/// <param name="background">the background span</param>
251+
/// <param name="source">the source color</param>
252+
/// <param name="amount">
253+
/// A span with values between 0 and 1 indicating the weight of the second source vector.
254+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
255+
/// </param>
256+
public void Blend(
257+
Configuration configuration,
258+
Span<TPixel> destination,
259+
ReadOnlySpan<TPixel> background,
260+
TPixel source,
261+
ReadOnlySpan<float> amount)
262+
{
263+
int maxLength = destination.Length;
264+
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
265+
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
266+
267+
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
268+
this.Blend(
269+
configuration,
270+
destination,
271+
background,
272+
source,
273+
amount,
274+
buffer.Memory.Span[..(maxLength * 2)]);
275+
}
276+
277+
/// <summary>
278+
/// Blends 2 rows together using caller-provided temporary vector scratch.
279+
/// </summary>
280+
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
281+
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
282+
/// <param name="destination">the destination span</param>
283+
/// <param name="background">the background span</param>
284+
/// <param name="source">the source span</param>
285+
/// <param name="amount">
286+
/// A span with values between 0 and 1 indicating the weight of the second source vector.
287+
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
288+
/// </param>
289+
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 3 rows.</param>
290+
public void Blend<TPixelSrc>(
291+
Configuration configuration,
292+
Span<TPixel> destination,
293+
ReadOnlySpan<TPixel> background,
294+
ReadOnlySpan<TPixelSrc> source,
295+
ReadOnlySpan<float> amount,
296+
Span<Vector4> workingBuffer)
297+
where TPixelSrc : unmanaged, IPixel<TPixelSrc>
298+
{
299+
int maxLength = destination.Length;
300+
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
301+
Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length));
302+
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
303+
Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length));
304+
305+
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
306+
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
307+
Span<Vector4> sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength);
148308

149309
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
150310
PixelOperations<TPixelSrc>.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale);
151311

152312
this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount);
153313

154-
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
314+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
155315
}
156316

157317
/// <summary>
158-
/// Blends a row against a constant source color.
318+
/// Blends a row against a constant source color using caller-provided temporary vector scratch.
159319
/// </summary>
160320
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
161321
/// <param name="destination">the destination span</param>
@@ -165,26 +325,28 @@ public void Blend<TPixelSrc>(
165325
/// A span with values between 0 and 1 indicating the weight of the second source vector.
166326
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
167327
/// </param>
328+
/// <param name="workingBuffer">Reusable temporary vector scratch with capacity for at least 2 rows.</param>
168329
public void Blend(
169330
Configuration configuration,
170331
Span<TPixel> destination,
171332
ReadOnlySpan<TPixel> background,
172333
TPixel source,
173-
ReadOnlySpan<float> amount)
334+
ReadOnlySpan<float> amount,
335+
Span<Vector4> workingBuffer)
174336
{
175337
int maxLength = destination.Length;
176338
Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length));
177339
Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length));
340+
Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length));
178341

179-
using IMemoryOwner<Vector4> buffer = configuration.MemoryAllocator.Allocate<Vector4>(maxLength * 2);
180-
Span<Vector4> destinationVectors = buffer.Slice(0, maxLength);
181-
Span<Vector4> backgroundVectors = buffer.Slice(maxLength, maxLength);
342+
Span<Vector4> destinationVectors = workingBuffer[..maxLength];
343+
Span<Vector4> backgroundVectors = workingBuffer.Slice(maxLength, maxLength);
182344

183345
PixelOperations<TPixel>.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale);
184346

185347
this.BlendFunction(destinationVectors, backgroundVectors, source.ToScaledVector4(), amount);
186348

187-
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale);
349+
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, destinationVectors, destination, PixelConversionModifiers.Scale);
188350
}
189351

190352
/// <summary>

0 commit comments

Comments
 (0)