Skip to content

Commit 3e90cfc

Browse files
Fix chunking
1 parent 76af5f8 commit 3e90cfc

3 files changed

Lines changed: 45 additions & 63 deletions

File tree

src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ public sealed unsafe partial class WebGPUDrawingBackend : IDrawingBackend, IDisp
2323

2424
// A single flush can rerun the staged path a small number of times while the scratch
2525
// buffers converge on the capacity reported by the scheduling stages.
26-
private const int MaxDynamicGrowthAttempts = 3;
26+
// The prepare shader cancels early when any single buffer overflows, so each
27+
// retry only discovers one new overflow. 8 attempts covers all 7 bump buffers
28+
// plus the final successful run. Only needed on the first flush; subsequent
29+
// flushes reuse the persisted GPU-reported sizes and need zero retries.
30+
private const int MaxDynamicGrowthAttempts = 8;
2731

2832
private readonly DefaultDrawingBackend fallbackBackend;
2933
private static bool? isSupported;
@@ -268,24 +272,18 @@ public void FlushCompositions<TPixel>(
268272

269273
if (stagedScene.EncodedScene.FillCount == 0)
270274
{
271-
// Empty staged scenes still establish the flush-sized scratch-capacity baseline.
272-
this.bumpSizes = stagedScene.Config.BumpSizes;
273275
return;
274276
}
275277

276278
if (WebGPUSceneDispatch.TryRenderStagedScene(ref stagedScene, ref schedulingArena, out bool requiresGrowth, out WebGPUSceneBumpSizes grownBumpSizes, out error))
277279
{
278-
// Persist the last successful capacities so the next flush starts from the
279-
// budget that actually worked on this device.
280-
this.bumpSizes = stagedScene.Config.BumpSizes;
280+
this.bumpSizes = grownBumpSizes;
281281
return;
282282
}
283283

284284
this.TestingLastFlushUsedGPU = false;
285285
if (requiresGrowth)
286286
{
287-
// Scheduling reported that one or more bump allocators overflowed. Retry the
288-
// same flush with the larger capacities read back from the GPU.
289287
currentBumpSizes = grownBumpSizes;
290288
continue;
291289
}

src/ImageSharp.Drawing.WebGPU/WebGPUSceneConfig.cs

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -202,47 +202,7 @@ public static WebGPUSceneBumpSizes Initial()
202202
1U << 17);
203203

204204
/// <summary>
205-
/// Raises the current capacities to the scene-derived lower bounds for one encoded scene.
206-
/// </summary>
207-
/// <remarks>
208-
/// Several staged allocators already have reliable CPU-side counts before any GPU scheduling work starts.
209-
/// Using those counts as the first attempt avoids wasting retries on scenes that are obviously larger than
210-
/// the previous flush. The GPU bump allocators still provide the final correction for the dynamic cases.
211-
/// </remarks>
212-
/// <param name="scene">The encoded scene whose aggregate scheduling counts are already known on the CPU.</param>
213-
/// <returns>The current capacities raised to this scene's known lower bounds.</returns>
214-
public WebGPUSceneBumpSizes WithSceneLowerBounds(WebGPUEncodedScene scene)
215-
=> new(
216-
MaxCapacity(this.Lines, GetSceneLinesLowerBound(scene)),
217-
MaxCapacity(this.Binning, GetSceneBinningLowerBound(scene)),
218-
MaxCapacity(this.PathTiles, GetSceneTileLowerBound(scene)),
219-
MaxCapacity(this.SegCounts, GetSceneSegmentLowerBound(scene)),
220-
MaxCapacity(this.Segments, GetSceneSegmentLowerBound(scene)),
221-
this.BlendSpill,
222-
this.Ptcl);
223-
224-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
225-
private static uint GetSceneLinesLowerBound(WebGPUEncodedScene scene)
226-
=> AddSizingSlack(checked((uint)Math.Max(scene.LineCount, 1)));
227-
228-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
229-
private static uint GetSceneBinningLowerBound(WebGPUEncodedScene scene)
230-
=> AddSizingSlack(checked((uint)Math.Max(scene.TotalBinMembershipCount, scene.DrawTagCount)));
231-
232-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
233-
private static uint GetSceneTileLowerBound(WebGPUEncodedScene scene)
234-
=> AddSizingSlack(checked((uint)Math.Max(scene.TotalTileMembershipCount, scene.PathCount)));
235-
236-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
237-
private static uint GetSceneSegmentLowerBound(WebGPUEncodedScene scene)
238-
=> AddSizingSlack(checked((uint)scene.LineCount));
239-
240-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
241-
private static uint MaxCapacity(uint current, uint required)
242-
=> current >= required ? current : required;
243-
244-
/// <summary>
245-
/// Adds a small retry margin and aligns the result so scene-derived capacities do not sit exactly on the edge.
205+
/// Adds a small retry margin and aligns the result so capacities do not sit exactly on the edge.
246206
/// </summary>
247207
[MethodImpl(MethodImplOptions.AggressiveInlining)]
248208
private static uint AddSizingSlack(uint required)

