Skip to content

v20.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Aug 02:16
· 57 commits to master since this release
dbaa973

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 under Camera3d, glTF scenes, animated models, Sprite3d billboards, 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. glTF EXT_mesh_gpu_instancing loads authored instancing with no user code
  • Ground shadows for 3D objects (#1515): castGroundShadow: true gives 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 Box3d body 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, plus isActive and isTrigger. 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_punctual lamps
  • Up to 32 lights (#1552), raised from 8, by moving light data into a std140 uniform buffer
  • Dual-language shaders: ShaderEffect and GLShader each 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
  • maxTextures setting (#1585): the WebGL texture pool follows the device instead of a hardcoded 16
  • antiAlias: true survives 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)
  • exclusion and "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 ShaderEffect sampler could silently corrupt normal-map lighting (#1585)
  • A scene containing only meshes stopped clearing its depth buffer after the first frame; a lit mesh with no usable normals rendered solid black
  • DropShadowEffect rendered 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 the Application, then await init(). Construction alone no longer builds the renderer or appends the canvas, so without the call nothing displays. This applies to code already using new Application(...) on 19.x:
    const app = new Application(640, 480, { parent: "screen" });
    await app.init();
  • video.init(), video.renderer, video.createCanvas() and video.getParent() are removed (deprecated since 18.3.0 / 19.7.0). Use app.init(), app.renderer, app.renderer.createCanvas() and app.getParentElement()
  • The WebGL renderer is WebGL 2 only (#1509). video.AUTO falls back to Canvas on WebGL-1-only devices. User shaders need no changes: GLSL ES 1.00 compiles unchanged on WebGL 2
  • video.AUTO now tries WebGPU first. Pin renderer: video.WEBGL (or the #webgl URI fragment) to stay on WebGL
  • Custom batchers extend WebGLBatcher instead of Batcher, 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 * uModelMatrix and tint with uTint. Sprite and post-effect shaders are unaffected
  • Application.updateAverageDelta is renamed lastUpdateDelta; 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.0

Full details in the CHANGELOG.