WebGL canvas context loss and frame stutter when rendering 3D scenes inside custom WebKit app webviews

Hi everyone,
I am currently testing a mobile WebGL web application powered by Babylon.js that renders interactive 3D assets inside an embedded WebKit frame. As part of our cross-platform testing setup, we are inspecting performance and script execution behavior when launched, but the engine frequently hits a WebGLContextLost event after a few seconds of continuous scene rendering. While the exact same scene runs smoothly at sixty frames per second on standard Chrome and Safari mobile browsers, initializing the engine canvas inside this custom runtime environment leads to severe GPU memory leaks and stuttering animations.

I am also running into a couple of related rendering and input handling issues with this mobile container integration. When the WebGL context drops, calling engine.restoreContext() fails to reload the active shader materials and texture buffers, leaving the canvas as a completely black box until the entire app session is re-launched. Additionally, multi-touch canvas gestures for camera control fail to register properly, causing the active ArcRotateCamera to lock up whenever touchscreen inputs overlap with background overlay scripts.

Has anyone encountered similar context loss crashes or touch input freezes when embedding Babylon.js canvases inside third-party WebKit shells or customized Android wrappers, and are there specific engine flags like loseContextOnPause or custom canvas context parameters you would recommend passing to maintain WebGL stability?

Ok shooting in the dark but I had a few similar experiences: the main culprit was always out of memory exceptions.

the context lost is a red herring in that case

Adding to what Deltakosh said, because the memory answer explains the context loss but not the two things that come after it — and the second one has had no answer here at all.

Why restoreContext() leaves you with a black canvas

Babylon can only rebuild what it can re-fetch. A Texture created from a URL is restorable: the engine re-requests it. A RawTexture, a DynamicTexture, a RenderTargetTexture, or anything built from an ArrayBuffer or a Blob:/ObjectURL you have since revoked is not — there is no source to go back to. If your materials use any of those, restore completes “successfully” and draws nothing, which is exactly the black box you describe.

There is also a flag worth checking before you touch anything else: engine.doNotHandleContextLost. Left at its default false, Babylon keeps a CPU-side copy of vertex and index buffer data specifically so it can rebuild after a loss. That copy is real memory, in the same process that is already running out of it. So the setting that makes restore work is also making the loss more likely — and if some code in your stack has set it to true to save memory, restore cannot work by construction. Whichever way that flag currently sits, it is probably not sitting there deliberately.

The multi-touch lockup is a separate bug and not a WebGL one

An ArcRotateCamera that locks when touches overlap with an overlay is almost always lost pointer capture, not a camera problem. Two things to check, in this order:

touch-action: none on the canvas element itself, not on a parent. Without it the WebView keeps its own scroll/zoom gesture recogniser alive, decides mid-gesture that the sequence belongs to it, and fires pointercancel. Babylon’s input handler treats pointerup and pointercancel differently, and a cancel arriving for one of two active pointers leaves the camera with a pointer id it will never see released. That is the lock.

The overlay scripts are the usual trigger: a listener that calls preventDefault() on touchmove while the canvas is also receiving pointer events makes the two event models disagree about who owns the gesture. Log pointerdown/pointerup/pointercancel with event.pointerId and reproduce the lock once — you will see a pointercancel with no matching release immediately before it, or you will not, and either way it settles the question in one run.

One measurement worth making before any of this

Sum your textures at their actual resident size rather than their file size: width × height × 4 bytes, plus roughly a third again for mipmaps. A single 4096×4096 RGBA texture is 67 MB on the GPU no matter that the PNG was 3 MB, and the WebKit shell you are in has a smaller GPU budget than the Safari that runs the same scene at sixty. Five of those and you are over on a mid-range device before the first frame.

If the number comes out large, GPU-compressed textures are the fix that actually changes the arithmetic — KTX2/Basis stays compressed in VRAM instead of being expanded at upload, which is a 4–6× cut in resident memory rather than a smaller download. Nothing else on the list moves the number that far.

Two questions, because they change which of the above matters:

  • What is the resident texture total, and are you on KTX2/Basis or on PNG/JPEG?
  • Does a pointercancel fire immediately before the camera locks?

I build browser 3D for a living — mostly industrial configurators that have to survive whatever mid-range Android is actually on the shop floor, so this failure mode is familiar.