the CSS background color of the parent element that holds the canvas.
Applied during initialization to prevent a white flash before the first render.
Set to "transparent" to disable, or any valid CSS color value.
Optionalbatcher?: new (renderer: any) => WebGLBatchera custom batcher class, riding the quad/primitive slots on either
GPU backend — extend WebGLBatcher under WebGL, WebGPUBatcher
under WebGPU (addBatcher rejects a wrong-backend class loudly)
the default blend mode to use. Both GPU renderers support the full set;
the Canvas fallback supports every mode except "none" — see
CanvasRenderer#setBlendMode for the list.
OptionalcameraClass?: new (minX: number, minY: number, maxX: number, maxY: number) => Camera2dDefault camera class instantiated for any Stage that does not
explicitly provide its own cameras. Set to Camera3d to opt
every stage in the app into perspective rendering by default. Stages
can still override per-instance via super({ cameras: [...] }) or
per-class via super({ cameraClass: Camera2d }). Built-in stages
(e.g. the loader screen) explicitly use Camera2d regardless
of this setting.
GPU-backend requirement. Camera classes whose
static defaultSortOn === "depth" (Camera3d and any subclass) need
a renderer with a depth buffer (renderer.supportsDepthBuffer —
WebGL 2 or WebGPU). Pairing such a cameraClass with
renderer: video.AUTO on a system where AUTO falls back to Canvas
emits a console.warn and produces a non-functional render. Pin
renderer: video.WEBGL (or WEBGPU) to make app.init() reject
instead.
whether 3D objects cast a soft "blob" shadow on the ground by default (#1515).
A ground shadow is what stops a character or prop reading as floating in a 2.5D scene — it answers "where is this standing", not "where is the light". It is deliberately not a simulated shadow; see Mesh#castGroundShadow.
This is the default for every Mesh, Sprite3d and
InstancedMesh; each can override it with its own
castGroundShadow (which wins), and level.load takes the same
option for one glTF scene (which wins over this).
Because it is a blanket opt-in, it skips meshes with no vertical
extent — a flat plane lying on the floor is the floor, and shadowing
it with itself would smear a blob across the whole ground. A per-object
castGroundShadow: true bypasses that safeguard, being an explicit
instruction.
On by default. Requires a GPU backend and a Camera3d, so a
2D game is untouched whatever this says — the Canvas renderer has no
depth buffer and the 2D-camera path draws none. Set it false to opt a
3D game out wholesale (a scene with baked lighting, or one bringing its
own shadows, would otherwise get two).
Optionalcompositor?: new (renderer: any) => WebGLBatchera custom batcher class (extend the active backend's base:
WebGLBatcher / WebGPUBatcher)
whether to display melonJS version and basic device information in the console
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.
The WebGPU backend honors it too, by rejecting a fallback
(software) adapter. The effect: under AUTO such a machine
gets the Canvas renderer, and under WEBGL / WEBGPU
app.init() rejects. Set to false to accept a software or
blocklisted context instead.
Enable the GPU procedural shader path for orthogonal tile layers
(backends advertising renderer.supportsShaderTileLayers — WebGL 2
and WebGPU). 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 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.
Let the post-effect chain carry color values above 1 instead of clamping at every write.
Render targets become half-float rather than 8 bits per channel, which
is what gives BloomEffect genuinely over-bright pixels to
threshold on and ToneMappingEffect a range to compress. Off by
default, for the same reasons antiAlias is: it costs GPU memory, and
a game gets a different pipeline depending on the driver underneath.
The cost is narrow. Render targets exist only while something is actually running a multi-pass effect chain, and only the CAMERA pair takes the half-float format, at 8 bytes per pixel instead of 4. Sprite chains and the advanced-blend target stay 8-bit whatever this says. A game with no camera post-effects allocates nothing either way.
Not HDR output on its own. This is headroom THROUGH the chain:
the frame is presented in SDR, so anything still above 1 at the end
clamps on the blit, exactly as it did before this setting existed. See
hdrOutput to remove that last clamp as well, on the backends that
can. Nothing is added to your effect
chain, so turning this on does not restyle your game. Add a
ToneMappingEffect to the camera if you want a curve rather than that
clamp, bearing in mind that every curve maps 1 to less than 1, so a
look built on clipped whites will change.
Ignored where it cannot be honoured: the Canvas renderer has no render targets, and a WebGL2 driver missing either half-float extension warns once, naming which half, and stays at 8 bits. Read Renderer#supportsHDR for what you actually got, and branch a grade on that rather than on this, which is only the request.
Present the frame in the display's full dynamic range, instead of clamping it to SDR on the way to the screen.
hdr gives the effect chain headroom and then clamps at the very last
step, because the surface the browser presents is 8 bits per channel.
This changes that surface: values above 1 reach the compositor and, on
a display that has the range, are actually shown brighter.
Requires hdr, and changes how your game looks on an HDR display.
A 2D look built on clipped whites relies on that final clamp; remove it
and everything you drove above 1 gets brighter rather than saturating.
That is the point, and it is why this is a separate opt-in rather than
part of hdr.
WebGPU only. The WebGL2 equivalent is an unapproved specification
change that no browser implements, so that backend reports false and
presents in SDR as it always has. Query
Renderer#supportsHDROutput for what was granted, and expect it
to differ per backend on the same machine.
enable high precision shaders (WebGL only). When false, shaders prefer "mediump" precision for better performance on some mobile GPUs, falling back to "lowp" if "mediump" is not supported. When true (default), the highest precision supported by the device is used. This setting is ignored by the Canvas renderer.
How many texture units the WebGL multi-texture batchers may use (#1585).
A batch can draw sprites from this many distinct textures before it has
to flush and start over, so a scene with more textures in flight than
this loses batching sharply. The pool used to be hardcoded to 16 — the
WebGL 2 spec floor for MAX_TEXTURE_IMAGE_UNITS, and roughly half
what current desktop and mobile hardware reports.
"auto" (default) — the device's reported limit, capped at 32.Raising it costs fragment-shader compile time and register pressure, because the batcher's shader unrolls one sampler and one branch per unit. Lowering it is the escape hatch if a driver misbehaves on wide sampler ladders.
Read at initialization only — the batchers compile their shaders
against this value, so unlike textureFilter there is no runtime setter.
WebGL only; the WebGPU backend sizes its own slot budget from its
per-stage binding limits, and the Canvas renderer has no batching.
The physics system to use. Accepts:
"builtin" (default) — the built-in SAT physics adapter"none" — disables physics; World.step skips the simulation,
the world container behaves like a pure scene graphPhysicsAdapter instance — e.g. new MatterAdapter() from
@melonjs/matter-adapter, or any third-party adapter{ adapter: PhysicsAdapter } — explicit form, reserved for
future per-app physics optionsThe adapter's physicLabel becomes world.physic so user code
can branch on the active engine without importing the concrete
adapter class (app.world.physic === "matter", etc.).
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.
Warm the renderer up during loader.preload(), behind the loading
screen, instead of paying for it on the frame that first draws (GPU
backends — the Canvas renderer has nothing to warm and ignores this).
Today that means the built-in shader programs, which is all Renderer#prewarm does. Named for the intent rather than the current contents: anything else worth doing before the first frame belongs behind the same switch, and should not need a second setting.
A GPU backend does not finish a shader when handed the source; it finishes it the first time something is drawn with it. That puts the whole cost on the frame a scene first appears, which is the moment a level or stage comes up and its geometry arrives a beat late. Warming up moves it to where a progress bar is already on screen.
On by default. The cost is small and it is paid where nothing is
waiting on a frame: a purely 2D game links the mesh-tier programs
it will never bind — both the unlit tier and the lit one that inherits
from it — measured at about 60ms on a fast desktop GPU, inside a preload
that is already showing a progress bar. A tiny 2D game with a near-empty
preload is the one case that pays without any possible benefit. Set it to
false for a target where linking is slow enough that the preload
itself would suffer.
The warm-up is run by loader.preload() — every call, not just the
first — so a game that preloads gets it automatically and a game that
never calls preload() never gets it at all. Call
Renderer#prewarm directly in that case.
Shaders declared as assets ({type: "shader"}) are covered too, so a
level's own effects are warmed by declaring them alongside that level's
assets rather than building them inline when the level starts.
Renderer to use. Four built-in modes (constants from me.video):
await app.init() always
resolves under AUTO; the WebGPU attempt is a full adapter/device
negotiation awaited inside init(), falling through to the
synchronous candidates when it rejects. Under the Canvas tail the
GPU-only subsystems (Camera3d, retained meshes, ShaderEffect,
Light2d/Light3d shading, GPU tilemap) degrade or disable — gate on
the renderer capability flags (supportsDepthBuffer,
shaderLanguage, …) if your scene depends on them.app.init()
rejects when no adapter/device can be acquired — it never
substitutes another backend.app.init() rejects if a WebGL 2
context is unavailable (WebGL-1-only device, driver-blocklisted
GPU, perf-caveat failure, etc.) — fail fast rather than render a
stuck blank canvas.Or pass a custom Renderer subclass instance for full control.
how the canvas is scaled to the page. A number is a fixed multiplier applied to the design resolution and nothing is measured; "auto" scales to the available space every time the window changes, using ApplicationSettings.scaleMethod to decide how and ApplicationSettings.scaleTarget to decide what is measured.
scaleMethod, scaleTarget and ApplicationSettings.scaleRounding apply to "auto" only. With a fixed number they are ignored.
how automatic scaling uses the space available to it. Applies when ApplicationSettings.scale is "auto".
The distinction that decides which to pick is whether the DESIGN RESOLUTION moves. "fit" keeps it and scales the whole frame to the largest factor that fits, leaving the rest as margin, so the game always sees exactly the world it was authored for. Every other mode calls renderer.resize(), so the design resolution follows the container and the game sees MORE or LESS of the world on a different window: "fill-min" / "fill-max" keep the aspect ratio while doing so, "flex" / "flex-width" / "flex-height" resize one or both axes, and "stretch" scales each axis independently so the aspect ratio is not preserved. "manual" does nothing automatic.
A game whose layout assumes a known frame wants "fit". A game that should reveal more world on a larger window wants one of the others, and has to cope with a viewport whose size it did not choose.
whether the automatically computed scale factor may be fractional, or must be a whole number of device pixels. "fractional" (the default) lets the canvas fill its container exactly, at the cost of source pixels landing on uneven numbers of device pixels: at a 2.667 factor some are 3 device pixels wide and some are 2, which browsers resolve differently and which pixel art shows as shimmering or uneven stems. "integer" rounds the factor DOWN so every source pixel covers the same whole number of device pixels, and letterboxes with whatever space is left over.
Only scaleMethod: "fit" is affected: every other method resizes the renderer to the container, so the design resolution changes and there is no fixed factor to round. The rounding is applied in DEVICE pixels, so devicePixelRatio cannot reintroduce a fraction.
What it costs depends on the container, and on devicePixelRatio, far more than it looks. A higher ratio has more device pixels to round among, so it lands closer to the factor it wanted. For a 480x270 design, which is exactly a quarter of 1920x1080:
| container | ratio | fit | integer | presented | area lost |
|---|---|---|---|---|---|
| 1920x1080 | 1 or 2 | 4.000 | 4 / 8 | 1920x1080 | none |
| 1440x810 | 1 | 3.000 | 3 | 1440x810 | none |
| 1280x720 | 2 | 2.667 | 5 | 1200x675 | 12% |
| 1280x720 | 1 | 2.667 | 2 | 960x540 | 44% |
| 1366x768 | 1 | 2.844 | 2 | 960x540 | 51% |
A design resolution that divides the sizes you expect costs nothing at all; one that does not can give up half the screen on a 1x display. Choose the design resolution with this in mind rather than reaching for the setting afterwards.
the element automatic scaling measures to decide how much space it has. By default it is the PARENT of the container the canvas was mounted in, which is one level further out than most callers expect and is the usual cause of a canvas that does not fit its box.
The case to watch for: a container that is position: fixed or absolute contributes no height to its parent, so that parent measures only whatever else it holds. If it measures <body>, the window size is used instead, and the canvas comes out larger than the box it lives in, cropped by whatever sits above it, by exactly the height of that other content on every resize.
Name the element here when the default does not land on the box you meant, or give the canvas container a parent whose box IS that area.
whether a renderable may sit on a fractional position WITHIN the frame. false, the default, snaps each translation to whole pixels, which is what stops a slowly moving or transformed sprite shimmering along its edges.
The snap TRUNCATES rather than rounds, and truncates toward zero, so a value that should swing symmetrically about the origin does not: an offset travelling between -3 and +3 lands on -2 and +2, and sits at each extreme a little longer than it should. Something animating over a short distance is where this shows.
It says nothing about how the frame reaches the screen. A game that snaps positions here and is then presented at a fractional scale has undone the work at the last step; ApplicationSettings.scaleRounding is what addresses that half.
Default texture magnification/minification filter, decoupled from
antiAlias (GPU backends — WebGL and WebGPU; the 2D Canvas
renderer has no per-texture filtering and ignores this).
antiAlias conflates two separate concerns: polygon-edge antialiasing
(MSAA) and texture sampling smoothness. This setting separates the
texture half out, so you can choose them independently — e.g. smooth
textures with no MSAA, or crisp pixel-art textures with MSAA edges.
"auto" (default) — follow antiAlias (linear when true, nearest
when false): unchanged behavior."nearest" — crisp/pixelated upscaling, regardless of antiAlias."linear" — smooth, regardless of antiAlias.This is the default for every texture; a Mesh can still override
it per-mesh via its own textureFilter setting (which wins).
// smooth textures but NO polygon-edge MSAA
const app = new Application(1024, 768, {
renderer: video.WEBGL,
antiAlias: false, // MSAA off
textureFilter: "linear", // textures still filtered smooth
});
// crisp pixel-art textures WITH MSAA-smoothed edges
new Application(1024, 768, {
renderer: video.WEBGL,
antiAlias: true, // MSAA on
textureFilter: "nearest", // textures stay crisp
});
the tone curve applied at the end of the camera's chain, or "none" for no curve. "aces" is the filmic one, "reinhard" the gentlest, "exponential" the simplest.
A curve is a look decision, not a companion to ApplicationSettings.hdr. Every curve maps 1 to less than 1, so switching one on changes a game whose art was authored against a clamp: the clipped white goes matte and the picture flattens. Turn it on for art authored in linear light, or when a deliberate grade is wanted.
Changing the mode rebuilds one shader, since the curve is baked into it; ApplicationSettings.toneMappingExposure and ApplicationSettings.toneMappingWhite are uniform writes and can be driven per frame, which is what makes a settings screen or a hit flash cheap. renderer.setToneMapping() is the runtime entry point.
Multiplier applied before the tone curve. Above 1 lifts the image
into the curve's shoulder; below 1 pulls it down. Ignored while
toneMapping is "none".
The post-exposure value the tone curve should map to display white, or
0 to leave the curve its own shoulder. This is what a "brightness" or
"peak" slider in a settings screen drives. Ignored while toneMapping
is "none".
whether the canvas composites with the page behind it. false, the default, makes the front buffer opaque: the clear alpha is 1, WebGPU is configured alphaMode: "opaque", and the canvas renderer clears rather than erases.
Turn it on only when the page is meant to show through, since an opaque surface is the cheaper one to composite and is what a game filling its container wants. With it on the clear alpha becomes 0 and WebGPU switches to "premultiplied", so anything drawn at partial alpha blends with the page rather than with a known background.
whether to enable verbose mode (additional console output for debugging)
Optionalcanvas?: neverthe DOM parent element (or its string ID) to hold the canvas in the HTML file
an existing canvas element to use as the renderer target (by default melonJS will create its own canvas based on given parameters)
Optionalparent?: never
whether to enable or not video scaling interpolation. On the GPU backends this drives polygon-edge antialiasing (up to 4× MSAA) — on the canvas itself, and equally through post-effect chains, whose scene capture targets are multisampled to match, so adding a camera effect never switches edge smoothing off. Texture sampling smoothness is controlled separately by
textureFilter. Multisampled capture targets cost GPU memory (roughly 30 bytes per pixel at 4×, ~55 MB at 1080p on WebGL) — withantiAlias: falseno multisampled storage is ever allocated.