fix(rendering): share one WebGPU adapter per GPU object - #774
Merged
Merged
Conversation
WebGpuBackend requested a fresh GPUAdapter on every initialize(); the spec gives adapters no destroy() equivalent to GPUDevice's, so release depends entirely on garbage collection. A process that constructs many backends in quick succession - the rendering parity matrix does, once per scene per property - can request adapters faster than the browser reclaims the previous ones. On Firefox this hit a driver ceiling well before anything had actually leaked, surfacing as requestDevice() rejecting with 'not enough memory left' partway through a run, at a boundary that moved between runs depending on GC timing. WebGpuBackend now shares one adapter per GPU object (an adapter can mint any number of devices, so reuse changes nothing about device lifetime) and self-heals: a requestDevice() rejection on the cached adapter retries once against a freshly requested one, and a device loss for a reason other than an explicit destroy() invalidates the cache before recovery, since a loss the backend did not cause is the strongest available signal that the adapter itself is gone too.
Exoridus
enabled auto-merge (squash)
September 22, 2026 16:38
Exoridus
disabled auto-merge
September 22, 2026 16:40
Bundle ReportChanges will decrease total bundle size by 20.7MB (-37.92%) ⬇️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: exo-esm-esmAssets Changed:
Files in
view changes for bundle: exo-esm-modules-esmAssets Changed:
Files in
view changes for bundle: exo-full-iife-Exo-iifeAssets Changed:
view changes for bundle: site-server-esmAssets Changed:
App Routes Affected:
view changes for bundle: exo-full-iife-min-Exo-iifeAssets Changed:
view changes for bundle: exo-iife-min-Exo-iifeAssets Changed:
Files in
view changes for bundle: exo-iife-Exo-iifeAssets Changed:
Files in
|
The shared-adapter fix alone did not resolve the Firefox 'not enough memory left' failure: two verification runs failed identically at the same test, and instrumented Firefox process memory climbed continuously and without a plateau throughout the run - the profile of a real, growing footprint rather than a fixed device/adapter-count ceiling. Every property (renders-something, oracle-agreement, determinism, cross-backend-parity) opened and destroyed its own WebGl2Backend and WebGpuBackend, so one scene's four properties constructed up to four separate WebGPU backends where one would do. The parity matrix runs scenes and properties in strict sequence, so nothing reused an already open backend between them. The runner now opens each scene's backends once, in a per-describe beforeAll, and shares them across every property that runs against that scene; ownership - and destroy() - moves from the properties to the runner's afterAll. This cuts WebGPU backend construction by roughly the property count per scene. Verified on Windows Firefox: two runs before this change failed identically (130/162 tests, same test, 'not enough memory left', peak single-process working set ~1988 MB); two runs after - one before and one after re-confirming on Chromium - completed all 162 tests with full 240-row evidence, peak working set ~1079 MB, and roughly half the wall-clock time.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WebGpuBackendrequested a freshGPUAdapteron everyinitialize(); the spec gives adapters nodestroy()equivalent toGPUDevice's, so release depends entirely on garbage collection.WebGpuBackendnow shares one adapter perGPUobject (an adapter can mint any number of devices), keyed by theGPUobject identity (WeakMap) so a real page's onenavigator.gpushares one adapter while each test's own mockGPUobject gets its own cache entry automatically - and self-heals: arequestDevice()rejection on the cached adapter retries once against a freshly requested one, and a device loss for a reason other than an explicitdestroy()invalidates the cache before recovery.This alone did not resolve the Firefox 'not enough memory left' failure from the release-readiness handoff: two verification runs failed identically (130/162 tests, same failing test, same duration), and instrumented Firefox process memory climbed continuously with no plateau through the run - the profile of a real, growing footprint, not a fixed adapter/device-count ceiling.
The actual fix is the second commit: the rendering parity matrix opened and destroyed a fresh
WebGl2Backend/WebGpuBackendper property rather than per scene - up to four separate WebGPU backends for one scene's four properties, run in strict sequence so nothing was ever reused. The runner now opens each scene's backends once (per-describebeforeAll) and shares them across every property that tests that scene; ownership ofdestroy()moves from the properties to the runner'safterAll.Verified on Windows Firefox, with process-memory instrumentation:
Both commits are kept together: the adapter-sharing fix is independently correct (real GC-timing robustness at the adapter level) and the two are meant to complement each other, even though the second one turned out to carry the actual fix.
Local
test/rendering/parity/evidence.jsonwas not committed here (measured locally, pre-merge SHA would not match); a follow-up re-measurement against the merged commit is needed to update the guaranteed-browser evidence.