Skip to content

Commit a5454f1

Browse files
authored
Merge pull request #1425 from melonjs/fix/imagelayer-predraw
fix(imagelayer): preDraw now applies flip / mask / post-effects
2 parents 7d55c15 + 24825f4 commit a5454f1

3 files changed

Lines changed: 524 additions & 5 deletions

File tree

packages/melonjs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
- Rendering: `IndexBuffer` split into renderer-agnostic `IndexBuffer` base (data accumulation) and `WebGLIndexBuffer` (GL buffer bind/upload)
4646

4747
### Fixed
48+
- ImageLayer: setting `mask`, `shader` / `postEffects`, `flipX()` or `flipY()` had no effect — `ImageLayer.preDraw()` was a stripped-down copy of `Renderable.preDraw()` that handled only alpha, tint and blend mode. The override now also applies flip, stencil mask, and post-effects (the anchor/`autoTransform` skip is preserved because `ImageLayer.draw()` computes its own world-space position).
4849
- WebGL: stencil masking (`setMask` / `MaskEffect`) now works correctly in WebGL1 mode (`preferWebGL1: true`). `WebGLRenderTarget` was relying on `gl.DEPTH_STENCIL` / `gl.DEPTH_STENCIL_ATTACHMENT` being exposed on the WebGL1 context, which some browser/driver combinations leave `undefined`, silently producing FBOs with no stencil attachment. The setup now uses spec-defined numeric fallbacks (0x84F9 / 0x821A) and validates completeness via `gl.checkFramebufferStatus()`. Also dropped the dead `WEBGL_depth_stencil` extension gate (those constants are core WebGL 1.0, no extension required) and removed the redundant `STENCIL_BUFFER_BIT` from `clearRenderTarget()` (which produced spurious `Clear called for non-existing buffers` warnings).
4950
- State: `state.freeze()` no longer leaves the game in inconsistent states when interacting with manual pause/resume or window blur. Specifically: (a) the freeze timer's auto-resume now respects whether the game was already paused when freeze started — won't unpause a manually-paused game on expiry; (b) calling `state.resume()` or `state.stop()` mid-freeze cancels the timer and resolves the freeze promise immediately; (c) window `BLUR` cancels the freeze (the visual "moment" is over by the time the user returns; regular `pauseOnBlur` keeps the game paused while away).
5051
- ParticleEmitter: constructor was using bitwise `|` instead of logical `||` for the `width`/`height` fallback, which silently rounded any provided value to the next odd number (e.g. `width: 16 → 17`, `width: 32 → 33`).

packages/melonjs/src/renderable/imagelayer.js

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,19 @@ export default class ImageLayer extends Sprite {
223223
}
224224

225225
/**
226-
* override the default predraw function
227-
* as repeat and anchor are managed directly in the draw method
226+
* Override the default preDraw to skip the base class's anchor offset
227+
* translation and `autoTransform` application — `ImageLayer.draw()`
228+
* computes its own world-space position from the viewport, the parallax
229+
* `ratio`, anchor, and zoom, so applying anchor/transform here would
230+
* double-translate the layer.
231+
*
232+
* Coordinate-sensitive setup (flip, stencil mask) is intentionally
233+
* deferred to `draw()` where it runs *after* the per-camera zoom
234+
* translate/scale — that way it stays correctly aligned at any
235+
* `viewport.zoom` and across multiple cameras. Coordinate-independent
236+
* setup (alpha, post-effects, tint, blend) stays here. The inherited
237+
* `Renderable.postDraw` cleans up symmetrically (`clearTint` /
238+
* `clearMask` / `endPostEffect` / `restore`).
228239
* @ignore
229240
*/
230241
preDraw(renderer) {
@@ -234,7 +245,12 @@ export default class ImageLayer extends Sprite {
234245
// apply the defined alpha value
235246
renderer.setGlobalAlpha(renderer.globalAlpha() * this.getOpacity());
236247

237-
// apply the defined tint, if any
248+
// delegate post-effect setup to the renderer (custom shader / postEffects)
249+
if (!this._postEffectManaged) {
250+
renderer.beginPostEffect(this);
251+
}
252+
253+
// apply the defined tint
238254
renderer.setTint(this.tint);
239255

240256
// apply blending if different from "normal"
@@ -289,6 +305,25 @@ export default class ImageLayer extends Sprite {
289305
renderer.translate(x * vZoom, y * vZoom);
290306
renderer.scale(vZoom, vZoom);
291307

308+
// apply flip — pivot at the centre of the drawn pattern in the
309+
// post-zoom local frame, so the mirror lands in the visible viewport
310+
// regardless of `viewport.zoom`. Done here (not in preDraw) because
311+
// preDraw runs before this translate/scale and would misalign.
312+
if (this._flip.x || this._flip.y) {
313+
const px = viewport.width;
314+
const py = viewport.height;
315+
renderer.translate(px, py);
316+
renderer.scale(this._flip.x ? -1 : 1, this._flip.y ? -1 : 1);
317+
renderer.translate(-px, -py);
318+
}
319+
320+
// stencil mask — also applied in the post-zoom local frame so the
321+
// mask shape's coordinates are interpreted relative to the layer's
322+
// drawn area, matching `drawPattern` below.
323+
if (this.mask) {
324+
renderer.setMask(this.mask);
325+
}
326+
292327
renderer.drawPattern(
293328
this._pattern,
294329
0,

0 commit comments

Comments
 (0)