# WebGPU CTS as an oracle Runs the WebGPU conformance test suite (`gpuweb/cts`) headlessly under dawn.node (Dawn's Node.js binding, no browser) and in Playwright browsers, to measure how much of it can check a future engine's WebGPU binding and validation layer over a linked GPU library (Dawn or wgpu). It says nothing about a generated GPU driver; none is planned. Both runs are complete; `webgpu-cts/aggregate-dawn-node.mjs` and `webgpu-cts/agreement.mjs` regenerate every number below from the gitignored per-query JSON. ## Oracle coverage estimate | part | dawn.node gives a verdict | of those, pass | agrees with Chromium | true pass/fail conflicts | estimate | |---|---:|---:|---:|---:|---| | Validation (`api,validation` + `shader,validation`) | 88.0% | 99.9% | 78.2% of 91,618 | 3.5% | **~85%, exact** | | Numeric execution (`shader,execution` + `api,operation`) | 55.8% | 97.7% | 67.5% of the 37% both completed | 1.0% | **~50%, ULP** | - Most validation disagreement (16,805 of 19,973 cases) is one software adapter reporting a format, limit or feature unsupported where the other supports it — a capability difference, not a dispute about behaviour. - Numeric execution is capped by coverage: 63% of cases never completed in Chromium, so much of dawn.node's result is trusted on one runtime. - Both runtimes are Dawn, so agreement confirms Dawn on two backends (lavapipe and SwiftShader), not two independent implementations. - Both figures are below the earlier "~90%" guess, which ignored the suite's own skip rate and how much a second runtime can reach in reasonable time. ```mermaid flowchart LR cts["gpuweb/cts
697 spec files"] --> dn["dawn.node on lavapipe
285,540 cases, 30 min"] cts --> chr["Chromium on SwiftShader
165,798 cases, ~89 min"] dn --> agree["agreement.mjs
per case, by area"] chr --> agree ff["Firefox, WebKit"] -. "no navigator.gpu" .-> x["not run"] ``` ## Pinned versions - Suite: `gpuweb/cts` commit `81aeeb49e5b15587ec6c1f59b2fdcb4074ffcf63` (2026-09-15), cloned into the gitignored `webgpu-cts/cts/`. - dawn.node: npm `webgpu` 0.6.1 (`dawn-gpu/node-webgpu`, gitHead `f2585d1b386b0f8df63b3d080131e35ab448a9bb`); the npm version is the reproducibility pin. - Playwright 1.50.0. - Host: 16 cores, no GPU, Mesa 25.2.8-0ubuntu0.24.04.2, LLVM 20.1.2 (lavapipe). Driver chosen explicitly with `VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json`, never the loader default. ## How to reproduce ```sh cd webgpu-cts npm install git clone https://github.com/gpuweb/cts.git git -C cts checkout 81aeeb49e5b15587ec6c1f59b2fdcb4074ffcf63 VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json node adapter-info.cjs # confirms lavapipe ./run-dawn-node.sh # full suite, sharded across nproc cores, resumable node aggregate-dawn-node.mjs # pass/fail/skip tallies, overall and by area # Chromium's GPU process needs /dev/dri/renderD128 access; run its steps # through `sg render -c '...'` if the calling shell isn't already in that group. sg render -c './run-browser-shards.sh results/browser-chromium' node agreement.mjs dawn-node browser-chromium # per-case cross-runtime agreement, by area ``` - `gpu-provider.cjs` wraps dawn.node as the suite's `GPUProviderModule` and installs its IDL constructors on `globalThis`, because the suite's `instanceof GPUDevice` checks expect them there (without it every test throws `ReferenceError`). - One CTS query per spec file (`list-queries.sh`), one process per query, so a slow or crashing file stalls only itself; `run-shard.sh` records finished queries in `OUT_DIR/done.txt` so a restart redoes only what did not finish. - `browser-run-queries.cjs` relaunches a shard's browser on a timeout and retries once; without that, one hung Chromium wedged its whole shard. ## Agreement: dawn.node on lavapipe 16 shards, `LP_NUM_THREADS=1` each: 30 minutes 22 seconds, 697 queries, 285,540 cases (about 157 cases/s), dominated by a few huge texture files. | Area | Total cases | Pass | Fail | Skip | |---|---|---|---|---| | shader,execution | 179,233 | 95,900 | 2,360 | 80,973 | | api,validation | 44,083 | 33,159 | 66 | 10,858 | | shader,validation | 47,535 | 47,363 | 1 | 171 | | api,operation | 10,695 | 7,591 | 38 | 3,066 | | web_platform,copyToTexture | 2,522 | 0 | 0 | 2,522 | | web_platform,external_texture | 162 | 0 | 0 | 162 | | web_platform,canvas | 490 | 0 | 1 | 489 | | compat,api | 608 | 497 | 0 | 111 | | idl,javascript | 94 | 74 | 20 | 0 | | util,texture | 57 | 39 | 6 | 12 | | idl,constants | 28 | 28 | 0 | 0 | | idl,constructable | 8 | 8 | 0 | 0 | | examples | 21 | 18 | 0 | 3 | | web_platform,worker | 3 | 0 | 0 | 3 | | print_environment | 1 | 1 | 0 | 0 | | **Total** | **285,540** | **184,678** | **2,492** | **98,370** | `fail` is dawn.node failing its own test (0.87%, mostly numeric-tolerance cases in `shader,execution`). Skips are feature-gated cases the suite correctly excludes. 20 result files needed recovery because dawn.node prints shader-compiler warnings to stdout before its JSON; the aggregator takes the last `{"version": ...}` object. ## Agreement: dawn.node vs. Chromium Chromium ran in two 12-shard passes (46 minutes, then 43 minutes over 124 errored queries, recovering 113). It has verdicts for 165,798 cases; the missing 119,742 are six files that never completed. | Area | Total | Agree | Disagree | Only dawn.node | |---|---|---|---|---| | api,operation | 10,695 | 8,662 | 1,769 | 264 | | api,validation | 44,083 | 32,399 | 11,684 | 0 | | compat,api | 608 | 515 | 93 | 0 | | examples | 21 | 20 | 1 | 0 | | idl,constants | 28 | 27 | 1 | 0 | | idl,constructable | 8 | 8 | 0 | 0 | | idl,javascript | 94 | 75 | 19 | 0 | | print_environment | 1 | 1 | 0 | 0 | | shader,execution | 179,233 | 38,691 | 21,064 | 119,478 | | shader,validation | 47,535 | 39,246 | 8,289 | 0 | | util,texture | 57 | 55 | 2 | 0 | | web_platform,canvas | 490 | 14 | 476 | 0 | | web_platform,copyToTexture | 2,522 | 456 | 2,066 | 0 | | web_platform,external_texture | 162 | 60 | 102 | 0 | | web_platform,worker | 3 | 0 | 3 | 0 | | **Total** | **285,540** | **120,229** | **45,569** | **119,742** | | Change (dawn.node → Chromium) | Count | Meaning | |---|---|---| | pass → skip | 26,908 | SwiftShader reports unsupported what lavapipe supports | | skip → pass | 9,174 | the reverse | | skip → fail | 4,328 | a case lavapipe excludes runs and fails on Chromium | | pass → fail | 4,874 | both run it; Chromium fails | | fail → pass | 261 | both run it; dawn.node fails | | fail → skip | 24 | Chromium excludes a case dawn.node fails | 89% of disagreements involve a skip — a capability gap between adapters. The 5,135 true conflicts concentrate in texture-sampling builtins (`textureGatherCompare`, `textureSampleGrad`, `textureSampleLevel`) by depth format and filter mode, not yet root-caused. ## Browsers `webgpu-cts/probe-browser-webgpu.cjs` checks `navigator.gpu` in each Playwright browser: - **Chromium 133.0.6943.16** — works, but reports `vendor: google, architecture: swiftshader`, its own bundled adapter, never the host's lavapipe. Adding the account to the `render` group and every flag tried (`--use-vulkan=native`, `--use-angle=vulkan`, `--ignore-gpu-blocklist`, and others) changed nothing. Likely, unconfirmed: Chromium's Dawn excludes CPU Vulkan adapters by policy. So the browser run is dawn.node-vs.-SwiftShader. - **Firefox 134.0** — `navigator.gpu` undefined even with `dom.webgpu.enabled`: "WebGPU is not yet available in Release or Beta builds." - **WebKit 18.2** — `navigator.gpu` undefined. ## What could not run, and why - **Firefox and WebKit runs**: no WebGPU in these Playwright builds. - **A browser on lavapipe**: Chromium only reaches SwiftShader. - **5 spec files in both runtimes**, rejected by the suite's loader as matching no cases (a suite-level query/path mismatch): `api,operation,command_buffer,render,dynamic_state`, `api,operation,compute_pipeline,entry_point_name`, `api,validation,buffer,threading`, `api,validation,encoding,cmds,render_pass`, `api,operation,rendering,robust_access_index`. - **6 files that never complete in Chromium**, timing out twice at 600 s: `api,operation,command_buffer,queries,timestampQuery`, `api,operation,command_buffer,queries,occlusionQuery`, and the `textureSample`, `textureSampleGrad`, `textureSampleLevel` and `textureGather` builtins. All complete under dawn.node; whether SwiftShader hangs or is just slow is not distinguished.