Proposal: Static Tilemap


#1

So, after using OpenFL 3.6.1 for a long time (for drawTiles), I finally updated my code for OpenFL 5.1.5 (I didn’t want to stay with the old version forever). However, the game ended up being a LOT slower, and looking at the tile rendering code, I realized it was not optimized for how I used it, and worse, there was no alternative that I could use. Here is a proposal for an edit to Tilemap or perhaps something separate called a Static Tilemap to optimize what I consider the most common use case of a Tilemap.

The problem with the current Tilemap is that in the GLTilemap renderer (which is the only one I care about - as it is used for WebGL and windows, mac, linux), it loops through all tiles, builds the buffer data and uploads it to the GPU every frame! This is because I can move and transform tiles at any time, and for a dynamic tilemap (like a particle system, the bunnymark demo or a Spriter animation), this is the best way to do this. However, lets say you add the tiles, and then never change them (like the ground tiles in an RPG or a map from Tiled), then this is a huge waste to recompute and re-upload every frame for both the CPU and the GPU.

It gets worse - for every Tilemap you have on screen, it computes all tiles and uploads all tiles to the GPU. This is why you want to have as few Tilemaps as possible and put everything into one map. Lets say you have multiple layers that each need their own Tilemap, or you break the map into Chunks for loading/unloading sections of the map (and each Chunk needs it’s own Tilemap), then you could be computing tons of tiles and uploading tons of tiles to the GPU every frame, killing performance.

The old drawTiles method was more flexible. If you had a static tilemap, you would call it once and it would only compute and upload the tiles to the GPU once, then get a nice performance benefit by just drawing that cached VBO every frame. If you needed a dynamic tilemap, you could clear the graphics object and call drawTiles with the updated positions, and it would re-compute and re-upload tiles every time you did that.

My Proposal
There are three ways I can think of for adding a Static Tilemap that is optimized for this use case (which, for any game that has ground tiles is very common):

  1. In the Tilemap class, add a function or variable that declares that you are done making changes to the tiles. Any edits to tiles added after this function call will be ignored. In the renderer, it will upload the tiles one more time after this flag is set and thereafter only render the cached VBOs. You could even switch this flag on and off to push changes for tilemaps that are changed infrequently (although, simply declaring done would be plenty for the most common use cases).

  2. Have the Tiles know what Tilemap they are added to, and when their dirty flags are set, set a global Tilemap dirty flag. Then, the Tilemap will only upload if this global dirty flag is set, otherwise use the cached VBOs (as no tiles have changed since the last render). This is obviously more work on the backend, but easier to use - if you edit or add a tile, it will automatically recompute and re-upload, and if you don’t, it won’t (automatically optimizing for static or infrequently edited tilemaps).

  3. You could make this a separate StaticTilemap class with it’s own renderer. However, I don’t see much reason to do this over 1 since the Tilemap already has the computing, uploading and rendering VBO code - it just needs to be broken up. EDIT: This could also be useful for other platforms/renderers, as I said, I only looked at the OpenGL renderer for Tilemaps.

Anyways, I’ll stick with OpenFL 3.6.1 for now, but I definitely think this would be a big deal for anyone making RPGs or using a lot of static tilemaps (especially for large worlds where you need to break the tiles into chunks).


#2

I am sorry for being delayed in writing back to you regarding your proposal.

The Tilemap was designed to be able to favor performance of static content. drawTiles had no sense of state, except for the case (you mention) where you graphics.drawTiles but to not graphics.clear. This is important, and one I feel the current Tilemap design can support easily.

You may have noticed OpenFL 6.0 includes a new TileArray API, which allows Tilemap to be used more closely to the older drawTiles API. In the process of this design change, I believe the caching situation has improved internally, as the TileArray (and its associated buffer object) are separate from the render step. Both the ordinarily Tilemap API and the new TileArray API become a TileArray internally.