melonjs / melonJS

a fresh, modern & lightweight HTML5 game engine
https://melonjs.org
MIT License
5.88k stars 646 forks source link

TextureCache.length can't be decreased #1195

Closed dt555777 closed 1 year ago

dt555777 commented 1 year ago

Describe the bug I'm writing a game similar to Tetris, but with dynamically generated blocks, i.e. each time a new block enters the game area, its shape, texture, colors, etc. are dynamically generated with thousands (maybe more) of possible combinations. This is why I'm not using a sprite sheet.

When the current block reaches the bottom of the game area, it is removed from its container (levelContainer.removeChild(blockEntity)), a new block is created and the associated image is generated by using a canvas and then passing it to the Entity constructor, finally the new block is added (levelContainer.addChild(blockEntity)).

The problem is that after a few (14 on my machine) blocks, I get Texture cache overflow: 16 texture units available for this GPU..

Looking at the Melon.js code, I see that each time a create a new entity a new texture is created from my generated image and it is added to the texture cache of the renderer. Anyway, when the entity is removed, the texture stays in the cache. Moreover event if the texture is manually removed from the cache, the TextureCache.length is not decreased, so even if there's only one sprite on the screen, the texture limit checked by TextureCache.validate is hit.

Expected behavior I would expect to be able, someway to remove a texture from the texture cache and decrease TextureCache.length. Or, better, to have that automatically happen when the Entity is destroyed.

Device:

Device: PC OS: Windows 10/11 Browser: Chrome melonJS Version: 15.3.0

obiot commented 1 year ago

the length not being decrease when an image (and corresponding texture) is removed from the cache is definitely a bug, so good catch on that one, and that's definitely something to be fixed.

About your use case and the use of multiple dynamic single texture, one thing to remember is that the amount of single texture you can use is a limit of the machine you are running the game on, and as of today the sweet spot seems still to be around maximum 16 textures (https://web3dsurvey.com/webgl/parameters/MAX_TEXTURE_IMAGE_UNITS), remove a couple of them for background or others, and you cannot really go higher than 14 anyway.

I'm not saying this to say there is nothing to fix in melonJS but just to say that maybe there are different approaches :

  1. maybe use one big texture (see https://melonjs.github.io/melonJS/docs/melonjs/CanvasTexture.html) in which you create all your blocks dynamically and just keep beside it a table with offset and size of each of them ? Plus performance wise its definitely better to have one big texture rather than using single one for everthying.

  2. what about actually literally drawing each piece dynamically ? since version 15.6, the Renderer object also exposes more Canvas drawing like API, and support masking and tinting for sprite drawing, so by mixing drawing and sprite you can have something like :

    
    // save the drawing context
    renderer.save();

// rotate renderering context based on the piece current rotation renderer.rotate(radian);

// draw a shape with different fill and stroke colours renderer.beginPath(); renderer.moveTo(...); renderer.lineTo(...); .. renderer.lineTo(...); renderer.setColor("#F00"); renderer.closePath(); renderer.fill(); renderer.setColor("A00"); renderer.stroke();

// can also draw a specific shape renderer.fill(shape); renderer.stroke(shape);

// add a tinted sprite on top of it renderer.setTint("F00"); // apply a mask corresponding to the shape type renderer.setMask(shape); renderer.drawImage(myImage);

// restore previous drawing context renderer.restore();



see as well [here](https://melonjs.github.io/examples/graphics/) for an example of primitive drawing

and you can also use different blend mode when drawing sprite over the current context : https://melonjs.github.io/melonJS/docs/melonjs/CanvasRenderer.html#setBlendMode
obiot commented 1 year ago

also just to comment back on the length issue, it cannot just be decreased. Imagine a scenario where 16 texture unit have been allocated. Length is therefore equals to 16, but if you delete the texture allocate to unit 4, and just decrease length by one, the current code will override the texture currently allocated with the texture unit 15, instead of taking the first non allocated unit within the max range (which is 4 in this example). So that's more what I'm looking at right now on how to fix it. actually remove length, and properly manage a range of texture unit in term of (de)allocation.

dt555777 commented 1 year ago

First of all, thank you for the quick answer.

Thank you also for your suggestions on alternatives: for sure I going to try the canvas-like APIs of Renderer, also because, in my case, the potential performance loss shouldn't be a problem at all and also because I'm not sure about the alternative with CanvasTexture.

obiot commented 1 year ago

for sure I going to try the canvas-like APIs of Renderer

Even just using tinting and masking are already quite just a super powerful couple of tools already. Look at the below demo, it's just one single sprite draw several times with different cutout mask, different tint, and different rotation. warning as it's a bit psychedelic, but the result is quite nice when you know there is only one sprite being used : https://melonjs.github.io/examples/masking/

(link to demo and source code : https://github.com/melonjs/melonJS#demos)

let me know how you progress, and I wouldn't complain for a quick demo or even just a video/gif to see how things look :)

obiot commented 1 year ago

with the 15.8 version now being released, and fixing the issue initially reported here, I'm closing this ticket.

come to show us your progress on the discord examples-progress channel : https://discord.com/channels/608636676461428758/986554136717844503