Skip to content

Commit 8e80543

Browse files
Make ToLinearGeometry() cache threadsafe.
1 parent 951ef8c commit 8e80543

File tree

9 files changed

+119
-91
lines changed

9 files changed

+119
-91
lines changed

src/ImageSharp.Drawing.WebGPU/WebGPUSceneEncoder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ private void AppendPlainFill(in ResolvedPathCommand command)
472472
int pathDataCheckpoint = this.PathData.Count;
473473
int styleCheckpoint = this.Styles.Count;
474474
bool appendStyle = !this.hasLastStyle || style0 != this.lastStyle0 || style1 != this.lastStyle1 || style2 != this.lastStyle2;
475-
LinearGeometry geometry = command.Path.ToLinearGeometry();
475+
LinearGeometry geometry = command.Path.ToLinearGeometry(Matrix4x4.Identity);
476476

477477
// Reserve the exact words/tags this item can append before encoding so the
478478
// subsequent Add calls stay on the already-allocated contiguous spans.
@@ -531,7 +531,7 @@ private void AppendPlainFill(in ResolvedPathCommand command)
531531
/// </summary>
532532
private void AppendPlainStroke(in ResolvedPathCommand command, Pen pen)
533533
{
534-
LinearGeometry geometry = command.Path.ToLinearGeometry();
534+
LinearGeometry geometry = command.Path.ToLinearGeometry(Matrix4x4.Identity);
535535
uint drawTag = GetDrawTag(command.Brush);
536536
GpuSceneDrawMonoid drawTagMonoid = GpuSceneDrawTag.Map(drawTag);
537537
(uint style0, uint style1, uint style2) = GetStrokeStyle(command.GraphicsOptions, pen);

src/ImageSharp.Drawing/ComplexPolygon.cs

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

44
using System.Diagnostics.CodeAnalysis;
55
using System.Numerics;
6+
using System.Threading;
67

78
namespace SixLabors.ImageSharp.Drawing;
89

@@ -99,14 +100,33 @@ public IEnumerable<ISimplePath> Flatten()
99100
return paths;
100101
}
101102

