Skip to content

Commit 39631a3

Browse files
obiotclaude
andauthored
chore(release): 19.6.0 — date + CHANGELOG polish (#1461)
Replace _unreleased_ with the release date and trim the verbose post-merge CHANGELOG entries (Highlights and the Added / Changed / Fixed bullets) down to one-sentence-each form. No behavior change. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ae86ee9 commit 39631a3

1 file changed

Lines changed: 9 additions & 8 deletions

File tree

packages/melonjs/CHANGELOG.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
# Changelog
22

3-
## [19.6.0] (melonJS 2) - _unreleased_
3+
## [19.6.0] (melonJS 2) - _2026-05-23_
44

5-
**Highlights:** WebGL-pipeline hardening release. Fixes a Windows + Chrome crash on NVIDIA Optimus dual-GPU laptops where switching between the integrated and discrete GPU loses the WebGL context, the renderer auto-destroys every `GLShader` via the existing `ONCONTEXT_LOST` subscription, and `gl.deleteProgram` throws on the dead context (ANGLE / D3D11 is strict about cross-context object use; Apple's GL driver silently no-ops, which is why the bug was Windows-only). The throw left `GLShader.uniforms = null` while `ShaderEffect.enabled` stayed true, and the next frame's `setUniform("uTime", …)` then crashed with `TypeError: Cannot read properties of null (reading 'uTime')`. The fix is layered: (1) `GLShader.destroy()` is now atomic + idempotent + wraps `gl.deleteProgram` in a try / catch so a partial-destroy can't happen; (2) `ShaderEffect.destroy()` sets `enabled = false` BEFORE the inner destroy so the public guard is in the safe state even if the inner destroy throws; (3) the previously half-implemented `ONCONTEXT_RESTORED` shader-recovery path is completed — shaders now suspend on context lost (keep source + cached uniform values) and recompile + replay uniforms on context restored; and (4) the renderer itself now recovers the rest of the pipeline on `webglcontextrestored` — its own `vertexBuffer` is re-created, default GL state (BLEND / DEPTH_TEST / SCISSOR_TEST / depthMask / blendMode) is re-applied, every batcher's GL resources are re-initialised, and stale texture-unit assignments in `TextureCache` are dropped so the next `drawImage` re-uploads from still-alive sources. The whole game keeps running across a GPU switch instead of just the shaders surviving.
5+
**Highlights:** WebGL context-loss hardening release. Fixes a Windows + Chrome crash where a GPU switch lost the WebGL context and a partial `GLShader.destroy()` left the next frame's `setUniform("uTime", …)` reading from `null` uniforms. Beyond the crash, the renderer now also transparently recovers the rest of the pipeline (vertex buffer, default GL state, batchers, texture cache) across a `webglcontextlost``webglcontextrestored` cycle, and shaders replay their cached uniforms on restore — so the game keeps drawing across a GPU switch without any intervention from user code.
66

77
### Added
8-
- **Transparent WebGL context recovery, pipeline-wide** — the renderer (not just `GLShader` / `ShaderEffect`) now survives a `webglcontextlost``webglcontextrestored` cycle without intervention from user code. On context restored the renderer re-creates `this.vertexBuffer`, re-applies default GL state (blend / depth / scissor / depthMask / blend mode), re-initialises every batcher (own vertex / index buffers, default shader, `boundTextures`), drops stale texture-unit assignments in `TextureCache`, releases the post-effect FBO pool, and emits `ONCONTEXT_RESTORED` so `GLShader` / `ShaderEffect` instances also recompile + replay their cached uniforms. NVIDIA Optimus GPU switches, browser tab eviction recovery, and `WEBGL_lose_context` teardowns no longer require user code to re-apply uniforms, re-upload textures, or recreate shaders / buffers — the next frame just keeps drawing.
9-
- `GLShader.destroyed` / `GLShader.suspended` / `ShaderEffect.destroyed` — public read-only diagnostic flags. `destroyed` is the stable "explicitly released" signal (distinct from `ShaderEffect.enabled`, which also toggles transiently across context lost / restored). `suspended` is `true` only between an `ONCONTEXT_LOST` and the matching `ONCONTEXT_RESTORED`.
8+
- **Transparent WebGL context recovery, pipeline-wide.** The renderer re-creates its vertex buffer, re-applies default GL state, re-initialises every batcher, drops stale texture-unit assignments, and emits `ONCONTEXT_RESTORED` so `GLShader` / `ShaderEffect` instances recompile + replay their cached uniforms — the next frame just keeps drawing across a GPU switch.
9+
- `GLShader.destroyed` / `GLShader.suspended` / `ShaderEffect.destroyed` — public read-only diagnostic flags. `destroyed` is the stable "explicitly released" signal (distinct from `ShaderEffect.enabled`, which auto-toggles across the cycle); `suspended` is true only between lost and restored.
1010

1111
### Changed
12-
- **`platformer` and `platformer-matter` example coins migrated to per-instance `ShineEffect`** — previously each example kept a module-level singleton `coinShader` with a manual refcount tracked via `LEVEL_LOADED` / `onDestroyEvent`. The other 19.5+ shader-using examples (`plinko-planck/entities/peg.ts`, `dropZone.ts`) already used per-instance shaders + `enabled = true / false` toggling for visibility — the platformer coins were the deviation in 19.5.0. Both platformer entities now follow the same pattern, with `GAME_UPDATE` subscription tied to `onActivateEvent` / `onDeactivateEvent` so pool-recycled coins (`pool.register("CoinEntity", ..., true)`) re-bind on every respawn instead of leaking one dead handler per pickup.
12+
- **Platformer example coins migrated to per-instance `ShineEffect`**, matching the pattern of every other 19.5+ shader-using example. `GAME_UPDATE` subscription tied to `onActivateEvent` / `onDeactivateEvent` so pool-recycled coins re-bind on respawn instead of leaking a dead handler per pickup.
1313

1414
### Fixed
15-
- **Stale shader references no longer crash** — calling `setUniform` / `bind` / `getAttribLocation` on a destroyed `GLShader` (or a destroyed `ShaderEffect`) is now a silent no-op instead of throwing. `GLShader.destroy()` is atomic + idempotent — it wraps `gl.deleteProgram` in a try / catch so a throw on a dead ANGLE / D3D11 context can't leave the shader half-destroyed, and an early-return on the new `destroyed` flag makes double-destroy a no-op. The pre-19.6 implementation set `uniforms = null` before calling `gl.deleteProgram` then nulled the remaining fields after — if the GL call threw (e.g. on NVIDIA Optimus laptops, where switching the active GPU loses the WebGL context and ANGLE / D3D11 then rejects `deleteProgram` against the dead context), the shader was left with `uniforms === null` and `program !== null`, the partial state that caused the 19.5.0 Windows + Chrome `TypeError: Cannot read properties of null (reading 'uTime')` crash via still-registered `GAME_UPDATE` → `setTime(...)` → `setUniform(...)` calls from `ShaderEffect.enabled === true`. Together with the no-op guards on the public methods, late-frame callers holding a stale reference now degrade silently instead of bringing the game down.
16-
- **`ShaderEffect.destroy()` reorders `enabled = false` BEFORE `_shader.destroy()`** — defense in depth. Even if the inner destroy throws outward (or any future failure mode leaves the inner shader half-torn-down), the public `setUniform` / `bind` / `getAttribLocation` / `uniforms` / `attributes` paths on the effect immediately become no-ops via the existing `enabled` gate.
17-
- **`GLShader._uniformCache` snapshots arrays / typed arrays on write** — the cache used for context-restored replay now `.slice()`s every `Array.isArray(value) || ArrayBuffer.isView(value)` write (in addition to the existing `value.toArray()` branch for vector/matrix types) before caching. A caller that mutated a reused `Float32Array` / `Array` after `setUniform` could otherwise silently rewrite what gets replayed on context restore, producing a visual divergence between the pre-restore and post-restore frames.
15+
- **Stale shader references no longer crash.** `GLShader.destroy()` is atomic + idempotent and wraps `gl.deleteProgram` in a try / catch, so a throw on a dead ANGLE / D3D11 context (the 19.5.0 Windows + Chrome `TypeError: Cannot read properties of null (reading 'uTime')` crash) can't leave a half-destroyed shader. Public `setUniform` / `bind` / `getAttribLocation` paths also no-op on destroyed shaders instead of throwing.
16+
- **`ShaderEffect.destroy()`** sets `enabled = false` BEFORE the inner destroy — even if the inner destroy throws outward, the effect's public methods are already in the safe no-op state via the `enabled` gate.
17+
- **`GLShader._uniformCache`** snapshots arrays / typed arrays via `captureValue` on write, so caller mutation after `setUniform` can't silently rewrite what replays on context restore. Reuses the cache slot's allocation between calls (zero per-frame GC for hot uniforms like light positions).
18+
- **`ShaderEffect.enabled`** auto-toggle preserves user-set state across a context cycle — an effect the user explicitly disabled stays disabled after restore.
1819

1920
### Performance
2021
- None.

0 commit comments

Comments
 (0)