WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
321876
GPU hangs unrecoverably when a render pass issues repeated indirect draws through `clampIndirectBufferToValidValues`, survives process teardown, requires reboot
https://bugs.webkit.org/show_bug.cgi?id=321876
Summary
GPU hangs unrecoverably when a render pass issues repeated indirect draws thr...
juretriglav
Reported
2026-08-16 11:17:48 PDT
Created
attachment 481065
[details]
Crash repro ## Summary A render pass that issues **repeated indirect draws whose pipeline declares at least one required vertex buffer layout** drives the AGX GPU into a state it never leaves. `Device Utilization %` pins at 100, `TiledSceneBytes` collapses to ~0 and never restarts, and `recoveryCount` never increments, the driver does not even attempt a reset. Firmware reports signature 544 ("progress timeout", TILER slots busy). Chrome/Dawn runs every arm of the reproduction at full frame rate. The common factor is WebKit's indirect-argument clamping path. It is entered whenever `computeMininumVertexInstanceCount` can lower either minimum, i.e. whenever the pipeline declares a required vertex layout (positive stride, ≥1 attribute, zero-attribute and undefined-stride layouts are dropped at `RenderPipeline.mm:648`). When neither minimum is lowered, `clampIndirectBufferTo‑ ValidValues` returns the application's own buffer and offset (`RenderPassEncoder.mm:948`) and the hang does not occur. ## Environment - macOS 26.5 (25F70), Apple M3 Max - WebKit trunk @ `87301340cf` (2026-08-13), MiniBrowser; also reproduces on shipping Safari 26.3 ## Reproduction **THE ATTACHED REPRODUCTION IS DESTRUCTIVE.** Attachment: `indirect-clamp-hang-repro.html`, self-contained, no imports or assets. One render pass, two `drawIndirect` calls from one arguments buffer, 490k + 120 instances of small additive quads. Load and wait ~20 s. **Reboot before retrying.** | URL | vertex layouts declared | result | | --- | --- | --- | | (default) instance-rate vertex buffer | 1 | **hangs, 2 of 3 runs** | | `?source=storage`, per-instance data pulled from a storage buffer by `instance_index` | **0** | clean 2/2 | | `?source=storage&dummyvb=1`, same, **plus a 256-byte vertex-rate buffer the shader never reads** | 1 | **hangs, machine down in ~10 s** | | `?draws=1` | 1 | clean 2/2, but see the caveat below | **The third row is the load-bearing one.** It has no instance-rate fetch anywhere: every per-instance value comes from a storage buffer, exactly as in the clean row above it. The only difference between the clean run and the machine-killing run is the *existence* of a declared vertex layout whose contents are never read. That is precisely the difference between skipping and entering the clamp. **Caveat on `?draws=1`:** it is not a clean isolation of draw count. With one draw at a fixed offset and unchanging minima, `indirectBufferRequiresRecomputation` returns false after the first frame, so that arm emits **zero clamps and zero barriers** thereafter, while the two-draw arm alternates offsets and misses the one-entry cache every time. It varies clamp frequency, not draw count. Counters from the `dummyvb` run sampled every 2 s via `ioreg -r -c AGXAccelerator`: ``` util: 0 2 97 100 100 100 100 100 100 tiled: 0.0 1.5 1.5 0.0 0.0 0.0 0.0 0.0 0.0 recoveryCount 0 -> 0 ``` ## Observed behaviour **The 100 % is a latched counter, not work.** During an unrecoverable instance, with no client process alive at all: ``` Device / Renderer / Tiler Utilization = 100 % fBusyCount = 0 <- driver believes nothing is busy fSubmissionsSinceLastCheck = 0 <- nothing queued fLastSubmissionPID = 402 <- WindowServer, not the (exited) browser recoveryCount = 14 <- no reset attempted ``` Because no job is outstanding, no progress timeout fires and the driver never resets. Instances where a job *was* still outstanding did reset (`recoveryCount++`) and the machine survived; this shape does not. `sudo sample` of WindowServer during a live hang shows it is **not** starved of GPU - it is idle, waiting for a frame that never arrives: ``` ws_main_thread 4016 / 4194 samples in mach_msg2_trap via CGXRunOneServicesPass com.apple.coreanimation.display.primary 4194 / 4194 samples in mach_msg2_trap ``` ## What the source shows `clampIndirectBufferToValidValues` writes clamped arguments into `Buffer::indirectBuffer()`, **one scratch slot per `Buffer`, sized for a single arguments struct** (`Buffer.mm:198`), and returns `(thatSlot, 0)`. Every clamping draw against a given arguments buffer is therefore issued as `drawPrimitives:indirectBuffer:<sameSlot> indirectBufferOffset:0`, separated only by `emitMemoryBarrier`, a vertex→vertex `MTLBarrierScopeBuffers` barrier. (Precisely: the early-outs return `(nil, 0)` for zero minima and the application's own buffer when both minima are `UINT32_MAX`. Every draw that actually *takes* the clamping path shares the one slot.) `memoryBarrierLimit` is `UINT32_MAX` on Apple9, so `splitRenderPass()` never fires and the barrier is always emitted. ## Why this reaches real content Our renderer issues many indirect draws per frame from shared arguments buffers, GPU culling writes one args slot per population and the pass draws them at successive offsets. That is the documented shape for indirect drawing, and [
bug 320490
](
https://bugs.webkit.org/show_bug.cgi?id=320490
) pushed us onto the direct path by making `executeBundles` reconstruct ICBs on every call. Two bugs composing. 320490 is fixed on trunk (2026-08-04), but shipping Safari will not carry that for months, and the direct path is reachable by any content that simply does not use render bundles. Our own mitigation is to remove every required vertex layout from indirect-drawing pipelines and fetch attributes from storage buffers instead. It works, and it costs real performance: bypassing fixed-function vertex fetch loses post-transform vertex reuse, which our measurements put at meaningful cost on well-indexed geometry. ## Attachments `indirect-clamp-hang-repro.html`, the reproduction, all four arms. **DESTRUCTIVE**
Attachments
Crash repro
(11.69 KB, text/html)
2026-08-16 11:17 PDT
,
juretriglav
no flags
Details
View All
Add attachment
proposed patch, testcase, etc.
Ahmad Saleem
Comment 1
2026-08-16 12:20:12 PDT
It reproduces for me on WebKit TOT (
319251@main
). Be careful in opening 'Crash repo', it hanged my system.
Radar WebKit Bug Importer
Comment 2
2026-08-16 12:20:37 PDT
<
rdar://problem/185057290
>
Mike Wyrzykowski
Comment 3
2026-08-16 19:18:20 PDT
https://github.com/WebKit/WebKit/pull/71563
juretriglav
Comment 4
2026-08-17 01:15:11 PDT
This is unfortunately a different bug, Mike Wyrzykowski, that linked PR is for
https://bugs.webkit.org/show_bug.cgi?id=321676
I’m still trying to figure this one out.
Mike Wyrzykowski
Comment 5
2026-08-17 09:12:41 PDT
Oh thank you for clarifying, I misread the bug number and thought they were identical (only middle digit `6` -> `8` is different)
Mike Wyrzykowski
Comment 6
2026-08-17 09:16:02 PDT
I don't reproduce on an M2 Ultra chip (M2 Mac Studio). Might be M3 and later specific
Mike Wyrzykowski
Comment 7
2026-08-17 09:25:45 PDT
Indeed seems to impact M3 / Apple9 and later
Ahmad Saleem
Comment 8
2026-08-17 12:12:35 PDT
Pull request:
https://github.com/WebKit/WebKit/pull/71794
EWS
Comment 9
2026-08-17 16:40:27 PDT
Committed
319317@main
(6dca6330dba2): <
https://commits.webkit.org/319317@main
> Reviewed commits have been landed. Closing PR #71794 and removing active labels.
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug