Skip to content

Commit 5a1c4ed

Browse files
Refactor CPU to actually follow architecture
1 parent 4117466 commit 5a1c4ed

43 files changed

Lines changed: 6127 additions & 2062 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,7 @@ artifacts/
227227

228228
/tests/CodeCoverage/OpenCover.*
229229
SixLabors.Shapes.Coverage.xml
230-
/tests/SixLabors.Shapes.Benchmarks/BenchmarkDotNet.Artifacts/results/
230+
/tests/SixLabors.Shapes.Benchmarks/BenchmarkDotNet.Artifacts/results/
231+
.dotnet
232+
.codex-*
233+
.claude

ImageSharp.Drawing.sln

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Drawing.WebGPU",
341341
EndProject
342342
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebGPUWindowDemo", "samples\WebGPUWindowDemo\WebGPUWindowDemo.csproj", "{2541FDCD-78AC-40DB-B5E3-6A715DC132BA}"
343343
EndProject
344+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp", "..\ImageSharp\src\ImageSharp\ImageSharp.csproj", "{FE71764E-E7BD-B378-8A4C-86929A28AD25}"
345+
EndProject
344346
Global
345347
GlobalSection(SolutionConfigurationPlatforms) = preSolution
346348
Debug|Any CPU = Debug|Any CPU
@@ -423,6 +425,18 @@ Global
423425
{2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|x64.Build.0 = Release|Any CPU
424426
{2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|x86.ActiveCfg = Release|Any CPU
425427
{2541FDCD-78AC-40DB-B5E3-6A715DC132BA}.Release|x86.Build.0 = Release|Any CPU
428+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
429+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|Any CPU.Build.0 = Debug|Any CPU
430+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x64.ActiveCfg = Debug|Any CPU
431+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x64.Build.0 = Debug|Any CPU
432+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x86.ActiveCfg = Debug|Any CPU
433+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x86.Build.0 = Debug|Any CPU
434+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|Any CPU.ActiveCfg = Release|Any CPU
435+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|Any CPU.Build.0 = Release|Any CPU
436+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x64.ActiveCfg = Release|Any CPU
437+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x64.Build.0 = Release|Any CPU
438+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x86.ActiveCfg = Release|Any CPU
439+
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x86.Build.0 = Release|Any CPU
426440
EndGlobalSection
427441
GlobalSection(SolutionProperties) = preSolution
428442
HideSolutionNode = FALSE
@@ -452,6 +466,7 @@ Global
452466
{23859314-5693-4E6C-BE5C-80A433439D2A} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}
453467
{061582C2-658F-40AE-A978-7D74A4EB2C0A} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
454468
{2541FDCD-78AC-40DB-B5E3-6A715DC132BA} = {528610AC-7C0C-46E8-9A2D-D46FD92FEE29}
469+
{FE71764E-E7BD-B378-8A4C-86929A28AD25} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
455470
EndGlobalSection
456471
GlobalSection(ExtensibilityGlobals) = postSolution
457472
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
@@ -460,6 +475,7 @@ Global
460475
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{061582c2-658f-40ae-a978-7d74a4eb2c0a}*SharedItemsImports = 5
461476
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2e33181e-6e28-4662-a801-e2e7dc206029}*SharedItemsImports = 5
462477
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13
478+
..\ImageSharp\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{fe71764e-e7bd-b378-8a4c-86929a28ad25}*SharedItemsImports = 5
463479
EndGlobalSection
464480
GlobalSection(Performance) = preSolution
465481
HasPerformanceSessions = true

src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.CoverageRasterizer.cs

