feat(webgl): detect GPU out-of-memory and emit an outOfMemory event#54
Merged
Conversation
Some devices log a flood of "Could not create WebGL Texture" on
image-heavy pages: a texImage2D OOM escalates to a lost context, after
which every gl.createTexture() returns null. The renderer had no way to
detect the originating GPU out-of-memory — getError() is only checked in
dev builds because it forces a CPU/GPU sync.
Add a once-per-loop OOM probe and surface it as an application event so
the app can recover (e.g. reload with a lower criticalThreshold):
- WebGlRenderer.checkForOutOfMemory() drains the GL error queue (bounded)
and reports whether GL_OUT_OF_MEMORY was seen. checkForOutOfMemory is
now a required CoreRenderer method; CanvasRenderer returns false.
- The probe runs at the idle transition (end of a render burst), not
every frame. GL errors accumulate and persist until drained, so a
single check still catches any OOM raised during the active frames
without paying the getError() sync per frame.
- TextureMemoryManager.handleOutOfMemory() queues an `outOfMemory` frame
event ({ memUsed, criticalThreshold }) and requests a best-effort
cleanup. Persistence/threshold-lowering/reload is left to the app;
RendererMainOutOfMemoryEvent documents the recommended integration
(read calibrated threshold from localStorage, namespaced per app for
file:// deployments; lower to 90% of the measured ceiling with a floor;
reload).
Tests: TextureMemoryManager event behavior and the idle-path probe
(fires at idle, not on active frames).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Problem
On image-heavy pages, some devices log a flood of
Could not create WebGL Texture(one device at a time). The root cause isn'tcreateTexturerunning out of memory —createTextureonly allocates a handle. It's a lost context: atexImage2DGPU out-of-memory escalates to context loss, after which everygl.createTexture()returns null, producing the flood.The renderer had no way to detect the originating GPU out-of-memory. WebGL only exposes it via
gl.getError()(GL_OUT_OF_MEMORY), which this engine deliberately checks only in dev builds becausegetError()forces a CPU/GPU sync. So in production the OOM was invisible until it had already become a dead context.What this adds
A GPU out-of-memory probe surfaced as an application event, so the app can recover (e.g. reload with a lower
criticalThreshold).WebGlRenderer.checkForOutOfMemory()— drains the GL error queue (bounded to keep the sync cost fixed) and returns whetherGL_OUT_OF_MEMORYwas seen.checkForOutOfMemoryis now a requiredCoreRenderermethod;CanvasRendererreturnsfalse(no GPU OOM signal on Canvas2D).getError()sync per frame. (Tradeoff: an app that never goes idle won't fire the probe until it settles; the error stays queued until then.)TextureMemoryManager.handleOutOfMemory()queues anoutOfMemoryframe event ({ memUsed, criticalThreshold }) and requests a best-effort cleanup of non-renderable textures.Policy lives in the app, not the renderer
The renderer only detects and reports. Persisting/lowering the threshold and reloading is application policy.
RendererMainOutOfMemoryEventcarries a full recommended-integration snippet in its docs:criticalThresholdfromlocalStorageon startup, namespaced per app (important forfile://TV deployments where the origin isnull/opaque and a bare key collides across apps).outOfMemory, lower to 90% of the measured ceiling (min(memUsed, criticalThreshold)) with a floor, persist, andlocation.reload().memUsedat the time of the failure is a measured ceiling — the real GPU budget is at or below it — which is why it's a good basis for the next threshold.Reviewer notes
emitinvokes listeners as(target, data); the new event docs and examples use that signature.checkForOutOfMemorybeingabstractmeans a future backend that forgets to implement it is a build error, not a silent skip.Tests
TextureMemoryManager.test.ts—outOfMemoryevent payload, cleanup request, threshold left unchanged, fresh estimate per fire.WebPlatform.outOfMemory.test.ts— probe fires once at the idle transition, handles OOM when reported, and does not probe on an active frame.🤖 Generated with Claude Code