src/ImageSharp.Drawing.WebGPU/WebGPUSceneDispatch.cs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,7 @@ public static bool TryCreateStagedScene<TPixel>(
170170
}
171171

172172
encodedScene = createdScene;
173-
WebGPUSceneBumpSizes sceneBumpSizes = bumpSizes.WithSceneLowerBounds(encodedScene);
174-
WebGPUSceneConfig config = WebGPUSceneConfig.Create(encodedScene, sceneBumpSizes);
173+
WebGPUSceneConfig config = WebGPUSceneConfig.Create(encodedScene, bumpSizes);
175174
uint baseColor = 0U;
176175
bool segmentChunkingRequired = false;
177176
if (!TryValidateBindingSizes(encodedScene, config, flushContext.DeviceState.MaxStorageBufferBindingSize, out bindingLimitFailure, out error))
@@ -967,6 +966,8 @@ public static unsafe bool TryRenderStagedScene(
967966
{
968967
requiresGrowth = true;
969968
grownBumpSizes = GrowBumpSizes(stagedScene.Config.BumpSizes, in bumpAllocators);
969+
WebGPUSceneBumpSizes cfg = stagedScene.Config.BumpSizes;
970+
System.IO.File.AppendAllText("bump_debug.log", $"[BUMP OVERFLOW] failed={bumpAllocators.Failed:X} | had: lines={cfg.Lines} bin={cfg.Binning} tiles={cfg.PathTiles} segc={cfg.SegCounts} segs={cfg.Segments} blend={cfg.BlendSpill} ptcl={cfg.Ptcl} | gpu: lines={bumpAllocators.Lines} bin={bumpAllocators.Binning} tiles={bumpAllocators.Tile} segc={bumpAllocators.SegCounts} segs={bumpAllocators.Segments} blend={bumpAllocators.BlendSpill} ptcl={bumpAllocators.Ptcl} | grown: lines={grownBumpSizes.Lines} bin={grownBumpSizes.Binning} tiles={grownBumpSizes.PathTiles} segc={grownBumpSizes.SegCounts} segs={grownBumpSizes.Segments} blend={grownBumpSizes.BlendSpill} ptcl={grownBumpSizes.Ptcl}\n");
970971
error = "The staged WebGPU scene needs larger scratch buffers and will be retried.";
971972
return false;
972973
}
@@ -978,7 +979,18 @@ public static unsafe bool TryRenderStagedScene(
978979
return false;
979980
}
980981

981-
// Scheduling passed. Submit the pre-recorded fine + copy. Fire-and-forget.
982+
// Scheduling passed. Persist the actual GPU usage so the next flush starts
983+
// from sizes that are known to work, eliminating retries for similar scenes.
984+
grownBumpSizes = new WebGPUSceneBumpSizes(
985+
Math.Max(bumpAllocators.Lines, stagedScene.Config.BumpSizes.Lines),
986+
Math.Max(bumpAllocators.Binning, stagedScene.Config.BumpSizes.Binning),
987+
Math.Max(bumpAllocators.Tile, stagedScene.Config.BumpSizes.PathTiles),
988+
Math.Max(bumpAllocators.SegCounts, stagedScene.Config.BumpSizes.SegCounts),
989+
Math.Max(bumpAllocators.Segments, stagedScene.Config.BumpSizes.Segments),
990+
Math.Max(bumpAllocators.BlendSpill, stagedScene.Config.BumpSizes.BlendSpill),
991+
Math.Max(bumpAllocators.Ptcl, stagedScene.Config.BumpSizes.Ptcl));
992+
993+
// Submit the pre-recorded fine + copy. Fire-and-forget.
982994
return WebGPUDrawingBackend.TrySubmit(flushContext);
983995
}
984996

