From 1c13008513f2361fedba8e23d71cdbc73142416e Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Sun, 29 Mar 2026 08:40:31 +0800 Subject: [PATCH 1/5] WebGLRenderer: rebind shared vertex buffer on batcher switch setBatcher() now rebinds the renderer's shared vertex buffer before activating a new batcher. This allows custom batchers that use their own GL buffers (e.g. SpineBatcher) to integrate without manual cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../melonjs/src/video/webgl/webgl_renderer.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/melonjs/src/video/webgl/webgl_renderer.js b/packages/melonjs/src/video/webgl/webgl_renderer.js index 0556ea81d..e0ab7d983 100644 --- a/packages/melonjs/src/video/webgl/webgl_renderer.js +++ b/packages/melonjs/src/video/webgl/webgl_renderer.js @@ -358,6 +358,8 @@ export default class WebGLRenderer extends Renderer { // flush the current batcher this.currentBatcher.flush(); } + // rebind the renderer's shared vertex buffer (custom batchers may have bound their own) + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); this.currentBatcher = batcher; this.currentBatcher.bind(); } @@ -744,15 +746,19 @@ export default class WebGLRenderer extends Renderer { * and will always fall back to "normal" in WebGL.
* @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation * @param {string} [mode="normal"] - blend mode - * @param {WebGLRenderingContext} [gl] - a WebGL context + * @param {boolean} [premultipliedAlpha=true] - whether textures use premultiplied alpha (affects source blend factor) * @returns {string} the blend mode actually applied (may differ if the requested mode is unsupported) */ - setBlendMode(mode = "normal", gl = this.gl) { + setBlendMode(mode = "normal", premultipliedAlpha = true) { if (this.currentBlendMode !== mode) { + const gl = this.gl; this.flush(); gl.enable(gl.BLEND); this.currentBlendMode = mode; + // source factor depends on whether textures use premultiplied alpha + const srcAlpha = premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA; + switch (mode) { case "screen": gl.blendEquation(gl.FUNC_ADD); @@ -763,7 +769,7 @@ export default class WebGLRenderer extends Renderer { case "additive": case "add": gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.ONE, gl.ONE); + gl.blendFunc(srcAlpha, gl.ONE); break; case "multiply": @@ -777,7 +783,7 @@ export default class WebGLRenderer extends Renderer { gl.blendFunc(gl.ONE, gl.ONE); } else { gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.blendFunc(srcAlpha, gl.ONE_MINUS_SRC_ALPHA); this.currentBlendMode = "normal"; } break; @@ -788,14 +794,14 @@ export default class WebGLRenderer extends Renderer { gl.blendFunc(gl.ONE, gl.ONE); } else { gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.blendFunc(srcAlpha, gl.ONE_MINUS_SRC_ALPHA); this.currentBlendMode = "normal"; } break; default: gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.blendFunc(srcAlpha, gl.ONE_MINUS_SRC_ALPHA); this.currentBlendMode = "normal"; break; } From 5a828f9437707a79afc97ce0b67dc4fd117ba7c7 Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Sun, 29 Mar 2026 08:40:59 +0800 Subject: [PATCH 2/5] Renderer: add base setBlendMode/GPURenderer, clean up as-any casts - Add setBlendMode() to base Renderer class for TypeScript compatibility - Add GPURenderer property to base Renderer (WebGL overrides with GPU string) - Remove renderer as-any casts in header.ts and resize.ts - Remove unused context param from Canvas setBlendMode and resize.ts Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/melonjs/src/application/header.ts | 7 +++---- packages/melonjs/src/application/resize.ts | 3 +-- .../melonjs/src/video/canvas/canvas_renderer.js | 5 ++--- packages/melonjs/src/video/renderer.js | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/melonjs/src/application/header.ts b/packages/melonjs/src/application/header.ts index de971c24d..c5dfe598f 100644 --- a/packages/melonjs/src/application/header.ts +++ b/packages/melonjs/src/application/header.ts @@ -8,12 +8,11 @@ import type Application from "./application.ts"; export function consoleHeader(app: Application): void { const renderType = app.renderer.type; const gpu_renderer = - typeof (app.renderer as any).GPURenderer === "string" - ? ` (${(app.renderer as any).GPURenderer})` + typeof app.renderer.GPURenderer === "string" + ? ` (${app.renderer.GPURenderer})` : ""; const depthTesting = - renderType.includes("WebGL") && - (app.renderer as any).depthTest === "z-buffer" + renderType.includes("WebGL") && app.renderer.depthTest === "z-buffer" ? "Depth Test | " : ""; const audioType = device.hasWebAudio ? "Web Audio" : "HTML5 Audio"; diff --git a/packages/melonjs/src/application/resize.ts b/packages/melonjs/src/application/resize.ts index 64df1b99a..deaf0130c 100644 --- a/packages/melonjs/src/application/resize.ts +++ b/packages/melonjs/src/application/resize.ts @@ -12,7 +12,6 @@ import type Application from "./application.ts"; function scale(game: Application, x: number, y: number): void { const renderer = game.renderer; const canvas = renderer.getCanvas(); - const context = renderer.getContext(); const settings = renderer.settings as any; const pixelRatio = device.devicePixelRatio; @@ -28,7 +27,7 @@ function scale(game: Application, x: number, y: number): void { // if anti-alias and blend mode were reset (e.g. Canvas mode) renderer.setAntiAlias(settings.antiAlias); - (renderer as any).setBlendMode(settings.blendMode, context); + renderer.setBlendMode(settings.blendMode); // force repaint game.repaint(); diff --git a/packages/melonjs/src/video/canvas/canvas_renderer.js b/packages/melonjs/src/video/canvas/canvas_renderer.js index c49413a49..34fc91679 100644 --- a/packages/melonjs/src/video/canvas/canvas_renderer.js +++ b/packages/melonjs/src/video/canvas/canvas_renderer.js @@ -140,11 +140,10 @@ export default class CanvasRenderer extends Renderer { *
* @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation * @param {string} [mode="normal"] - blend mode - * @param {CanvasRenderingContext2D} [context] * @returns {string} the blend mode actually applied (may differ if the requested mode is unsupported) */ - setBlendMode(mode = "normal", context) { - context = context || this.getContext(); + setBlendMode(mode = "normal") { + const context = this.getContext(); this.currentBlendMode = mode; switch (mode) { case "lighter": diff --git a/packages/melonjs/src/video/renderer.js b/packages/melonjs/src/video/renderer.js index 7d7fc87ec..d45e81514 100644 --- a/packages/melonjs/src/video/renderer.js +++ b/packages/melonjs/src/video/renderer.js @@ -73,6 +73,12 @@ export default class Renderer { */ this.depthTest = "sorting"; + /** + * The GPU renderer string (WebGL only, undefined for Canvas) + * @type {string|undefined} + */ + this.GPURenderer = undefined; + /** * The Path2D instance used by the renderer to draw primitives * @type {Path2D} @@ -240,6 +246,16 @@ export default class Renderer { return this.currentBlendMode; } + /** + * set the current blend mode. + * Subclasses (CanvasRenderer, WebGLRenderer) implement the actual GL/Canvas logic. + * @param {string} [mode="normal"] - blend mode + * @param {boolean} [premultipliedAlpha=true] - whether textures use premultiplied alpha (WebGL only) + */ + setBlendMode(mode = "normal") { + this.currentBlendMode = mode; + } + /** * get the current fill & stroke style color. * @returns {Color} current global color From 3d515d37e576768e667e048dbec9dd2f4e678496 Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Sun, 29 Mar 2026 08:41:13 +0800 Subject: [PATCH 3/5] Plugin: use pluginClass.name for reliable class name extraction plugin.register() now uses pluginClass.name before falling back to the toString() regex. Fixes class name extraction when bundlers (e.g. esbuild) strip class names from minified output, which caused "plugin extends already registered" errors when registering multiple plugins. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/melonjs/src/plugin/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/melonjs/src/plugin/plugin.ts b/packages/melonjs/src/plugin/plugin.ts index 639cc9b3e..635be4632 100644 --- a/packages/melonjs/src/plugin/plugin.ts +++ b/packages/melonjs/src/plugin/plugin.ts @@ -95,7 +95,8 @@ export function register( ...args: any[] ): void { // derive name from class if not provided - const pluginName = name || pluginClass.toString().match(/ (\w+)/)![1]; + const pluginName = + name || pluginClass.name || pluginClass.toString().match(/ (\w+)/)![1]; // ensure me.plugins[name] is not already "used" if (cache[pluginName]) { From 9a4a7e204ff40137e8891fc4f72f3909f0826790 Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Sun, 29 Mar 2026 08:41:25 +0800 Subject: [PATCH 4/5] Update CHANGELOG with renderer and plugin fixes Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/melonjs/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/melonjs/CHANGELOG.md b/packages/melonjs/CHANGELOG.md index 09f9e4a35..e719541db 100644 --- a/packages/melonjs/CHANGELOG.md +++ b/packages/melonjs/CHANGELOG.md @@ -26,6 +26,10 @@ - TypeScript: convert application, input, and UI modules to TypeScript — application, header, resize, input, pointer, pointerevent, gamepad, uibaseelement, uispriteelement, uitextbutton ### Fixed +- WebGLRenderer: `setBatcher()` now rebinds the shared vertex buffer when switching batchers, allowing custom batchers with their own GL buffers to integrate without manual cleanup +- WebGLRenderer: `setBlendMode()` now accepts a `premultipliedAlpha` parameter (default `true`) for correct blending with non-premultiplied textures; removed internal `gl`/`context` parameter from both WebGL and Canvas renderers +- Renderer: add base `setBlendMode()` and `GPURenderer` property to fix TypeScript `as any` casts in `header.ts` and `resize.ts` +- Plugin: `plugin.register()` now uses `pluginClass.name` for name derivation, fixing class name extraction when bundlers strip class names - TMXTileset: fix animation key using first frame tileid instead of the tile's own id - CanvasRenderer: replace bezier ellipse approximation with native `context.ellipse()` (with polyfill for older browsers) - Plugin: fix `plugin.get()` throwing `TypeError` when searching by name with no match (instanceof on string) From b485a09c600f3598c680e3d874efe6418dc9a3e7 Mon Sep 17 00:00:00 2001 From: Olivier Biot Date: Sun, 29 Mar 2026 09:51:48 +0800 Subject: [PATCH 5/5] Fix setBlendMode JSDoc: remove stale "given context" wording Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/melonjs/src/video/canvas/canvas_renderer.js | 2 +- packages/melonjs/src/video/webgl/webgl_renderer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/melonjs/src/video/canvas/canvas_renderer.js b/packages/melonjs/src/video/canvas/canvas_renderer.js index 34fc91679..9918e83c1 100644 --- a/packages/melonjs/src/video/canvas/canvas_renderer.js +++ b/packages/melonjs/src/video/canvas/canvas_renderer.js @@ -108,7 +108,7 @@ export default class CanvasRenderer extends Renderer { } /** - * set a blend mode for the given context.
+ * set the current blend mode for this renderer.
* All renderers support:
* - "normal" : draws new content on top of the existing content
*
diff --git a/packages/melonjs/src/video/webgl/webgl_renderer.js b/packages/melonjs/src/video/webgl/webgl_renderer.js index e0ab7d983..89b72fb29 100644 --- a/packages/melonjs/src/video/webgl/webgl_renderer.js +++ b/packages/melonjs/src/video/webgl/webgl_renderer.js @@ -726,7 +726,7 @@ export default class WebGLRenderer extends Renderer { } /** - * set a blend mode for the given context.
+ * set the current blend mode for this renderer.
* All renderers support:
* - "normal" : draws new content on top of the existing content
*