Lines changed: 76 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -110,37 +110,16 @@ private bool TryCreateEdgeBuffer<TPixel>(
110110

111111
int bandCount = (int)DivideRoundUp(interest.Height, TileHeight);
112112

113-
IMemoryOwner<GpuEdge>? defEdgeOwner;
114-
int edgeCount;
115-
IMemoryOwner<uint>? defBandOffsets;
116-
bool edgeSuccess;
117-
if (definition.IsStroke)
118-
{
119-
edgeSuccess = TryBuildStrokeEdges(
120-
flushContext.MemoryAllocator,
121-
definition.Path,
122-
in interest,
123-
definition.RasterizerOptions.SamplingOrigin,
124-
definition.StrokeWidth,
125-
(float)(definition.StrokeOptions?.MiterLimit ?? 4.0),
126-
bandCount,
127-
out defEdgeOwner,
128-
out edgeCount,
129-
out defBandOffsets,
130-
out error);
131-
}
132-
else
133-
{
134-
edgeSuccess = TryBuildFixedPointEdges(
135-
flushContext.MemoryAllocator,
136-
definition.Path,
137-
in interest,
138-
definition.RasterizerOptions.SamplingOrigin,
139-
out defEdgeOwner,
140-
out edgeCount,
141-
out defBandOffsets,
142-
out error);
143-
}
113+
// All commands are fills by this point — strokes were expanded by the batcher.
114+
bool edgeSuccess = TryBuildFixedPointEdges(
115+
flushContext.MemoryAllocator,
116+
definition.Geometry,
117+
in interest,
118+
definition.RasterizerOptions.SamplingOrigin,
119+
out IMemoryOwner<GpuEdge>? defEdgeOwner,
120+
out int edgeCount,
121+
out IMemoryOwner<uint>? defBandOffsets,
122+
out error);
144123

145124
if (!edgeSuccess)
146125
{
@@ -154,70 +133,16 @@ private bool TryCreateEdgeBuffer<TPixel>(
154133

155134
geometries[i] = new DefinitionGeometry(defEdgeOwner, edgeCount, bandCount, defBandOffsets);
156135

157-
if (definition.IsStroke && edgeCount > 0)
158-
{
159-
// Centerline edges are band-sorted. Create one StrokeExpandCommand per band
160-
// so the GPU expand shader writes outline edges into per-band output slots.
161-
// This produces band-sorted outline edges compatible with the fill rasterizer.
162-
Span<uint> clBandOffsets = defBandOffsets!.Memory.Span;
163-
LineCap defLineCap = definition.StrokeOptions?.LineCap ?? LineCap.Butt;
164-
LineJoin defLineJoin = definition.StrokeOptions?.LineJoin ?? LineJoin.Bevel;
165-
int outlineEdgesPerCenterline = ComputeOutlineEdgesPerCenterline(
166-
definition.StrokeWidth * 0.5f, defLineJoin, defLineCap);
167-
strokeCommands ??= [];
168-
for (int b = 0; b < bandCount; b++)
169-
{
170-
uint bandStart = clBandOffsets[b];
171-
uint bandEnd = b + 1 < clBandOffsets.Length ? clBandOffsets[b + 1] : (uint)edgeCount;
172-
uint bandEdgeCount = bandEnd - bandStart;
173-
if (bandEdgeCount == 0)
174-
{
175-
continue;
176-
}
177-
178-
int bandOutlineMax = (int)bandEdgeCount * outlineEdgesPerCenterline;
179-
strokeCommands.Add(new StrokeExpandCommand(
180-
i,
181-
(uint)runningEdgeStart + bandStart,
182-
bandEdgeCount,
183-
definition.StrokeWidth * 0.5f,
184-
(uint)defLineCap,
185-
(uint)defLineJoin,
186-
(float)(definition.StrokeOptions?.MiterLimit ?? 4.0),
187-
bandOutlineMax,
188-
Band: b));
189-
190-
totalOutlineSlots += bandOutlineMax;
191-
}
192-
193-
totalStrokeCenterlineEdges += edgeCount;
194-
195-
// Placeholder EdgePlacement — will be updated after outline space is allocated.
196-
int bandOffsetEntriesForDef = bandCount + 1;
197-
edgePlacementsSpan[i] = new EdgePlacement(
198-
(uint)runningEdgeStart,
199-
(uint)edgeCount,
200-
fillRule,
201-
(uint)runningBandOffset,
202-
(uint)bandCount);
203-
204-
runningEdgeStart += edgeCount;
205-
runningBandOffset += bandOffsetEntriesForDef;
206-
}
207-
else
208-
{
209-
int bandOffsetEntriesForDef = bandCount + 1;
210-
211-
edgePlacementsSpan[i] = new EdgePlacement(
212-
(uint)runningEdgeStart,
213-
(uint)edgeCount,
214-
fillRule,
215-
(uint)runningBandOffset,
216-
(uint)bandCount);
136+
int bandOffsetEntriesForDef = bandCount + 1;
137+
edgePlacementsSpan[i] = new EdgePlacement(
138+
(uint)runningEdgeStart,
139+
(uint)edgeCount,
140+
fillRule,
141+
(uint)runningBandOffset,
142+
(uint)bandCount);
217143

218-
runningEdgeStart += edgeCount;
219-
runningBandOffset += bandOffsetEntriesForDef;
220-
}
144+
runningEdgeStart += edgeCount;
145+
runningBandOffset += bandOffsetEntriesForDef;
221146
}
222147

223148
totalEdgeCount = runningEdgeStart;
@@ -597,7 +522,7 @@ private void DisposeCoverageResources()
597522
/// </remarks>
598523
private static bool TryBuildFixedPointEdges(
599524
MemoryAllocator allocator,
600-
IPath path,
525+
PreparedGeometry geometry,
601526
in Rectangle interest,
602527
RasterizerSamplingOrigin samplingOrigin,
603528
out IMemoryOwner<GpuEdge>? edgeOwner,
@@ -616,55 +541,44 @@ private static bool TryBuildFixedPointEdges(
616541
int interestX = interest.X;
617542
int interestY = interest.Y;
618543
int bandCount = (int)DivideRoundUp(height, TileHeight);
619-
620-
// Flatten once and reuse the list for both passes.
621-
IEnumerable<ISimplePath> flattened = path.Flatten();
622-
IReadOnlyList<ISimplePath> contours = flattened is IReadOnlyList<ISimplePath> list
623-
? list
624-
: [.. flattened];
544+
ReadOnlySpan<PreparedLineSegment> segments = geometry.Segments;
625545

626546
// Pass 1: Count edges per band.
627547
int totalSubEdges = 0;
628548
using IMemoryOwner<int> bandCountsOwner = allocator.Allocate<int>(bandCount, AllocationOptions.Clean);
629549
Span<int> bandCounts = bandCountsOwner.Memory.Span;
630550

631-
for (int c = 0; c < contours.Count; c++)
551+
for (int i = 0; i < segments.Length; i++)
632552
{
633-
ISimplePath simplePath = contours[c];
634-
ReadOnlySpan<PointF> points = simplePath.Points.Span;
635-
int segmentCount = simplePath.IsClosed ? points.Length : points.Length - 1;
553+
PreparedLineSegment segment = segments[i];
554+
PointF p0 = segment.P0;
555+
PointF p1 = segment.P1;
636556

637-
for (int j = 0; j < segmentCount; j++)
557+
int y0 = (int)MathF.Round(((p0.Y - interestY) + samplingOffsetY) * FixedOne);
558+
int y1 = (int)MathF.Round(((p1.Y - interestY) + samplingOffsetY) * FixedOne);
559+
if (y0 == y1)
638560
{
639-
PointF p0 = points[j];
640-
PointF p1 = points[j + 1 == points.Length ? 0 : j + 1];
641-
642-
int y0 = (int)MathF.Round(((p0.Y - interestY) + samplingOffsetY) * FixedOne);
643-
int y1 = (int)MathF.Round(((p1.Y - interestY) + samplingOffsetY) * FixedOne);
644-
if (y0 == y1)
645-
{
646-
continue;
647-
}
561+
continue;
562+
}
648563

649-
int yMinFixed = Math.Min(y0, y1);
650-
int yMaxFixed = Math.Max(y0, y1);
651-
int minRow = Math.Max(0, yMinFixed >> FixedShift);
652-
int maxRow = Math.Min(height - 1, (yMaxFixed - 1) >> FixedShift);
564+
int yMinFixed = Math.Min(y0, y1);
565+
int yMaxFixed = Math.Max(y0, y1);
566+
int minRow = Math.Max(0, yMinFixed >> FixedShift);
567+
int maxRow = Math.Min(height - 1, (yMaxFixed - 1) >> FixedShift);
653568

654-
if (minRow > maxRow)
655-
{
656-
continue;
657-
}
658-
659-
int minBand = minRow / TileHeight;
660-
int maxBand = maxRow / TileHeight;
661-
for (int b = minBand; b <= maxBand; b++)
662-
{
663-
bandCounts[b]++;
664-
}
569+
if (minRow > maxRow)
570+
{
571+
continue;
572+
}
665573

666-
totalSubEdges += maxBand - minBand + 1;
574+
int minBand = minRow / TileHeight;
575+
int maxBand = maxRow / TileHeight;
576+
for (int b = minBand; b <= maxBand; b++)
577+
{
578+
bandCounts[b]++;
667579
}
580+
581+
totalSubEdges += maxBand - minBand + 1;
668582
}
669583

670584
if (totalSubEdges == 0)
@@ -690,47 +604,42 @@ private static bool TryBuildFixedPointEdges(
690604
using IMemoryOwner<uint> writeCursorsOwner = allocator.Allocate<uint>(bandCount, AllocationOptions.Clean);
691605
Span<uint> writeCursors = writeCursorsOwner.Memory.Span;
692606

693-
for (int c = 0; c < contours.Count; c++)
607+
for (int i = 0; i < segments.Length; i++)
694608
{
695-
ISimplePath simplePath = contours[c];
696-
ReadOnlySpan<PointF> points = simplePath.Points.Span;
697-
int segmentCount = simplePath.IsClosed ? points.Length : points.Length - 1;
698-
for (int j = 0; j < segmentCount; j++)
609+
PreparedLineSegment segment = segments[i];
610+
PointF p0 = segment.P0;
611+
PointF p1 = segment.P1;
612+
float fx0 = (p0.X - interestX) + samplingOffsetX;
613+
float fy0 = (p0.Y - interestY) + samplingOffsetY;
614+
float fx1 = (p1.X - interestX) + samplingOffsetX;
615+
float fy1 = (p1.Y - interestY) + samplingOffsetY;
616+
617+
int x0 = (int)MathF.Round(fx0 * FixedOne);
618+
int y0 = (int)MathF.Round(fy0 * FixedOne);
619+
int x1 = (int)MathF.Round(fx1 * FixedOne);
620+
int y1 = (int)MathF.Round(fy1 * FixedOne);
621+
if (y0 == y1)
699622
{
700-
PointF p0 = points[j];
701-
PointF p1 = points[j + 1 == points.Length ? 0 : j + 1];
702-
float fx0 = (p0.X - interestX) + samplingOffsetX;
703-
float fy0 = (p0.Y - interestY) + samplingOffsetY;
704-
float fx1 = (p1.X - interestX) + samplingOffsetX;
705-
float fy1 = (p1.Y - interestY) + samplingOffsetY;
706-
707-
int x0 = (int)MathF.Round(fx0 * FixedOne);
708-
int y0 = (int)MathF.Round(fy0 * FixedOne);
709-
int x1 = (int)MathF.Round(fx1 * FixedOne);
710-
int y1 = (int)MathF.Round(fy1 * FixedOne);
711-
if (y0 == y1)
712-
{
713-
continue;
714-
}
623+
continue;
624+
}
715625

716-
int yMinFixed = Math.Min(y0, y1);
717-
int yMaxFixed = Math.Max(y0, y1);
718-
int minRow = Math.Max(0, yMinFixed >> FixedShift);
719-
int maxRow = Math.Min(height - 1, (yMaxFixed - 1) >> FixedShift);
626+
int yMinFixed = Math.Min(y0, y1);
627+
int yMaxFixed = Math.Max(y0, y1);
628+
int minRow = Math.Max(0, yMinFixed >> FixedShift);
629+
int maxRow = Math.Min(height - 1, (yMaxFixed - 1) >> FixedShift);
720630

721-
if (minRow > maxRow)
722-
{
723-
continue;
724-
}
631+
if (minRow > maxRow)
632+
{
633+
continue;
634+
}
725635

726-
GpuEdge edge = new() { X0 = x0, Y0 = y0, X1 = x1, Y1 = y1 };
727-
int minBand = minRow / TileHeight;
728-
int maxBand = maxRow / TileHeight;
729-
for (int band = minBand; band <= maxBand; band++)
730-
{
731-
finalSpan[(int)(offsets[band] + writeCursors[band])] = edge;
732-
writeCursors[band]++;
733-
}
636+
GpuEdge edge = new() { X0 = x0, Y0 = y0, X1 = x1, Y1 = y1 };
637+
int minBand = minRow / TileHeight;
638+
int maxBand = maxRow / TileHeight;
639+
for (int band = minBand; band <= maxBand; band++)
640+
{
641+
finalSpan[(int)(offsets[band] + writeCursors[band])] = edge;
642+
writeCursors[band]++;
734643
}
735644
}
736645

0 commit comments

Comments
 (0)