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
15 changes: 15 additions & 0 deletions packages/melonjs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## [20.0.0] (melonJS 2) - _unreleased_

### Changed (breaking)
- **The WebGL renderer is now WebGL 2 only** ([#1509](https://github.com/melonjs/melonJS/issues/1509)) — the WebGL 1 fallback path is removed. `renderer: video.AUTO` falls back to the Canvas renderer on WebGL-1-only devices; `renderer: video.WEBGL` throws there. **User shaders need no changes** — `ShaderEffect` bodies and raw `GLShader` sources (GLSL ES 1.00) compile unchanged on WebGL 2 contexts.
- `preferWebGL1` setting and the `#webgl1` URI flag are removed (`#webgl` / `#webgl2` are synonyms)
- `device.isWebGLSupported()` now probes for a WebGL **2** context — it finally agrees with what renderer construction actually requests (the gate probed WebGL 1 before, so the two could disagree)
- `renderer.type` is always `"WebGL2"` for the WebGL renderer; `renderer.WebGLVersion` is deprecated (always `2`)
- behavior corrections on ex-WebGL-1 configs: `repeat` wrap now genuinely tiles non-power-of-two textures (was clamp + warning), `"darken"` / `"lighten"` blend modes use true MIN/MAX equations (were silently downgraded to `"normal"`), and `createPattern()` accepts non-power-of-two sources (threw before)
- TMX GPU tilemap eligibility is now advertised through `renderer.supportsShaderTileLayers` (a backend capability flag) instead of a WebGL-version check
- the WebGL renderer is now selected only on devices providing a WebGL 2 context **and** passing the `failIfMajorPerformanceCaveat` check (which melonJS leaves enabled by default, unlike the WebGL default of `false`) — a software rasterizer or blocklisted driver therefore gets the Canvas renderer under `video.AUTO`, and throws under `video.WEBGL`. Set `failIfMajorPerformanceCaveat: false` to accept such a context
- **each `Batcher` now owns an immutable Vertex Array Object** built at init ([#1509](https://github.com/melonjs/melonJS/issues/1509)): vertex attribute layout is frozen once built, batcher switches cost a single `bindVertexArray` (steady-state frames issue zero attribute-specification calls, measured), and `Batcher.unbind()` no longer disables attribute arrays. Custom batchers inheriting `Batcher.init()`/`bind()` need no changes. Custom shaders hosted by a built-in batcher must declare that batcher's attributes first, in layout order (ShaderEffect-generated vertex shaders already comply) — a console warning fires on mismatch. `GLShader.setVertexAttributes` is no longer called by the engine (still public)

### Performance
- **Vertex Array Objects for every batcher** ([#1509](https://github.com/melonjs/melonJS/issues/1509)) — vertex attribute layout is specified once at init rather than on every batcher switch and every mesh flush, so steady-state frames issue **zero** attribute-specification calls. How much GL traffic this saves depends on how often a scene alternates batchers: a scene that stays on one batcher saves about 2 calls per frame, one mixing sprites, meshes and primitives about 40 — in both cases well under a millisecond. The structural benefit is the larger one: attribute-state leaks between batchers become impossible by construction.

## [19.9.1] (melonJS 2) - _2026-07-28_

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion packages/melonjs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "melonjs",
"version": "19.9.1",
"version": "20.0.0",
"description": "melonJS Game Engine",
"homepage": "http://www.melonjs.org/",
"type": "module",
Expand Down
20 changes: 8 additions & 12 deletions packages/melonjs/src/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
if (physic === "none") {
return { adapter: undefined, physicLabel: "none" };
}
if (physic === undefined || physic === "builtin") {

Check warning on line 95 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, the types have no overlap

Check warning on line 95 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, the types have no overlap

Check warning on line 95 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Unnecessary conditional, the types have no overlap
return { adapter: undefined, physicLabel: "builtin" };
}
// instance or { adapter } object — extract and pass through. The
Expand All @@ -102,7 +102,7 @@
// predating the `physicLabel` field.
const adapter =
typeof physic === "object" && "adapter" in physic ? physic.adapter : physic;
return { adapter, physicLabel: adapter?.physicLabel ?? "builtin" };

Check warning on line 105 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary optional chain on a non-nullish value

Check warning on line 105 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary optional chain on a non-nullish value

Check warning on line 105 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Unnecessary optional chain on a non-nullish value
}

/**
Expand Down Expand Up @@ -316,16 +316,11 @@
} as ResolvedApplicationSettings;

// override renderer settings if &webgl or &canvas is defined in the URL
// (the WebGL renderer is WebGL 2 only since 20.0 — the legacy
// `#webgl1` flag is gone, `#webgl`/`#webgl2` are synonyms)
const uriFragment = getUriFragment();
if (
uriFragment.webgl === true ||
uriFragment.webgl1 === true ||
uriFragment.webgl2 === true
) {
if (uriFragment.webgl === true || uriFragment.webgl2 === true) {
settings.renderer = WEBGL;
if (uriFragment.webgl1 === true) {
settings.preferWebGL1 = true;
}
} else if (uriFragment.canvas === true) {
settings.renderer = CANVAS;
}
Expand All @@ -337,7 +332,7 @@
this.settings = settings;

// identify parent element and/or the html target for resizing
this.parentElement = device.getElement(this.settings.parent!);

Check warning on line 335 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Forbidden non-null assertion

Check warning on line 335 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Forbidden non-null assertion

Check warning on line 335 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Forbidden non-null assertion
if (typeof this.settings.scaleTarget !== "undefined") {
this.settings.scaleTarget = device.getElement(this.settings.scaleTarget);
}
Expand Down Expand Up @@ -381,10 +376,11 @@
if (!device.isWebGLSupported(this.settings)) {
throw new Error(
"Application: `renderer: video.WEBGL` was requested " +
"but the WebGL context could not be created in this " +
"environment (driver-blocklisted GPU, " +
"`failIfMajorPerformanceCaveat: true` on a software " +
"renderer, etc.). Use `video.AUTO` if Canvas " +
"but a WebGL 2 context could not be created in this " +
"environment (WebGL-1-only device, driver-blocklisted " +
"GPU, `failIfMajorPerformanceCaveat: true` on a " +
"software renderer, etc.). The WebGL renderer is " +
"WebGL 2 only since 20.0. Use `video.AUTO` if Canvas " +
"fallback is acceptable, or check " +
"`device.isWebGLSupported()` before construction.",
);
Expand Down Expand Up @@ -558,7 +554,7 @@
if (this.settings.consoleHeader) {
if (this.world.physic === "none") {
console.log("physics: disabled");
} else if (this.world.adapter) {

Check warning on line 557 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy

Check warning on line 557 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy

Check warning on line 557 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Unnecessary conditional, value is always truthy
const a = this.world.adapter as {
constructor: { name: string };
name?: string;
Expand Down Expand Up @@ -618,7 +614,7 @@
// point to the current active stage "default" camera
const current = state.get();
if (typeof current !== "undefined") {
this.viewport = current.cameras.get("default")!;

Check warning on line 617 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Forbidden non-null assertion

Check warning on line 617 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Forbidden non-null assertion

Check warning on line 617 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Forbidden non-null assertion
}

// publish reset notification
Expand Down Expand Up @@ -712,12 +708,12 @@
this.parentElement) as ElementWithLegacyFullscreen;
/* eslint-disable @typescript-eslint/unbound-method -- `this` is restored explicitly via `.call(target)` below */
const request =
target.requestFullscreen ||

Check warning on line 711 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy

Check warning on line 711 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy

Check warning on line 711 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Unnecessary conditional, value is always truthy
target.webkitRequestFullscreen ||
target.mozRequestFullScreen ||
target.msRequestFullscreen;
/* eslint-enable @typescript-eslint/unbound-method */
const result = request?.call(target);

Check warning on line 716 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary optional chain on a non-nullish value

Check warning on line 716 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary optional chain on a non-nullish value

Check warning on line 716 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Unnecessary optional chain on a non-nullish value
if (result instanceof Promise) result.catch(console.error);
}
}
Expand All @@ -731,12 +727,12 @@
const doc = globalThis.document as DocumentWithLegacyExitFullscreen;
/* eslint-disable @typescript-eslint/unbound-method -- `this` is restored explicitly via `.call(doc)` below */
const exit =
doc.exitFullscreen ||

Check warning on line 730 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy

Check warning on line 730 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy

Check warning on line 730 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Unnecessary conditional, value is always truthy
doc.webkitExitFullscreen ||
doc.mozCancelFullScreen ||
doc.msExitFullscreen;
/* eslint-enable @typescript-eslint/unbound-method */
const result = exit?.call(doc);

Check warning on line 735 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary optional chain on a non-nullish value

Check warning on line 735 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary optional chain on a non-nullish value

Check warning on line 735 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Unnecessary optional chain on a non-nullish value
if (result instanceof Promise) result.catch(console.error);
}

Expand Down Expand Up @@ -788,7 +784,7 @@
globalThis.removeEventListener("resize", this._onResize);
globalThis.removeEventListener(
"orientationchange",
this._onOrientationChange!,

Check warning on line 787 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Forbidden non-null assertion

Check warning on line 787 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / lint

Forbidden non-null assertion

Check warning on line 787 in packages/melonjs/src/application/application.ts

View workflow job for this annotation

GitHub Actions / test

Forbidden non-null assertion
);
globalThis.removeEventListener("scroll", this._onScroll!);
if (device.screenOrientation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export const defaultApplicationSettings = {
renderer: AUTO,
scale: 1.0,
scaleMethod: ScaleMethods.Manual,
preferWebGL1: false,
powerPreference: "default",
transparent: false,
antiAlias: false,
Expand Down
51 changes: 32 additions & 19 deletions packages/melonjs/src/application/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,21 @@ type PhysicsType =
| PhysicsAdapter
| { adapter: PhysicsAdapter };

type PowerPreference = "default" | "low-power";
type PowerPreference = "default" | "low-power" | "high-performance";

export type ApplicationSettings = {
/**
* Renderer to use. Three built-in modes (constants from `me.video`):
*
* - {@link CANVAS} — HTML5 Canvas backend. No shader / mesh /
* Camera3d support.
* - {@link WEBGL} — **requires WebGL**. Throws at `new Application(...)`
* time if WebGL is unavailable (driver-blocklisted GPU, perf-caveat
* failure, etc.). Use this when your scene needs Camera3d, Mesh,
* ShaderEffect, Light2d or GPU tilemap — you'd rather fail fast
* than render a stuck blank canvas.
* - {@link AUTO} — try WebGL, silently fall back to Canvas if
* - {@link WEBGL} — **requires WebGL 2** (the WebGL renderer is
* WebGL 2 only since 20.0). Throws at `new Application(...)`
* time if a WebGL 2 context is unavailable (WebGL-1-only device,
* driver-blocklisted GPU, perf-caveat failure, etc.). Use this when
* your scene needs Camera3d, Mesh, ShaderEffect, Light2d or GPU
* tilemap — you'd rather fail fast than render a stuck blank canvas.
* - {@link AUTO} — try WebGL 2, silently fall back to Canvas if
* unavailable. Application construction always succeeds. The
* WebGL-only subsystems (Camera3d, Mesh, ShaderEffect, Light2d,
* GPU tilemap) silently stop working under the Canvas fallback —
Expand Down Expand Up @@ -74,14 +75,20 @@ export type ApplicationSettings = {
scaleTarget: HTMLElement;

/**
* if true the renderer will only use WebGL 1
* @default false
*/
preferWebGL1: boolean;

/**
* a hint to the user agent indicating what configuration of GPU is suitable for the WebGL context. To be noted that Safari and Chrome (since version 80) both default to "low-power" to save battery life and improve the user experience on these dual-GPU machines.
* @default default
* A hint to the user agent about which GPU to use on multi-GPU systems
* (discrete vs integrated). Browsers generally favour the low-power GPU
* unless asked otherwise, to preserve battery life.
*
* - `"default"` — no hint; let the user agent decide.
* - `"low-power"` — prefer the integrated GPU.
* - `"high-performance"` — prefer the discrete GPU. Note that browsers
* only honour this for pages that handle context loss, since switching
* GPU can drop the context; melonJS registers those handlers itself,
* so the request is respected.
*
* The same hint (and the same values, minus `"default"`) is used by
* WebGPU's adapter request, so this setting is backend-neutral.
* @default "default"
*/
powerPreference: PowerPreference;

Expand Down Expand Up @@ -166,17 +173,23 @@ export type ApplicationSettings = {
* When `true` (default), eligible layers render via a single quad per
* tileset + a fragment shader doing per-fragment GID lookup, bypassing
* the per-tile draw loop entirely. Layers that don't qualify
* (Canvas/WebGL1, non-orthogonal, collection-of-image tilesets,
* (Canvas renderer, non-orthogonal, collection-of-image tilesets,
* tilerendersize "grid", non-zero tileoffset, oversampled beyond the
* shader's overflow window) fall back to the legacy path automatically.
* Set to `false` to disable globally.
* @default true
*/
gpuTilemap: boolean;
/**
* if true, the renderer will fail if the browser reports a major performance caveat
* (e.g. software WebGL). Set to false to allow WebGL on machines with
* blocklisted GPU drivers or software renderers.
* If true, treat WebGL as unavailable when the browser warns that a
* context would perform dramatically worse than a native application
* (a software rasterizer, a blocklisted driver). Note this is stricter
* than the WebGL default, which is `false`.
*
* Combined with the WebGL 2 requirement, the effect is: under
* {@link AUTO} such a machine gets the Canvas renderer, and under
* {@link WEBGL} construction throws. Set to `false` to accept a
* software or blocklisted WebGL context instead.
* @default true
*/
failIfMajorPerformanceCaveat: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/melonjs/src/lang/deprecated.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class CanvasTexture extends CanvasRenderTarget {
* @param {number} width - the desired width of the canvas
* @param {number} height - the desired height of the canvas
* @param {object} attributes - The attributes to create both the canvas and context
* @param {boolean} [attributes.context="2d"] - the context type to be created ("2d", "webgl", "webgl2")
* @param {boolean} [attributes.context="2d"] - the context type to be created ("2d", "webgl" — creates a WebGL 2 context)
* @param {boolean} [attributes.offscreenCanvas=false] - will create an offscreenCanvas if true instead of a standard canvas
* @param {boolean} [attributes.willReadFrequently=false] - Indicates whether or not a lot of read-back operations are planned
* @param {boolean} [attributes.antiAlias=false] - Whether to enable anti-aliasing, use false (default) for a pixelated effect.
Expand Down
14 changes: 7 additions & 7 deletions packages/melonjs/src/level/tiled/TMXLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const FLIP_AD_BIT = 1 << 2;
// the fallback so apps without any tilemap stay quiet (the prior heads-up
// at Application init time fired regardless of whether a TMX layer was
// ever loaded).
let _warnedNoWebGL2Once = false;
let _warnedNoGpuTileSupportOnce = false;

/**
* extract a 3-bit flip mask from a raw 32-bit GID (Tiled's flip bits live in
Expand Down Expand Up @@ -409,11 +409,11 @@ export default class TMXLayer extends Renderable {
// — emitted from the first TMX layer that hits it, so apps with
// no tilemap loaded stay quiet.
if (gpuAllowed) {
if (elig.reason === "no-webgl2-renderer") {
if (!_warnedNoWebGL2Once) {
_warnedNoWebGL2Once = true;
if (elig.reason === "no-gpu-renderer") {
if (!_warnedNoGpuTileSupportOnce) {
_warnedNoGpuTileSupportOnce = true;
console.warn(
"melonJS: gpuTilemap is enabled but the active renderer is not WebGL 2 — falling back to the legacy tile renderer for every tile layer",
"melonJS: gpuTilemap is enabled but the active renderer has no GPU tile-layer support — falling back to the legacy tile renderer for every tile layer",
);
}
} else {
Expand All @@ -440,8 +440,8 @@ export default class TMXLayer extends Renderable {
if (!gpuAllowed) {
return { ok: false, reason: "gpuTilemap disabled" };
}
if (!renderer || renderer.WebGLVersion !== 2) {
return { ok: false, reason: "no-webgl2-renderer" };
if (!renderer || renderer.supportsShaderTileLayers !== true) {
return { ok: false, reason: "no-gpu-renderer" };
}
if (this.orientation !== "orthogonal") {
return {
Expand Down
20 changes: 14 additions & 6 deletions packages/melonjs/src/system/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,10 +637,15 @@ export function getParentBounds(element: string | HTMLElement) {
}

/**
* returns true if the device supports WebGL
* Returns true if the device supports WebGL 2 — the version the WebGL
* renderer requires since 20.0. This probes the same context type that
* renderer construction requests, so the two can never disagree.
*
* When it returns false, `renderer: video.AUTO` falls back to the Canvas
* renderer and `renderer: video.WEBGL` throws at construction time.
* @param [options] - context creation options
* @param [options.failIfMajorPerformanceCaveat=true] - If true, the renderer will switch to CANVAS mode if the performances of a WebGL context would be dramatically lower than that of a native application making equivalent OpenGL calls.
* @returns true if WebGL is supported
* @param [options.failIfMajorPerformanceCaveat=true] - if true, report WebGL as unsupported when the browser warns that a context would perform dramatically worse than a native application (e.g. a software rasterizer). Set false to accept WebGL on blocklisted drivers or software renderers.
* @returns true if WebGL 2 is supported
* @category Application
*/
export function isWebGLSupported(options?: {
Expand All @@ -655,10 +660,13 @@ export function isWebGLSupported(options?: {
failIfMajorPerformanceCaveat: options.failIfMajorPerformanceCaveat,
}),
};
// WebGL 2 only since 20.0 (#1509) — this gate must agree with
// the context actually created by the WebGL renderer (which
// requests "webgl2"), so AUTO falls back to Canvas exactly when
// renderer construction would fail
const _supported = !!(
globalThis.WebGLRenderingContext &&
(canvas.getContext("webgl", ctxOptions) ||
canvas.getContext("experimental-webgl", ctxOptions))
globalThis.WebGL2RenderingContext &&
canvas.getContext("webgl2", ctxOptions)
);
WebGLSupport = _supported ? 1 : 0;
} catch {
Expand Down
11 changes: 11 additions & 0 deletions packages/melonjs/src/video/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ export default class Renderer {
*/
this.type = "Generic";

/**
* Whether this renderer backend can draw TMX tile layers through a
* GPU shader path (see the `gpuTilemap` application setting).
* `false` here on the base/Canvas renderer; GPU backends flip it.
* A capability flag rather than a version/class check so future
* backends advertise support without consumers growing type checks.
* @type {boolean}
* @default false
*/
this.supportsShaderTileLayers = false;

/**
* The background color used to clear the main framebuffer.
* Note: alpha value will be set based on the transparent property of the renderer settings.
Expand Down
37 changes: 14 additions & 23 deletions packages/melonjs/src/video/rendertarget/canvasrendertarget.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,9 @@ const defaultAttributes = {
stencil: true,
blendMode: "normal",
failIfMajorPerformanceCaveat: true,
preferWebGL1: false,
powerPreference: "default",
};

// WebGL version (if a gl context is created)
let WebGLVersion;

// a helper function to create the 2d/webgl context
function createContext(canvas, attributes) {
let context;
Expand All @@ -50,24 +46,13 @@ function createContext(canvas, attributes) {
failIfMajorPerformanceCaveat: attributes.failIfMajorPerformanceCaveat,
};

// attempt to create a WebGL2 context unless not requested
if (attributes.preferWebGL1 !== true) {
context = canvas.getContext("webgl2", attr);
if (context) {
WebGLVersion = 2;
}
}
// WebGL 2 only since 20.0 (#1509) — no WebGL 1 fallback; callers
// (autoDetectRenderer / Application) fall back to the Canvas
// renderer instead when this throws
context = canvas.getContext("webgl2", attr);

// fallback to WebGL1
if (!context) {
WebGLVersion = 1;
context =
canvas.getContext("webgl", attr) ||
canvas.getContext("experimental-webgl", attr);
}

if (!context) {
throw new Error("A WebGL context could not be created.");
throw new Error("A WebGL 2 context could not be created.");
}
} else {
throw new Error("Invalid context type. Must be one of '2d' or 'webgl'");
Expand All @@ -89,8 +74,7 @@ class CanvasRenderTarget extends RenderTarget {
* @param {number} width - the desired width of the canvas
* @param {number} height - the desired height of the canvas
* @param {object} attributes - The attributes to create both the canvas and context
* @param {string} [attributes.context="2d"] - the context type to be created ("2d", "webgl")
* @param {boolean} [attributes.preferWebGL1=false] - set to true for force using WebGL1 instead of WebGL2 (if supported)
* @param {string} [attributes.context="2d"] - the context type to be created ("2d", "webgl" — a "webgl" request creates a WebGL 2 context)
* @param {boolean} [attributes.transparent=false] - specify if the canvas contains an alpha channel
* @param {boolean} [attributes.offscreenCanvas=false] - will create an offscreenCanvas if true instead of a standard canvas
* @param {boolean} [attributes.willReadFrequently=false] - Indicates whether or not a lot of read-back operations are planned
Expand Down Expand Up @@ -120,7 +104,14 @@ class CanvasRenderTarget extends RenderTarget {
// create the context
this.context = createContext(this.canvas, this.attributes);

this.WebGLVersion = WebGLVersion;
if (this.attributes.context === "webgl") {
/**
* The WebGL version of the created context.
* @type {number}
* @deprecated since 20.0.0 — the WebGL renderer is WebGL 2 only, this is always `2`
*/
this.WebGLVersion = 2;
}

// enable or disable antiAlias if specified
this.setAntiAlias(this.attributes.antiAlias);
Expand Down
17 changes: 4 additions & 13 deletions packages/melonjs/src/video/rendertarget/webglrendertarget.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
import RenderTarget from "./rendertarget.ts";

// `DEPTH_STENCIL` (0x84F9) and `DEPTH_STENCIL_ATTACHMENT` (0x821A) are core
// WebGL 1.0 constants per the spec — no extension required. They're exposed
// natively on WebGL2 contexts and on most WebGL1 implementations, but a few
// browsers/drivers leave one or both as `undefined` on the gl context.
// Passing `undefined` to `renderbufferStorage` / `framebufferRenderbuffer`
// silently produces an incomplete framebuffer (no error), so we fall back
// to the spec-defined numeric values when the context lookup is missing.
const DEPTH_STENCIL = 0x84f9;
const DEPTH_STENCIL_ATTACHMENT = 0x821a;

/**
* Try to attach a depth+stencil (preferred) or depth-only renderbuffer to
* the currently-bound framebuffer, validating completeness at each step.
Expand All @@ -21,9 +11,10 @@ const DEPTH_STENCIL_ATTACHMENT = 0x821a;
* @ignore
*/
function attachDepthStencil(gl, framebuffer, renderbuffer, width, height) {
const depthStencil = gl.DEPTH_STENCIL ?? DEPTH_STENCIL;
const depthStencilAttachment =
gl.DEPTH_STENCIL_ATTACHMENT ?? DEPTH_STENCIL_ATTACHMENT;
// core WebGL 2 constants — always present on the context (the WebGL 1
// driver-gap numeric fallbacks were removed with WebGL 1 support)
const depthStencil = gl.DEPTH_STENCIL;
const depthStencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT;

// preferred path: packed depth + stencil so masking works
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
Expand Down
Loading