Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/melonjs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 3 additions & 4 deletions packages/melonjs/src/application/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
3 changes: 1 addition & 2 deletions packages/melonjs/src/application/resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Expand Down
3 changes: 2 additions & 1 deletion packages/melonjs/src/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Comment on lines +98 to +99
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pluginName derivation can still produce incorrect names for anonymous classes. For example, plugin.register(class extends BasePlugin {}) yields pluginClass.name === "" and the regex on toString() will typically match "extends", causing collisions like "plugin extends already registered". Consider validating the derived name (and/or guarding against regex match failure) and throwing a clear error requiring an explicit name when it can’t be reliably derived.

Copilot uses AI. Check for mistakes.

// ensure me.plugins[name] is not already "used"
if (cache[pluginName]) {
Expand Down
7 changes: 3 additions & 4 deletions packages/melonjs/src/video/canvas/canvas_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default class CanvasRenderer extends Renderer {
}

/**
* set a blend mode for the given context. <br>
* set the current blend mode for this renderer. <br>
* All renderers support: <br>
* - "normal" : draws new content on top of the existing content <br>
* <img src="../images/normal-blendmode.png" width="180"/> <br>
Expand Down Expand Up @@ -140,11 +140,10 @@ export default class CanvasRenderer extends Renderer {
* <img src="../images/exclusion-blendmode.png" width="180"/> <br>
* @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":
Expand Down
16 changes: 16 additions & 0 deletions packages/melonjs/src/video/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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") {
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc for Renderer#setBlendMode documents an optional premultipliedAlpha parameter, but the method signature only declares mode. If this is meant to provide TypeScript compatibility with WebGLRenderer#setBlendMode(mode, premultipliedAlpha), the base method should accept the second optional parameter as well (even if it’s unused), otherwise TS users calling renderer.setBlendMode(mode, false) won’t type-check against the base class.

Suggested change
setBlendMode(mode = "normal") {
setBlendMode(mode = "normal", premultipliedAlpha = true) { // premultipliedAlpha is unused in the base renderer

Copilot uses AI. Check for mistakes.
this.currentBlendMode = mode;
}

/**
* get the current fill & stroke style color.
* @returns {Color} current global color
Expand Down
20 changes: 13 additions & 7 deletions packages/melonjs/src/video/webgl/webgl_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -724,7 +726,7 @@ export default class WebGLRenderer extends Renderer {
}

/**
* set a blend mode for the given context. <br>
* set the current blend mode for this renderer. <br>
* All renderers support: <br>
* - "normal" : draws new content on top of the existing content <br>
* <img src="../images/normal-blendmode.png" width="180"/> <br>
Expand All @@ -744,15 +746,19 @@ export default class WebGLRenderer extends Renderer {
* and will always fall back to "normal" in WebGL. <br>
* @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;
Comment on lines +752 to +760
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setBlendMode(mode, premultipliedAlpha) only updates GL state when mode changes. If callers switch premultipliedAlpha (e.g. rendering non-PMA textures after PMA ones) while keeping the same mode, the blend func will not be updated and blending will be wrong. Consider tracking the last applied premultipliedAlpha (or last srcAlpha factor) and include it in the early-exit condition, or always reapply blendEquation/blendFunc when premultipliedAlpha differs.

Copilot uses AI. Check for mistakes.

switch (mode) {
case "screen":
gl.blendEquation(gl.FUNC_ADD);
Expand All @@ -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":
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
Loading