102-
/// <inheritdoc />
103-
public LinearGeometry ToLinearGeometry()
103+
/// <inheritdoc/>
104+
public LinearGeometry ToLinearGeometry(Matrix4x4 transform)
105+
=> transform.IsIdentity ? this.GetLinearGeometryCore() : this.CreateTransformedLinearGeometryCore(transform);
106+
107+
/// <summary>
108+
/// Returns the retained identity geometry, publishing it once for concurrent readers.
109+
/// </summary>
110+
/// <returns>The retained identity geometry.</returns>
111+
private LinearGeometry GetLinearGeometryCore()
104112
{
105-
if (this.linearGeometry is not null)
113+
LinearGeometry? cached = Volatile.Read(ref this.linearGeometry);
114+
if (cached is not null)
106115
{
107-
return this.linearGeometry;
116+
return cached;
108117
}
109118

119+
LinearGeometry geometry = this.CreateLinearGeometryCore();
120+
LinearGeometry? published = Interlocked.CompareExchange(ref this.linearGeometry, geometry, null);
121+
return published ?? geometry;
122+
}
123+
124+
/// <summary>
125+
/// Materializes the retained identity geometry for the composed contours.
126+
/// </summary>
127+
/// <returns>The retained identity geometry.</returns>
128+
private LinearGeometry CreateLinearGeometryCore()
129+
{
110130
int pointCount = 0;
111131
int contourCount = 0;
112132
int segmentCount = 0;
@@ -121,7 +141,7 @@ public LinearGeometry ToLinearGeometry()
121141

122142
foreach (IPath path in this.paths)
123143
{
124-
LinearGeometry geometry = path.ToLinearGeometry();
144+
LinearGeometry geometry = path.ToLinearGeometry(Matrix4x4.Identity);
125145

126146
if (geometry.Info.PointCount == 0)
127147
{
@@ -150,7 +170,7 @@ public LinearGeometry ToLinearGeometry()
150170

151171
foreach (IPath path in this.paths)
152172
{
153-
LinearGeometry geometry = path.ToLinearGeometry();
173+
LinearGeometry geometry = path.ToLinearGeometry(Matrix4x4.Identity);
154174
if (geometry.Info.PointCount == 0)
155175
{
156176
continue;
@@ -181,7 +201,7 @@ public LinearGeometry ToLinearGeometry()
181201

182202
RectangleF bounds = hasBounds ? RectangleF.FromLTRB(minX, minY, maxX, maxY) : RectangleF.Empty;
183203

184-
this.linearGeometry = new LinearGeometry(
204+
return new LinearGeometry(
185205
new LinearGeometryInfo
186206
{
187207
Bounds = bounds,
@@ -193,18 +213,15 @@ public LinearGeometry ToLinearGeometry()
193213
},
194214
contours,
195215
points);
196-
197-
return this.linearGeometry;
198216
}
199217

200-
/// <inheritdoc/>
201-
public LinearGeometry ToLinearGeometry(Matrix4x4 transform)
218+
/// <summary>
219+
/// Materializes transformed linear geometry without caching the transformed result.
220+
/// </summary>
221+
/// <param name="transform">The transform to apply to each emitted point.</param>
222+
/// <returns>The transformed retained geometry.</returns>
223+
private LinearGeometry CreateTransformedLinearGeometryCore(Matrix4x4 transform)
202224
{
203-
if (transform.IsIdentity)
204-
{
205-
return this.ToLinearGeometry();
206-
}
207-
208225
int pointCount = 0;
209226
int contourCount = 0;
210227
int segmentCount = 0;

src/ImageSharp.Drawing/EmptyPath.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ public sealed class EmptyPath : IPath
4747
/// <inheritdoc />
4848
public IEnumerable<ISimplePath> Flatten() => [];
4949

50-
/// <inheritdoc />
51-
public LinearGeometry ToLinearGeometry() => EmptyGeometry;
52-
5350
/// <inheritdoc />
5451
public LinearGeometry ToLinearGeometry(Matrix4x4 transform) => EmptyGeometry;
5552

src/ImageSharp.Drawing/IPath.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ public interface IPath
2727
public IEnumerable<ISimplePath> Flatten();
2828

2929
/// <summary>
30-
/// Converts this path into retained linear geometry for backend consumption.
30+
/// Converts this path to a retained linear-geometry representation, applying the specified
31+
/// projective transform to each point during flattening.
3132
/// </summary>
33+
/// <param name="transform">The transform to apply to each point.</param>
3234
/// <remarks>
3335
/// <para>
3436
/// The returned <see cref="LinearGeometry"/> is the canonical lowered representation for backend scene building.
@@ -45,14 +47,6 @@ public interface IPath
4547
/// contract.
4648
/// </para>
4749
/// </remarks>
48-
/// <returns>The retained linear geometry for this path.</returns>
49-
public LinearGeometry ToLinearGeometry();
50-
51-
/// <summary>
52-
/// Converts this path to a retained linear-geometry representation, applying the specified
53-
/// projective transform to each point during flattening.
54-
/// </summary>
55-
/// <param name="transform">The transform to apply to each point.</param>
5650
/// <returns>The retained linear geometry with transformed points.</returns>
5751
public LinearGeometry ToLinearGeometry(Matrix4x4 transform);
5852

src/ImageSharp.Drawing/Path.cs

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics.CodeAnalysis;
55
using System.Globalization;
66
using System.Numerics;
7+
using System.Threading;
78

89
namespace SixLabors.ImageSharp.Drawing;
910

@@ -130,17 +131,36 @@ public IEnumerable<ISimplePath> Flatten()
130131
yield return this;
131132
}
132133

133-
/// <inheritdoc />
134-
public virtual LinearGeometry ToLinearGeometry()
134+
/// <inheritdoc/>
135+
public virtual LinearGeometry ToLinearGeometry(Matrix4x4 transform)
136+
=> transform.IsIdentity ? this.GetLinearGeometryCore() : this.CreateTransformedLinearGeometryCore(transform);
137+
138+
/// <summary>
139+
/// Returns the retained identity geometry, publishing it once for concurrent readers.
140+
/// </summary>
141+
/// <returns>The retained identity geometry.</returns>
142+
private LinearGeometry GetLinearGeometryCore()
135143
{
136-
if (this.linearGeometry is not null)
144+
LinearGeometry? cached = Volatile.Read(ref this.linearGeometry);
145+
if (cached is not null)
137146
{
138-
return this.linearGeometry;
147+
return cached;
139148
}
140149

150+
LinearGeometry geometry = this.CreateLinearGeometryCore();
151+
LinearGeometry? published = Interlocked.CompareExchange(ref this.linearGeometry, geometry, null);
152+
return published ?? geometry;
153+
}
154+
155+
/// <summary>
156+
/// Materializes the retained identity geometry for this path.
157+
/// </summary>
158+
/// <returns>The retained identity geometry.</returns>
159+
private LinearGeometry CreateLinearGeometryCore()
160+
{
141161
if (this.lineSegments.Length == 0)
142162
{
143-
this.linearGeometry = new LinearGeometry(
163+
return new LinearGeometry(
144164
new LinearGeometryInfo
145165
{
146166
Bounds = RectangleF.Empty,
@@ -152,8 +172,6 @@ public virtual LinearGeometry ToLinearGeometry()
152172
},
153173
[],
154174
[]);
155-
156-
return this.linearGeometry;
157175
}
158176

159177
PointF? lastEndPoint = null;
@@ -220,7 +238,7 @@ public virtual LinearGeometry ToLinearGeometry()
220238

221239
RectangleF bounds = hasBounds ? RectangleF.FromLTRB(minX, minY, maxX, maxY) : RectangleF.Empty;
222240

223-
this.linearGeometry = new LinearGeometry(
241+
return new LinearGeometry(
224242
new LinearGeometryInfo
225243
{
226244
Bounds = bounds,
@@ -232,34 +250,15 @@ public virtual LinearGeometry ToLinearGeometry()
232250
},
233251
contours,
234252
points);
235-
236-
return this.linearGeometry;
237253
}
238254

239-
/// <inheritdoc/>
240-
public virtual LinearGeometry ToLinearGeometry(Matrix4x4 transform)
255+
/// <summary>
256+
/// Materializes transformed linear geometry without caching the transformed result.
257+
/// </summary>
258+
/// <param name="transform">The transform to apply to each emitted point.</param>
259+
/// <returns>The transformed retained geometry.</returns>
260+
private LinearGeometry CreateTransformedLinearGeometryCore(Matrix4x4 transform)
241261
{
242-
if (transform.IsIdentity)
243-
{
244-
return this.ToLinearGeometry();
245-
}
246-
247-
if (this.lineSegments.Length == 0)
248-
{
249-
return new LinearGeometry(
250-
new LinearGeometryInfo
251-
{
252-
Bounds = RectangleF.Empty,
253-
ContourCount = 0,
254-
PointCount = 0,
255-
SegmentCount = 0,
256-
NonHorizontalSegmentCountPixelBoundary = 0,
257-
NonHorizontalSegmentCountPixelCenter = 0
258-
},
259-
[],
260-
[]);
261-
}
262-
263262
PointF? lastEndPoint = null;
264263
int pointCount = 0;
265264

src/ImageSharp.Drawing/Processing/Backends/DefaultRasterizer.Stroke.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal static partial class DefaultRasterizer
1717
/// <summary>
1818
/// Creates retained row-local raster payload for one stroked centerline geometry.
1919
/// </summary>
20-
/// <param name="path">The source stroke path.</param>
20+
/// <param name="geometry">The source stroke centerline geometry.</param>
2121
/// <param name="pen">The stroke metadata.</param>
2222
/// <param name="translateX">The destination-space X translation applied at composition time.</param>
2323
/// <param name="translateY">The destination-space Y translation applied at composition time.</param>
@@ -26,7 +26,7 @@ internal static partial class DefaultRasterizer
2626
/// <param name="allocator">The allocator used for retained raster storage.</param>
2727
/// <returns>The retained rasterizable geometry for the stroke, or <see langword="null"/> when the stroke produces no coverage.</returns>
2828
internal static StrokeRasterizableGeometry? CreatePathStrokeRasterizableGeometry(
29-
IPath path,
29+
LinearGeometry geometry,
3030
Pen pen,
3131
int translateX,
3232
int translateY,
@@ -39,7 +39,6 @@ internal static partial class DefaultRasterizer
3939
return null;
4040
}
4141

42-
LinearGeometry geometry = path.ToLinearGeometry();
4342
return CreateRetainedStrokeRasterizableGeometry(
4443
geometry,
4544
new StrokeStyle(pen),
@@ -1913,4 +1912,3 @@ public StrokeStyle(Pen pen)
19131912
}
19141913

19151914
#pragma warning restore SA1201 // Elements should appear in the correct order
1916-

src/ImageSharp.Drawing/Processing/Backends/FlushScene.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Numerics;
45
using System.Runtime.CompilerServices;
56
using SixLabors.ImageSharp.Memory;
67

@@ -732,12 +733,14 @@ private static bool TryPrepareFillPath(
732733
out PreparedFillItem prepared)
733734
{
734735
IPath path = command.SourcePath;
735-
bool hasTransform = !command.Transform.IsIdentity;
736-
Brush sourceBrush = hasTransform ? command.Brush.Transform(command.Transform) : command.Brush;
736+
Matrix4x4 transform = command.Transform;
737+
bool hasTransform = !transform.IsIdentity;
738+
LinearGeometry geometry = path.ToLinearGeometry(transform);
739+
Brush sourceBrush = hasTransform ? command.Brush.Transform(transform) : command.Brush;
737740

738741
if (!TryResolveRasterization(
739742
sourceBrush,
740-
hasTransform ? RectangleF.Transform(path.Bounds, command.Transform) : path.Bounds,
743+
geometry.Info.Bounds,
741744
command.RasterizerOptions,
742745
command.DestinationOffset,
743746
command.TargetBounds,
@@ -750,7 +753,7 @@ private static bool TryPrepareFillPath(
750753
}
751754

752755
DefaultRasterizer.RasterizableGeometry? rasterizable = DefaultRasterizer.CreateRasterizableGeometry(
753-
hasTransform ? path.ToLinearGeometry(command.Transform) : path.ToLinearGeometry(),
756+
geometry,
754757
command.DestinationOffset.X,
755758
command.DestinationOffset.Y,
756759
rasterizerOptions,
@@ -772,12 +775,15 @@ private static bool TryPrepareStrokePath(
772775
out PreparedStrokeItem prepared)
773776
{
774777
IPath path = command.SourcePath;
775-
bool hasTransform = !command.Transform.IsIdentity;
776-
Brush sourceBrush = hasTransform ? command.Brush.Transform(command.Transform) : command.Brush;
778+
Matrix4x4 transform = command.Transform;
779+
bool hasTransform = !transform.IsIdentity;
780+
LinearGeometry geometry = path.ToLinearGeometry(Matrix4x4.Identity);
781+
RectangleF strokeBounds = GetStrokeBounds(geometry.Info.Bounds, command.Pen);
782+
Brush sourceBrush = hasTransform ? command.Brush.Transform(transform) : command.Brush;
777783

778784
if (!TryResolveRasterization(
779785
sourceBrush,
780-
hasTransform ? RectangleF.Transform(GetStrokeBounds(path.Bounds, command.Pen), command.Transform) : GetStrokeBounds(path.Bounds, command.Pen),
786+
hasTransform ? RectangleF.Transform(strokeBounds, transform) : strokeBounds,
781787
command.RasterizerOptions,
782788
command.DestinationOffset,
783789
command.TargetBounds,
@@ -792,12 +798,12 @@ private static bool TryPrepareStrokePath(
792798
// Retained-scene preparation preserves the stroke centerline and forwards the drawing
793799
// transform into the CPU stroke rasterizer so expansion still happens before transform.
794800
DefaultRasterizer.StrokeRasterizableGeometry? rasterizable = DefaultRasterizer.CreatePathStrokeRasterizableGeometry(
795-
path,
801+
geometry,
796802
command.Pen,
797803
command.DestinationOffset.X,
798804
command.DestinationOffset.Y,
799805
rasterizerOptions,
800-
command.Transform,
806+
transform,
801807
allocator);
802808
if (rasterizable is null)
803809
{

0 commit comments

Comments
 (0)