Summary
A WebGL context loss on Android browsers occurs when a hidden sub‑viewport is made visible while rendering a Godot 4.6 scene. Desktop and iOS browsers keep the context alive, but Android’s WebGL implementation aggressively discards it under certain memory‑pressure and visibility‑change conditions, leading to the “context lost” error.
Key takeaway: Android browsers can automatically lose the WebGL context when a canvas is hidden‑then‑shown, especially after a texture‑heavy operation such as loading a 3D dice scene.
Root Cause
- Canvas visibility toggle triggers a paint event that forces the browser to reclaim GPU memory.
- Android WebView/Chrome treats the hidden canvas as non‑essential and may call
gl.getExtension('WEBGL_lose_context').loseContext(). - Godot’s engine does not automatically re‑initialize the context after loss on Android, so the next draw call throws an exception.
- The issue is amplified by:
- Large texture atlases for dice faces.
- Frequent calls to
CanvasItem.visible = trueon the sub‑viewport. - Lack of explicit handling of
WEBGL_context_lostevents in the exported HTML wrapper.
Why This Happens in Real Systems
- Mobile GPU memory is limited; browsers enforce stricter policies to avoid out‑of‑memory crashes.
- Visibility‑change heuristics differ per platform: Android browsers prioritize foreground canvases.
- Godot’s Web export bundles a generic WebGL bootstrap that does not include a robust “context restoration” path for Android.
- Real‑world apps that dynamically hide/show UI layers (e.g., in‑game menus, AR overlays) often hit the same pitfall.
Real-World Impact
- Users see a frozen screen or a console error like
WebGL context lost. - Gameplay or UI interactions become unresponsive until the page is refreshed.
- In analytics, Android‑only crash reports spike, degrading user retention.
- Developers waste time debugging platform‑specific graphics failures.
Example or Code (if necessary and relevant)
# Re‑initialize the sub‑viewport when the context is restored
func _ready():
RenderingServer.connect("frame_post_draw", self, "_on_frame_post_draw")
var canvas = get_viewport().get_canvas_item()
canvas.connect("context_lost", self, "_on_context_lost")
canvas.connect("context_restored", self, "_on_context_restored")
func _on_context_lost():
print("WebGL context lost – scheduling reload")
# Optionally display a loading UI
func _on_context_restored():
print("WebGL context restored – re‑create resources")
$SubViewportContainer.visible = false # reset visibility
$SubViewportContainer.visible = true # force re‑draw
# Reload textures, materials, etc., if necessary
How Senior Engineers Fix It
- Pre‑emptive context handling
- Register
WEBGL_context_lostandWEBGL_context_restoredlisteners in the HTML wrapper and forward them to Godot viaJavaScriptBridge. - On loss, pause rendering, release heavy textures, and force a full scene reload when restored.
- Register
- Avoid hidden‑canvas pitfalls
- Keep the sub‑viewport canvas always attached but set its
modulate.a = 0to hide instead of togglingvisible. - Use
CanvasItem.visible = falseonly for UI nodes, not for the root viewport container.
- Keep the sub‑viewport canvas always attached but set its
- Memory budgeting
- Reduce dice texture size, compress with Basis Universal, and reuse a single material instance.
- Call
RenderingServer.free_rid()for any temporary textures before showing the sub‑viewport.
- Testing & CI
- Add automated Selenium/WebDriver tests on Android Chrome that simulate visibility changes and assert no
context lostevents. - Monitor
Performance.memoryin the browser console to catch memory spikes early.
- Add automated Selenium/WebDriver tests on Android Chrome that simulate visibility changes and assert no
Why Juniors Miss It
- They assume WebGL contexts are permanent once created, overlooking platform‑specific loss policies.
- They hide canvases by toggling
visiblewithout understanding the underlying GPU resource reclamation. - They rely on Godot’s default export without adding custom JS listeners for context events.
- They often test only on desktop, missing the tight memory constraints of Android browsers.
Bottom line: Treat the WebGL context as a volatile resource on mobile, and build explicit loss‑handling pathways into both the Godot project and the surrounding JavaScript shim.