Skip to content

Commit 11ed8b1

Browse files
Use ThreadLocal<T> for allocations
1 parent 54bd57c commit 11ed8b1

4 files changed

Lines changed: 96 additions & 25 deletions

File tree

src/ImageSharp.Drawing/Processing/GradientBrush.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public abstract BrushApplicator<TPixel> CreateApplicator<TPixel>(
4444
/// <summary>
4545
/// Base class for gradient brush applicators
4646
/// </summary>
47+
/// <typeparam name="TPixel">The pixel format.</typeparam>
4748
internal abstract class GradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
4849
where TPixel : unmanaged, IPixel<TPixel>
4950
{
@@ -115,11 +116,10 @@ protected GradientBrushApplicator(
115116
{
116117
return from.Color.ToPixel<TPixel>();
117118
}
118-
else
119-
{
120-
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
121-
return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel<TPixel>();
122-
}
119+
120+
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
121+
122+
return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel<TPixel>();
123123
}
124124
}
125125

src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public override BrushApplicator<TPixel> CreateApplicator<TPixel>(
5454
/// <summary>
5555
/// The linear gradient brush applicator.
5656
/// </summary>
57+
/// <typeparam name="TPixel">The pixel format.</typeparam>
5758
private sealed class LinearGradientBrushApplicator<TPixel> : GradientBrushApplicator<TPixel>
5859
where TPixel : unmanaged, IPixel<TPixel>
5960
{

src/ImageSharp.Drawing/Processing/PathGradientBrush.cs

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Linq;
88
using System.Numerics;
9+
using System.Threading;
910
using SixLabors.ImageSharp.Memory;
1011
using SixLabors.ImageSharp.PixelFormats;
1112

@@ -208,6 +209,7 @@ public Vector4 ColorAt(float distance)
208209
/// <summary>
209210
/// The path gradient brush applicator.
210211
/// </summary>
212+
/// <typeparam name="TPixel">The pixel format.</typeparam>
211213
private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
212214
where TPixel : unmanaged, IPixel<TPixel>
213215
{
@@ -219,14 +221,22 @@ private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
219221

220222
private readonly float maxDistance;
221223

222-
private readonly int maxIntersections;
223-
224224
private readonly IList<Edge> edges;
225225

226226
private readonly TPixel centerPixel;
227227

228228
private readonly TPixel transparentPixel;
229229

230+
private readonly MemoryAllocator allocator;
231+
232+
private readonly int maxIntersections;
233+
234+
private readonly int scalineWidth;
235+
236+
private readonly ThreadLocal<ThreadContextData> threadContextData;
237+
238+
private bool isDisposed;
239+
230240
/// <summary>
231241
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
232242
/// </summary>
@@ -253,8 +263,14 @@ public PathGradientBrushApplicator(
253263
this.hasSpecialCenterColor = hasSpecialCenterColor;
254264
this.centerPixel = centerColor.ToPixel<TPixel>();
255265
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Max(d => d.Length());
256-
this.maxIntersections = this.edges.Max(e => e.Path.MaxIntersections);
257266
this.transparentPixel = Color.Transparent.ToPixel<TPixel>();
267+
268+
this.scalineWidth = source.Width;
269+
this.maxIntersections = this.edges.Max(e => e.Path.MaxIntersections);
270+
this.allocator = configuration.MemoryAllocator;
271+
this.threadContextData = new ThreadLocal<ThreadContextData>(
272+
() => new ThreadContextData(this.allocator, this.scalineWidth, this.maxIntersections),
273+
true);
258274
}
259275

260276
internal TPixel this[int x, int y, Span<PointF> intersections, Span<PointOrientation> orientations]
@@ -317,38 +333,55 @@ public PathGradientBrushApplicator(
317333
/// <inheritdoc />
318334
public override void Apply(Span<float> scanline, int x, int y)
319335
{
320-
MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator;
321-
using IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length);
322-
using IMemoryOwner<TPixel> overlayBuffer = memoryAllocator.Allocate<TPixel>(scanline.Length);
323-
using IMemoryOwner<PointF> intersectionsBuffer = memoryAllocator.Allocate<PointF>(this.maxIntersections);
324-
using IMemoryOwner<PointOrientation> orientationsBuffer = memoryAllocator.Allocate<PointOrientation>(this.maxIntersections);
325-
326-
Span<float> amountSpan = amountBuffer.Memory.Span;
327-
Span<TPixel> overlaySpan = overlayBuffer.Memory.Span;
328-
Span<PointF> intersectionsSpan = intersectionsBuffer.Memory.Span;
329-
Span<PointOrientation> orientationsSpan = orientationsBuffer.Memory.Span;
336+
ThreadContextData values = this.threadContextData.Value;
337+
Span<float> amounts = values.AmountSpan.Slice(0, scanline.Length);
338+
Span<TPixel> overlays = values.OverlaySpan.Slice(0, scanline.Length);
339+
Span<PointF> intersections = values.IntersectionsSpan;
340+
Span<PointOrientation> orientations = values.OrientationsSpan;
330341
float blendPercentage = this.Options.BlendPercentage;
331342

332343
// TODO: Remove bounds checks.
333344
if (blendPercentage < 1)
334345
{
335346
for (int i = 0; i < scanline.Length; i++)
336347
{
337-
amountSpan[i] = scanline[i] * blendPercentage;
338-
overlaySpan[i] = this[x + i, y, intersectionsSpan, orientationsSpan];
348+
amounts[i] = scanline[i] * blendPercentage;
349+
overlays[i] = this[x + i, y, intersections, orientations];
339350
}
340351
}
341352
else
342353
{
343354
for (int i = 0; i < scanline.Length; i++)
344355
{
345-
amountSpan[i] = scanline[i];
346-
overlaySpan[i] = this[x + i, y, intersectionsSpan, orientationsSpan];
356+
amounts[i] = scanline[i];
357+
overlays[i] = this[x + i, y, intersections, orientations];
347358
}
348359
}
349360

350361
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
351-
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
362+
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlays, amounts);
363+
}
364+
365+
protected override void Dispose(bool disposing)
366+
{
367+
if (this.isDisposed)
368+
{
369+
return;
370+
}
371+
372+
base.Dispose(disposing);
373+
374+
if (disposing)
375+
{
376+
foreach (ThreadContextData data in this.threadContextData.Values)
377+
{
378+
data.Dispose();
379+
}
380+
381+
this.threadContextData.Dispose();
382+
}
383+
384+
this.isDisposed = true;
352385
}
353386

354387
private (Edge edge, Intersection? info) FindIntersection(
@@ -410,6 +443,43 @@ private static bool FindPointOnTriangle(PointF v1, PointF v2, PointF v3, PointF
410443
v = ((d00 * d21) - (d01 * d20)) / denominator;
411444
return true;
412445
}
446+
447+
private sealed class ThreadContextData : IDisposable
448+
{
449+
private bool isDisposed;
450+
private readonly IMemoryOwner<float> amountBuffer;
451+
private readonly IMemoryOwner<TPixel> overlayBuffer;
452+
private readonly IMemoryOwner<PointF> intersectionsBuffer;
453+
private readonly IMemoryOwner<PointOrientation> orientationsBuffer;
454+
455+
public ThreadContextData(MemoryAllocator memoryAllocator, int scanlineLength, int maxIntersections)
456+
{
457+
this.amountBuffer = memoryAllocator.Allocate<float>(scanlineLength);
458+
this.overlayBuffer = memoryAllocator.Allocate<TPixel>(scanlineLength);
459+
this.intersectionsBuffer = memoryAllocator.Allocate<PointF>(maxIntersections);
460+
this.orientationsBuffer = memoryAllocator.Allocate<PointOrientation>(maxIntersections);
461+
}
462+
463+
public Span<float> AmountSpan => this.amountBuffer.Memory.Span;
464+
465+
public Span<TPixel> OverlaySpan => this.overlayBuffer.Memory.Span;
466+
467+
public Span<PointF> IntersectionsSpan => this.intersectionsBuffer.Memory.Span;
468+
469+
public Span<PointOrientation> OrientationsSpan => this.orientationsBuffer.Memory.Span;
470+
471+
public void Dispose()
472+
{
473+
if (!this.isDisposed)
474+
{
475+
this.isDisposed = true;
476+
this.amountBuffer.Dispose();
477+
this.overlayBuffer.Dispose();
478+
this.intersectionsBuffer.Dispose();
479+
this.orientationsBuffer.Dispose();
480+
}
481+
}
482+
}
413483
}
414484
}
415485
}

src/ImageSharp.Drawing/Processing/SolidBrush.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public BrushApplicator<TPixel> CreateApplicator<TPixel>(
3636
/// <summary>
3737
/// The solid brush applicator.
3838
/// </summary>
39+
/// <typeparam name="TPixel">The pixel format.</typeparam>
3940
private class SolidBrushApplicator<TPixel> : BrushApplicator<TPixel>
4041
where TPixel : unmanaged, IPixel<TPixel>
4142
{
@@ -97,14 +98,13 @@ public override void Apply(Span<float> scanline, int x, int y)
9798
}
9899

99100
Configuration configuration = this.Configuration;
100-
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
101-
102101
if (this.Options.BlendPercentage == 1f)
103102
{
104103
this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.Memory.Span, scanline);
105104
}
106105
else
107106
{
107+
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
108108
using IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length);
109109
Span<float> amountSpan = amountBuffer.Memory.Span;
110110

0 commit comments

Comments
 (0)