v20.0.0
What's New
A new WebGPU backend, and WebGL 2 as the baseline. Version 20.0 introduces a complete WebGPU renderer covering the entire engine feature set, 2D and 3D, verified example-for-example against WebGL. At the same time the aging WebGL 1 path is retired, which frees the WebGL backend to modernize throughout. video.AUTO now tries WebGPU first, falls back to WebGL 2 if that is unavailable, and ultimately to Canvas. See the reworked Hello WebGPU example.
This release has breaking changes. Starting a game is now two steps: construct the Application, then await app.init(). Read the Compatibility section below before upgrading.
New Features
- WebGPU renderer (#1184) covering the full 2D contract (sprites, text and particles with multi-texture batching, shapes and Path2D, blend modes, patterns, scissor clipping, stencil masks, GPU tilemaps, 2D lights and normal maps, frame captures, gradient fills, compressed textures) and the complete 3D tier (
drawMesh, retained geometry underCamera3d, glTF scenes, animated models,Sprite3dbillboards, split-screen viewports) - Mesh instancing (#1508) via the new
InstancedMesh: one geometry drawn many times in a single call. A 100 000-tree forest runs at 60 fps on both GPU backends, with optional per-instance colors and data. See the new Instanced Forest example. glTFEXT_mesh_gpu_instancingloads authored instancing with no user code - Ground shadows for 3D objects (#1515):
castGroundShadow: truegives a mesh, billboard or whole instanced scatter a soft blob shadow, so 2.5D characters stop reading as floating. One extra draw per object, and one for an entire scatter whatever its size - 3D collision (#1476): the new
Box3dbody shape lets a body be pushed back along Z. The 2D path is unchanged - Per-shape collision settings (#1590): each shape on a body may carry its own
collisionType/collisionMask, plusisActiveandisTrigger. So one body can have a footprint that only hits terrain and a hurtbox that only reacts to attacks. A shape that sets nothing behaves exactly as before - Point and spot 3D lights (#1536) on both GPU backends, including authored glTF
KHR_lights_punctuallamps - Up to 32 lights (#1552), raised from 8, by moving light data into a
std140uniform buffer - Dual-language shaders:
ShaderEffectandGLShadereach carry GLSL and WGSL bodies, so one asset runs on either GPU backend. All 18 built-in effects render identically on both. Existing GLSL-only effects are untouched maxTexturessetting (#1585): the WebGL texture pool follows the device instead of a hardcoded 16antiAlias: truesurvives post effects (#1556); capture targets are now multisampled themselves- Mesh textures sample generated mipmaps on both GPU backends, so distant geometry stops shimmering
- OBJ vertex normals (#1572), MTL specular and per-texel opacity (#1575), and per-material diffuse textures on multi-material models (#1573)
exclusionand"none"blend modes on both GPU backends (#1318)- Backend-neutral vertex formats and topologies (#1551); the GL-enum form stays supported indefinitely
Performance
Measured against 19.9.1 on the same machine, same harness and geometry on both sides:
| 19.9.1 | 20.0.0 | ||
|---|---|---|---|
| 2D, 512 quads/frame, one texture past the batch limit | 33 draws, 1 626 uploads, 4.71 ms/frame | 1 draw, 0 uploads, 0.06 ms/frame | 76x |
| 3D, 64 meshes of 5 000 vertices | 128 draws, 3.80 ms/frame | 64 draws, 0.04 ms/frame | 95x |
Below the texture limit the new path is marginally slower (0.052 to 0.062 ms), the residual being the per-source residency lookup that buys the rest. Also in this release: immutable texture storage across the WebGL pipeline, and Vertex Array Objects for every batcher.
Bug Fixes
Application.destroy()leaked the WebGL context, and destroyed renderers stayed subscribed to global events forever, pinning their GPU objects against garbage collection. Both hit any app that tears down and rebuilds- The 3D broadphase silently dropped collisions between bodies at different depths, and an entire 2.5D gameplay plane sat unpartitioned at the root of the octree, degrading it to a linear scan
- A multi-shape sensor body was still pushed out of collisions (#1591), and
Body.destroy()threw for ellipse colliders or when called twice - A texture-cache overflow re-created and re-uploaded every texture once per draw, and a
ShaderEffectsampler could silently corrupt normal-map lighting (#1585) - A scene containing only meshes stopped clearing its depth buffer after the first frame; a
litmesh with no usable normals rendered solid black DropShadowEffectrendered its shadow mirrored when chained with other effects on WebGL; untextured glTF materials rendered washed out; indexed PNGs in glTF assets rendered greyscale on Safari
Compatibility
This is a major release with breaking changes.
await app.init()is now mandatory. Construct theApplication, then awaitinit(). Construction alone no longer builds the renderer or appends the canvas, so without the call nothing displays. This applies to code already usingnew Application(...)on 19.x:const app = new Application(640, 480, { parent: "screen" }); await app.init();
video.init(),video.renderer,video.createCanvas()andvideo.getParent()are removed (deprecated since 18.3.0 / 19.7.0). Useapp.init(),app.renderer,app.renderer.createCanvas()andapp.getParentElement()- The WebGL renderer is WebGL 2 only (#1509).
video.AUTOfalls back to Canvas on WebGL-1-only devices. User shaders need no changes: GLSL ES 1.00 compiles unchanged on WebGL 2 video.AUTOnow tries WebGPU first. Pinrenderer: video.WEBGL(or the#webglURI fragment) to stay on WebGL- Custom batchers extend
WebGLBatcherinstead ofBatcher, which is now the backend-neutral base. One word, no other change - Custom mesh shaders receive geometry in model space (#1507); position with
uProjectionMatrix * uViewMatrix * uModelMatrixand tint withuTint. Sprite and post-effect shaders are unaffected Application.updateAverageDeltais renamedlastUpdateDelta; the old name keeps working as an alias
See the Upgrade Guide for the full migration.
Released alongside
| package | version |
|---|---|
@melonjs/spine-plugin |
4.0.0 (WebGPU support; requires melonJS 20) |
@melonjs/planck-adapter |
1.2.0 |
@melonjs/matter-adapter |
1.1.1 |
@melonjs/debug-plugin |
16.1.1 |
@melonjs/tiled-inflate-plugin |
1.2.1 |
Install
npm install melonjs@20.0.0Full details in the CHANGELOG.