v19.4.0
What's New in melonJS 19.4.0
New Features
- GPU-accelerated WebGL 2 tile rendering for orthogonal TMX maps — each visible layer now renders as a single quad whose fragment shader walks the per-layer GID index texture and samples the tileset atlas, with no per-tile draw loop. Supports animated tiles, flip bits (H/V/AD), per-layer opacity/tint, per-layer blend mode, and oversized bottom-aligned tiles. Enabled by default via
Application.settings.gpuTilemap; falls back transparently to the legacy CPU renderer on isometric/staggered/hexagonal layers, collection-of-image tilesets, non-zerotileoffset, or non-WebGL-2 contexts. Rough win on a mid-tier mobile GPU with a 3-layer 800×600 viewport: ~2–4 ms down to ~0.3–0.8 ms per frame; up to ~5–8× on dense large maps; effectively free on desktop GPUs. - GLSL ES 3.00 custom shaders —
GLShadernow accepts both vertex and fragment source written with#version 300 es. The precision injector and attribute extractor handle both 1.00 and 3.00.ShaderEffectremains 1.00-only because WebGL requires both stages of a program to share a version, and it pairs the user's fragment with the built-in 1.00 quad vertex shader. TextureResource/BufferTextureResource— a renderer-agnostic source for textures synthesized from raw byte buffers rather than loaded from an image. Flows through the standardTextureCacheand batcher path. Supportsrgba8andrgba8ui(WebGL 2) formats. Used internally by the GPU TMX renderer.
Changed
- WebGL 1 NPOT warning is now scoped — removed the unconditional
[Texture] ... is not a POT texturewarning. The engine handles NPOT correctly (clamp wrap, non-mipmapped filters). A targeted warning now fires only whenrepeat: "repeat*"is requested on an NPOT texture under WebGL 1, the one case where the user's intent is silently downgraded. throttle(fn, wait)is now generic over its argument tuple.throttle<T extends unknown[]>((...args: T) => void, wait)preserves the wrapped function's parameter types.
Bug Fixes
- WebGL:
MaterialBatcher.uploadTexturePOT check used destination quad size — thewandhparameters (the destination quad size, not the texture's) drove theisPOTcheck, which gates both the wrap-mode fallback andgenerateMipmap. Visible as aGL_INVALID_OPERATIONfromgl.generateMipmapon WebGL 1; silent wasted work on WebGL 2. Texture dimensions are now derived from the source itself. - SAT: ellipse collisions silently failed inside a centered level —
testEllipseEllipseandtestPolygonEllipsebuilt the relative-position vector by addinga.ancestor.getAbsolutePosition()where they should have subtracted it, shifting the circle by2 * ancestor.absPos. Manifests whenlevel.loadauto-centers the level container on a wider viewport, breaking every circle-vs-anything collision. The polygon/polygon path is unaffected — it builds two absolute positions and letsisSeparatingAxisdo the subtraction. Latent because every existing SAT unit test wired the mock ancestor to(0, 0), where the sign error is arithmetically invisible. - TMX: static children kept stale absolute bounds after the level was centered —
TMXTileMap.addTosetscontainer.posafter adding children, so each child's cached absolute bounds (computed ataddChildtime) didn't include the centering offset. Children that moved on their own refreshed via theposobserver, but TMX layers, Tiled collision shapes, triggers, and decorative sprites stayed stuck at their pre-centering bounds — visible as debug overlay shapes drawn at the wrong screen position, and as broken viewport culling for anything outside the pre-centering box._setBoundsnow walks the container subtree and refreshes absolute bounds after the position actually moves (both initial load and viewport resize). - ImageLayer:
repeat-x/repeat-y/no-repeatproduced different output on Canvas vs WebGL (closes #1290) —ImageLayer.drawasked the renderer to fillviewport.width * 2×viewport.height * 2regardless of repeat mode, then leaned on each renderer's overflow behavior on the non-tiling axis. Canvas left the overflow transparent (HTML spec); WebGL stretched the bottom row / right column viaGL_CLAMP_TO_EDGE. The draw extent is now clamped to the source dimensions on any axis that isn't tiling, so neither renderer enters its overflow path and both produce the same strip-shaped output. Matches Pixi'sTilingSpritemental model — the tile rectangle is the tile rectangle.
Performance
Uint16Array-backed TMX layer data — TMX tile layers now backlayerDatawith a flatUint16Arrayand the orientation renderers read directly from it, with noTileallocations during map parse or per-frame rendering. Per-layer memory drops ~25× (40 KB vs ~1 MB on a 100×100 layer); modest FPS gain on Canvas (~2–5% in tile-heavy scenes). Public API is unchanged.- Engine-wide uniform value cache — every shader the engine builds (sprite batchers, light effects, post-effect chains, the TMX GPU renderer, user-authored
GLShader/ShaderEffect) now caches the last value sent for each uniform and skips redundantgl.uniform*calls. Vec/mat values compare element-wise so a reused scratchFloat32Arrayis detected correctly. Biggest beneficiaries are the per-frame projection-matrix upload (now skipped after the first frame) and the TMX GPU renderer's layer-lifetime constants. Modest on its own (~0.1–0.5 ms saved per frame on mid-tier mobile), more in scenes with many custom shaders or post-effect chains, but stacks cleanly with every other rendering win. - TMX GPU renderer fragment-shader fast path — branches on
uOverflow == (0, 0)and uses a single-cell fast path for tilesets whose tiles fit the cell exactly (the common case), skipping the worst-case 25-iteration candidate-cell loop entirely. The slow path (oversized bottom-aligned tiles) is unchanged. Roughly 10–25% fragment-shader cost reduction for the common case (~0.05–0.2 ms per frame on mid-tier mobile, lost in the noise on desktop GPUs); the win compounds with viewport size since fragment work scales with pixel count.
Install
npm install melonjs@19.4.0