Creating lots of small canvases slow. Alternative?

Started by
11 comments, last by Wyrframe 6 years, 1 month ago

Hello,

I'm working on a tile based engine in HTML5+canvas.

I currently made every single tile in the world its own little canvas. The reason is, I make two canvases per tile, and depending on state display one of the other. This allows fast switching between the two with style.display.visiblity.

However, while that actual showing and swapping visibility goes fast, what goes slow is creating them at the start of the game, in Firefox at least. In Chrome, this all goes super fast. In firefox, simply creating a couple of thousand canvases, especially calling getContext('2d') on each of them, goes super slow.

 

Is there any alternative better way to have a 2D tile based engine where each tile can efficiently swap between two different graphics?

Thanks!

Advertisement

I do not think that there is any special hardware support for
style.display.visiblity
it is just that modern browser manage to do nothing stupid while switching this property.

I would say that good old bitBlt is as fast.

Or you could target the C64 or NES or Sega Master System. They have hardware support for tiles with four colors!

Hello!

1 hour ago, Lode said:

Is there any alternative better way to have a 2D tile based engine where each tile can efficiently swap between two different graphics?

Clearly "yes". Juggling with thousands of canvases to draw something seems like a very (very, very) bad approach to me. You could have done something like this in the pre-canvas days of HTML when your only option to draw a tile map was to use an individual <img> element to draw each tile. However, the whole idea behind the <canvas> element is that you can draw any sort of image dynamically so that you don't have to use such hacks any more.

The solution is simple: Just use one canvas and paint your tile map on that. Drawing on canvas is quite fast, it is very unlikely that you run into unsolvable performance problems with that. In the end, of course, it depends on the resolution of the canvas and on the number of tiles, but a typical scenario (full HD, a couple of hundreds of tiles in the viewport) can probably be rendered at interactive frame rates (i.e. a complete redraw on each frame, including fluid scrolling).

 

The difficulty is this:

Each tile is not a nice predefined bitmap image, but a line drawing that depends on the environment. So I can't really use a set of textures, the texture is created with line drawings on the fly (what a tile looks like depends on neighbors etc..., like there may be connecting lines or arrows to neighbors, so it would be too much textures to have them for every possible combination)

beginPath, lineTo and moveTo also seem pretty slow. So I need some fast way to draw the two line drawings of every world tile, and then once I have those I could indeed do the bitBlt.

The two different line drawings per tile to toggle to are usually just a color change, but sometimes also a shape change or a letter character change.

Any suggestion for how to create, for example, 100x100 such little individual line drawings fast?

Hello Lode!

I do not yet completely understand your use case, but perhaps these suggestions/comments can help?

No. 1: If you don't need to update the entire map view on each frame, then don't do it. Only redraw the tiles that have changed.

No. 2: The performance of path drawing on the canvas heavily depends on the number of state changes (i.e. changing of color, line width etc.) and "beginPath() ... fill() / stroke()" commands. It's the same concept as with OpenGL draw calls (and perhaps the very same reason behind the scenes). Try to "batch-draw" shapes that need the same draw settings, it will significantly speed up your rendering.

No. 3: Are you aware that you can have invisible canvases (i.e. ones that just "hang around" as JavaScript objects, not added to the DOM), and that there is also the ImageData class which provides a low-level way to store image data?

I still strongly suggest not to create thousands of canvases, but maybe you could use one (or, if absolutely neccessary, a few) extra hidden ones as a sort of buffer/cache for your line drawings? Maybe you could pre-render the line drawings to an invisible canvas and then copy parts of that invisible canvas to the right places on the visible one?

Can you show a screenshot of how your map display currently looks like, or something similar to what you want to accomplish?

No screenshot, but I can describe:

Imagine a grid of 100x100 tiles (it can be bigger, e.g., 150x400 should also work), forming a diagram.

The diagram is a big line drawing formed by those tiles. Each tile has one of a few possible types, but its look depends on the neighbors. Examples:

-there could be arrows or lines pointing at any combination of 0 to 8 neighbors --> that alone gives already 3 to the power of 8 combinations

-there can be border around the tile, or on any subset of its 4 sides

-it might have a special background color

-it might have a letter on it

So the above gives a combinatory explosion of what a tile could look like. If there were only 100 or so different looks, I would use 100 premade textures. But that's not how it is, the tiles need to be created when loading the map and they could in theory all look slightly different (in practice many may look the same due to regularities in maps but it's hard to guess which before starting to draw them).

So for the same reason as 100 premade textures is not possible, I think having a few hidden ones as a buffer is difficult for the same reason.

Depending on what happens, it is possible that almost every tile needs to be updated to the other one in a single frame. So each map tile has two possible looks (usually) and the updates per frame is that some change to the other look (and it is currently smart enough to only flip those that changed, but if I would instead go to a different type of rendering where everything is redrawn per frame then that doesn't matter of course)

I will try if copying parts of a few pre-made ones existing out of only parts of a tile (like 1 of the 8 arrows, ...) will work, and if that will be fast enough every frame.

Thanks for the suggestions!

P.S. no matter how horrible it sounds, chrome can do it fast! I only discovered that it can be slow when trying it in firefox

Okay. I still don't understand which advantage you get from splitting the drawing up into thousands of separate canvases though. As far as I understand you at the moment, it is meant to be a measure against the need to redraw the entire map view even when only a small subset of the tiles need to be updated. Is this correct? If yes, you don't need to do this, as already mentioned. Just clear and redraw the parts of the canvas that have changed. Am I missing something?

What I was avoiding by doing the visibility hidden swapping, was redrawing of the lines.

I will try it out with 1 big canvas and redrawing everything per frame (or even only the parts that changed, but that could very well be everything).

I just will need to use blitting or copying then I guess, because I also already know the amount of lineTo moveTo etc.. to redraw everything per frame will be too slow. So I hope the copying will be fast.

 

I see.

Have you thought about composing your combined tile images by drawing multiple bitmap-based parts on top of each other with alpha transparency? Maybe pre-render the parts with line/path drawing in an invisible canvas and then copy the bitmaps from there?

Also, keep in mind that with one canvas, you can make use of batch drawing, as long as you have enough shapes that should be drawn with the same settings. With many separate canvases, you lose this option since each canvas has its own drawing context.

Another thing: You say you want to draw 100x100 tiles or even much more. Are you drawing the *entire* map at once? And if yes, are your tiles reall so small that the entire map fits on a single screen? If only a part of the map is visible at any given moment, you should of course only draw the visible tiles. This would save you a lot of time, too.

I'd really like to see what image you want to draw :). I just don't want to believe that you really need thousands of individual canvases for that ;).

Also, you can draw whatever on a canvas or an off-screen canvas surface, and then save a rectangle of it as a tile bitmap; you can then draw that bitmap with a very easy, fast operation as much as you need to. Look at how EaselJS implements "cached bitmaps", for example code.

RIP GameDev.net: launched 2 unusably-broken forum engines in as many years, and now has ceased operating as a forum at all, happy to remain naught but an advertising platform with an attached social media presense, headed by a staff who by their own admission have no idea what their userbase wants or expects.Here's to the good times; shame they exist in the past.

This topic is closed to new replies.

Advertisement