@@ -1061,11 +1073,13 @@ private static unsafe bool TryRenderSegmentChunkedStagedScene(
10611073
WebGPUSceneConfig chunkConfig = WebGPUSceneConfig.Create(encodedScene, chunkBumpSize, chunkWindow);
10621074
if (!TryValidateBindingSizes(encodedScene, chunkConfig, maxStorageBufferBindingSize, out BindingLimitFailure bindingLimitFailure, out error))
10631075
{
1076+
System.IO.File.AppendAllText("bump_debug.log", $"[CHUNK VALIDATION FAIL] buffer={bindingLimitFailure.Buffer} required={bindingLimitFailure.RequiredBytes} limit={bindingLimitFailure.LimitBytes} chunkTileH={requestedTileHeight} totalTileH={totalTileHeight} tiles={chunkBumpSize.PathTiles} segc={chunkBumpSize.SegCounts} segs={chunkBumpSize.Segments} srcTiles={stagedScene.Config.BumpSizes.PathTiles} srcSegc={stagedScene.Config.BumpSizes.SegCounts}\n");
10641077
if (IsChunkableBindingFailure(bindingLimitFailure.Buffer))
10651078
{
10661079
uint smallerTileHeight = ShrinkChunkTileHeight(requestedTileHeight, remainingTileHeight, bindingLimitFailure);
10671080
if (smallerTileHeight >= requestedTileHeight)
10681081
{
1082+
System.IO.File.AppendAllText("bump_debug.log", $"[CHUNK SHRINK STUCK] smallerH={smallerTileHeight} requestedH={requestedTileHeight}\n");
10691083
return false;
10701084
}
10711085

@@ -1293,18 +1307,17 @@ private static WebGPUSceneChunkWindow CreateChunkWindow(uint tileYStart, uint re
12931307
private static WebGPUSceneBumpSizes ScaleChunkBumpSizes(WebGPUSceneBumpSizes sourceBumpSizes, WebGPUEncodedScene scene, uint chunkTileHeight)
12941308
{
12951309
uint fullTileHeight = checked((uint)scene.TileCountY);
1296-
uint chunkTileBufferHeight = AlignUp(chunkTileHeight, 16U);
1297-
uint pathTileFloor = AddSizingSlack(ScaleCount((uint)Math.Max(scene.TotalTileMembershipCount, scene.PathCount), chunkTileBufferHeight, fullTileHeight));
1298-
uint segmentFloor = AddSizingSlack(ScaleCount((uint)scene.LineCount, chunkTileBufferHeight, fullTileHeight));
1310+
uint pathTileFloor = AddSizingSlack(ScaleCount((uint)Math.Max(scene.TotalTileMembershipCount, scene.PathCount), chunkTileHeight, fullTileHeight));
1311+
uint segmentFloor = AddSizingSlack(ScaleCount((uint)scene.LineCount, chunkTileHeight, fullTileHeight));
12991312

13001313
return new WebGPUSceneBumpSizes(
13011314
sourceBumpSizes.Lines,
13021315
sourceBumpSizes.Binning,
1303-
Math.Max(ScaleCount(sourceBumpSizes.PathTiles, chunkTileBufferHeight, fullTileHeight), pathTileFloor),
1304-
Math.Max(ScaleCount(sourceBumpSizes.SegCounts, chunkTileBufferHeight, fullTileHeight), segmentFloor),
1305-
Math.Max(ScaleCount(sourceBumpSizes.Segments, chunkTileBufferHeight, fullTileHeight), segmentFloor),
1306-
ScaleCount(sourceBumpSizes.BlendSpill, chunkTileBufferHeight, fullTileHeight),
1307-
ScaleCount(sourceBumpSizes.Ptcl, chunkTileBufferHeight, fullTileHeight));
1316+
Math.Max(ScaleCount(sourceBumpSizes.PathTiles, chunkTileHeight, fullTileHeight), pathTileFloor),
1317+
Math.Max(ScaleCount(sourceBumpSizes.SegCounts, chunkTileHeight, fullTileHeight), segmentFloor),
1318+
Math.Max(ScaleCount(sourceBumpSizes.Segments, chunkTileHeight, fullTileHeight), segmentFloor),
1319+
ScaleCount(sourceBumpSizes.BlendSpill, chunkTileHeight, fullTileHeight),
1320+
ScaleCount(sourceBumpSizes.Ptcl, chunkTileHeight, fullTileHeight));
13081321
}
13091322

13101323
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1448,8 +1461,8 @@ private static uint GrowBumpSize(uint currentSize, uint requiredSize)
14481461
return currentSize;
14491462
}
14501463

1451-
uint nextSize = checked(requiredSize + Math.Max(requiredSize / 2U, 4096U));
1452-
return nextSize > currentSize ? nextSize : checked(currentSize + 1U);
1464+
// Use the GPU-reported size doubled so one retry is always enough.
1465+
return checked(requiredSize * 2U);
14531466
}
14541467

14551468
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1839,14 +1852,25 @@ void Callback(BufferMapAsyncStatus status, void* userData)
18391852
{
18401853
GpuSceneBumpAllocators bumpAllocators = statuses[i];
18411854
WebGPUSceneBumpSizes currentSizes = chunkBumpSizes[i];
1855+
WebGPUSceneBumpSizes chunkActuals = new(
1856+
Math.Max(bumpAllocators.Lines, currentSizes.Lines),
1857+
Math.Max(bumpAllocators.Binning, currentSizes.Binning),
1858+
Math.Max(bumpAllocators.Tile, currentSizes.PathTiles),
1859+
Math.Max(bumpAllocators.SegCounts, currentSizes.SegCounts),
1860+
Math.Max(bumpAllocators.Segments, currentSizes.Segments),
1861+
Math.Max(bumpAllocators.BlendSpill, currentSizes.BlendSpill),
1862+
Math.Max(bumpAllocators.Ptcl, currentSizes.Ptcl));
1863+
WebGPUSceneBumpSizes expandedActuals = ExpandChunkBumpSizesToSceneBudget(chunkActuals, fullTileHeight, chunkTileHeights[i]);
1864+
grownBumpSizes = MaxBumpSizes(grownBumpSizes, expandedActuals);
1865+
18421866
if (!RequiresScratchReallocation(in bumpAllocators, currentSizes))
18431867
{
18441868
continue;
18451869
}
18461870

18471871
WebGPUSceneBumpSizes grownChunkBumpSizes = GrowBumpSizes(currentSizes, in bumpAllocators);
18481872
WebGPUSceneBumpSizes grownSourceBumpSizes = ExpandChunkBumpSizesToSceneBudget(grownChunkBumpSizes, fullTileHeight, chunkTileHeights[i]);
1849-
grownBumpSizes = requiresGrowth ? MaxBumpSizes(grownBumpSizes, grownSourceBumpSizes) : grownSourceBumpSizes;
1873+
grownBumpSizes = MaxBumpSizes(grownBumpSizes, grownSourceBumpSizes);
18501874
requiresGrowth = true;
18511875
}
18521876

0 commit comments

Comments
 (0)