Skip to content

v19.4.0

Choose a tag to compare

@obiot obiot released this 12 May 09:27
· 45 commits to master since this release
a6b8c21

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-zero tileoffset, 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 shadersGLShader now accepts both vertex and fragment source written with #version 300 es. The precision injector and attribute extractor handle both 1.00 and 3.00. ShaderEffect remains 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 standard TextureCache and batcher path. Supports rgba8 and rgba8ui (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 texture warning. The engine handles NPOT correctly (clamp wrap, non-mipmapped filters). A targeted warning now fires only when repeat: "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.uploadTexture POT check used destination quad size — the w and h parameters (the destination quad size, not the texture's) drove the isPOT check, which gates both the wrap-mode fallback and generateMipmap. Visible as a GL_INVALID_OPERATION from gl.generateMipmap on 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 leveltestEllipseEllipse and testPolygonEllipse built the relative-position vector by adding a.ancestor.getAbsolutePosition() where they should have subtracted it, shifting the circle by 2 * ancestor.absPos. Manifests when level.load auto-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 lets isSeparatingAxis do 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 centeredTMXTileMap.addTo sets container.pos after adding children, so each child's cached absolute bounds (computed at addChild time) didn't include the centering offset. Children that moved on their own refreshed via the pos observer, 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. _setBounds now 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-repeat produced different output on Canvas vs WebGL (closes #1290) — ImageLayer.draw asked the renderer to fill viewport.width * 2 × viewport.height * 2 regardless 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 via GL_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's TilingSprite mental model — the tile rectangle is the tile rectangle.

Performance

  • Uint16Array-backed TMX layer data — TMX tile layers now back layerData with a flat Uint16Array and the orientation renderers read directly from it, with no Tile allocations 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 redundant gl.uniform* calls. Vec/mat values compare element-wise so a reused scratch Float32Array is 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