You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
WebGLRenderer.createPattern keys its texture cache by the source image reference. When called twice for the same image with different repeat modes, the second call deletes the first call's GPU texture before uploading the new one — leaving the first pattern handle pointing at a freed/replaced texture.
The deletion is intentional (fix for #1278, which originally leaked the prior texture). The follow-on issue is that the cache key is too coarse: image alone doesn't distinguish between patterns with different wrap modes, so the "leak fix" trampled the still-live earlier pattern.
// clean up any previous pattern texture for this image// see https://github.com/melonjs/melonJS/issues/1278if(this.cache.has(image)){this.currentBatcher.deleteTexture2D(this.cache.get(image));}
How to trigger
consthoriz=renderer.createPattern(sky,"repeat-x");constvert=renderer.createPattern(sky,"repeat-y");// `horiz` now references a deleted/replaced texture.// Subsequent draws using `horiz` either fail GL validation or// render the wrap mode of `vert` (the last call wins).renderer.drawPattern(horiz,0,0,800,64);// ← uses vert's texturerenderer.drawPattern(vert,0,0,64,600);// ← also vert's texture (correct, but coincidentally)
Same outcome whenever two ImageLayer instances share the same image with different repeat modes:
constsky=loader.getImage(\"clouds\");consthorizonStrip=newImageLayer({image: sky,repeat: \"repeat-x\" });
constsideStrip=newImageLayer({image: sky,repeat: \"repeat-y\" });
app.world.addChild(horizonStrip);app.world.addChild(sideStrip);// Whichever ImageLayer is created second wins; the first// renders with the second's wrap mode.
Canvas mode is unaffected — CanvasRenderer.createPattern returns a fresh CanvasPattern per call and doesn't cache textures.
Why it isn't visible today
Typical melonJS usage creates one pattern per image:
Each ImageLayer has its own image; the constructor calls createPattern once.
The repeat-x / repeat-y / "repeat" config is usually picked once at level design time, not toggled at runtime.
Most games don't use the same source image across multiple ImageLayers with different repeat modes — they pick a different asset per layer.
The collision only surfaces in three scenarios:
Two or more ImageLayers sharing one image with different repeat modes (uncommon — most parallax setups use distinct images).
Runtime mutation: changing an existing layer's repeat and re-calling createPatternwhile another reference to the original handle is still live.
None of those are typical use, which is why the bug has been latent since the #1278 fix landed.
Proposed fix
Either:
A. Key the cache by (image, repeat) — minimal change, retains the leak-prevention behavior of #1278 within each (image, repeat) slot:
constkey={ image, repeat };// or a composite key
B. Keep one cache entry per image but reconfigure the wrap mode in place instead of delete-and-replace — calls into MaterialBatcher.createTexture2D with the new wrap params on the existing GL texture, without re-uploading the pixels. Avoids the re-upload cost too.
Either approach preserves the no-leak invariant from #1278.
This ticket: createPattern uses the bare image reference as a texture key, missing the repeat-mode dimension (webgl_renderer.js).
Different code paths, different caches, different fixes. Worth noting because the underlying design lesson is the same: cache keys must include every dimension the cache entry depends on.
Description
WebGLRenderer.createPatternkeys its texture cache by the sourceimagereference. When called twice for the same image with different repeat modes, the second call deletes the first call's GPU texture before uploading the new one — leaving the first pattern handle pointing at a freed/replaced texture.The deletion is intentional (fix for #1278, which originally leaked the prior texture). The follow-on issue is that the cache key is too coarse:
imagealone doesn't distinguish between patterns with different wrap modes, so the "leak fix" trampled the still-live earlier pattern.Location:
packages/melonjs/src/video/webgl/webgl_renderer.js—createPattern(), lines 584-588:How to trigger
Same outcome whenever two
ImageLayerinstances share the same image with different repeat modes:Canvas mode is unaffected —
CanvasRenderer.createPatternreturns a freshCanvasPatternper call and doesn't cache textures.Why it isn't visible today
Typical melonJS usage creates one pattern per image:
ImageLayerhas its own image; the constructor callscreatePatternonce.repeat-x/repeat-y/ "repeat" config is usually picked once at level design time, not toggled at runtime.ImageLayers with different repeat modes — they pick a different asset per layer.The collision only surfaces in three scenarios:
ImageLayers sharing one image with different repeat modes (uncommon — most parallax setups use distinct images).renderer.createPatterndirectly with the same image and different repeat modes (this is what surfaced the bug while building a visual repro for Canvas: drawPattern repeat-x/repeat-y doesn't match WebGL behavior #1290).repeatand re-callingcreatePatternwhile another reference to the original handle is still live.None of those are typical use, which is why the bug has been latent since the #1278 fix landed.
Proposed fix
Either:
A. Key the cache by
(image, repeat)— minimal change, retains the leak-prevention behavior of #1278 within each(image, repeat)slot:B. Keep one cache entry per image but reconfigure the wrap mode in place instead of delete-and-replace — calls into
MaterialBatcher.createTexture2Dwith the new wrap params on the existing GL texture, without re-uploading the pixels. Avoids the re-upload cost too.Either approach preserves the no-leak invariant from #1278.
Relationship to #1281
Loosely thematic — both issues are "cache key too narrow for the data it indexes":
addUVs/getUVsuse coordinate strings (\"x,y,w,h\") as region keys, causing collisions and pollution inSprite.addAnimation(atlas.js).createPatternuses the bareimagereference as a texture key, missing the repeat-mode dimension (webgl_renderer.js).Different code paths, different caches, different fixes. Worth noting because the underlying design lesson is the same: cache keys must include every dimension the cache entry depends on.