I have a set of images I, that I want to perform some processing on (tinting), before drawing onto the main canvas. For my use case, the image needs a different tint that needs to be calculated every frame.
To achieve the desired result, I draw the image I onto a secondary hidden canvas B, with a globalCompositeOperation to apply the desired effect. Thus far, there are no noticable performance issues. It's only when I draw B to the main canvas that I start to see a significant framerate drop. Looking at chrome's performance debugger seems to suggest most of the time is taken by the GPU, and not scripting.
What am I doing wrong? Is there a way to work around this performance issue?
I have tried separately drawing on canvas B, and in a different test drawing B to the main canvas. Neither seem to bring any noticable performance issue. Only the combination of both drawing to B and drawing B to the main canvas gives this performance issue.
Context: I am using an apple silicon M1 chip, running on the latest version of chrome
Related
I intend to have a FabricJS canvas that will display potentially dozens of different graphics, which could either be served up as individual image files or a sprite. For each graphic, it's possible that particular graphic would be displayed on the canvas 0 to dozens of times. I know that FabricJS has the clipping ability, which would make the use of sprites possible, and I know that generally speaking, sprites on the web are highly preferred over individual images, such as for CSS.
However, I've also heard of browser behaviors/quirks surrounding canvas where some optimizations that you might expect to take place, don't -- I can't find the link at the moment, but one that I recall was how large canvas-content elements out of view in Chrome would still take a considerable amount of time to be calculated/"rendered", despite not being relevant to on-screen appearance.
So, are there any known expectations on whether n individual non-clipped images would be preferable/unpreferable to a sprite that's clipped n times for a FabricJS canvas? In case it matters, this would not be a heavily animated FabricJS canvas; things may move when dragged, for example, but constant animation is not what's involved in this case.
Just do a test yourself to find out what slows down your PC. It depends on the PC and browser and graphics card.
I don't know anything about Fabric.js, but computer CPUs and graphics cards can handle clipping/graphic manipulation easily. You're not displaying millions of polygons like a 3D game, so you should be fine.
Here's a website that explains the CSS sprites vs individual images network performance increases:
https://medium.com/parlay-engineering/emoji-at-scale-render-performance-of-css-sprites-vs-individual-images-f0a0a2dd8039
Ive been having a really baffling slow down issue with the game I am working on probably because I am unsure how to handle graphics (most likely responsible for the slow down) in javascript without using a third party framework like Phaser/ImapactJS/EaselJS etc)*. The following is the low down on how I am approaching my graphics. I would be very thankful for some tips or methods on how to do this right.
My game is tile based - using tiles designed at 500x500 px because I want them to display decently on high definition devices.
I am using a spritesheet to load all (most of) my tiles before the main loop is run. This image is roughly 4000 x 4000 (keeping it below 4096 because the GPU cant handle texture sizes larger than that).
I then use the drawImage function to cycle through and draw each tile on a part of the canvas using information (w, h, x, y) stored in the tile array. I do this on every cycle of the main loop using my drawMap function.
The map is currently 6x6 tiles in size
A character spritesheet is also loaded and drawn on to the canvas after the map has been drawn. The character displays a different frame of the animation on every cycle of the main loop. There are sets of animations for the character each contained in the same spritesheet.
The character sprite sheet is roughly 4000x3500
The character is roughly 350x250 px
Other objects also use the same sprite sheet. Currently there is only one object.
Possibly helpful questions:
Am I using too many spritesheets or too few?
Should I only draw something if it's coordinates are in bounds of the screen?
How should I go about garbage collection? Do I need to set image objects to null when no longer in use?
Thanks in advance for input. I would just like to know if I am going about it the right way and pick your brains as how to speed it up enough.
*Note that I plan to port the JS game to cocoonJS which provides graphics acceleration for the canvas element on mobile.
** If interested please visit my Patreon page for fun!
You have asked lots of questions here, I'll address the ones I've run into.
I would like to start out by saying very clearly,
Use a profiler
Find out whether each thing you are advised to do, by anybody, is making an improvement. Unless we work on your code, we can only give you theories on how to optimise it.
How should I go about garbage collection? Do I need to set image objects to null when no longer in use?
If you are no longer using an object, setting its reference to null will probably mean it gets garbage collected. Having nulls around is not necessarily good but this is not within the scope of this question.
For high performance applications, you want to avoid too much allocation and therefore too much garbage collection activity. See what your profiler says - the chrome profiler can tell you how much CPU time the garbage collector is taking up. You might be OK at the moment.
I then use the drawImage function to cycle through and draw each tile on a part of the canvas using information (w, h, x, y) stored in the tile array. I do this on every cycle of the main loop using my drawMap function.
This is quite slow - instead consider drawing the current on screen tiles to a background canvas, and then only drawing areas which were previously obscured.
For example, if your player walks to the left, there is going to be a lot of tiles on the left hand side of the screen which have come into view; you will need to draw the background buffer onto the screen, offset to account for the movement, and then draw the missing tiles.
My game is tile based - using tiles designed at 500x500 px because I want them to display decently on high definition devices
If I interpret this right, your tiles are 500x500px in diameter, and you are drawing a small number of these on screen. and then for devices without such a high resolution, the canvas renderer is going to be scaling these down. You really want to be drawing pixels 1:1 on each device.
Would you be able, instead, to have a larger number of smaller tiles on screen - thereby avoiding the extra drawing at the edges? Its likely that the tiles around the edges will sometimes draw only a few pixels of one edge, and the rest of the image will be cropped anyway, so why not break them up further?
Should I only draw something if it's coordinates are in bounds of the screen?
Yes, this is a very common and good optimisation to take. You'll find it makes a big difference.
Am I using too many spritesheets or too few?
I have found that when I have a small number of sprite sheets, the big performance hit is when I frequently switch between them. If during one draw phase, you draw all your characters from character_sheet.png and then draw all the plants from plant_sheet.png you'll be ok. Switching between them can cause lots of trouble and you'll see a slow down. You will know this is happening if your profiler tells you that drawImage is taking a big proportion of your frame.
I've recently started learning to work with HTML5 Canvas and I've got a bunch of questions really. All related to performance. I understand that everything with this is quite subjective to what I'm doing. But I'm just hoping to gain some clarity.
In the example of creating game with an aerial view of the player. all 2d with a map that scrolls around on both X and Y axis.
Question 1: In my understanding, As long as there are no moving objects in my map canvas. it's more efficient to do something like create a large canvas element that I draw once and then use JS to scroll the page / move the canvas. rather than create a canvas that is the size of my screen, translate the canvas and redraw the map with each movement.
Question 2: If I use the above method of having a large canvas that scrolls about to move my map. will there be much performance difference between a simple drawing on the canvas and a complex drawing? Example: simple canvas with a couple rows of single colored blocks compared to complex canvas with thousands of lines, circles, gradients, patterns and detail. If the two canvases are the same width and height there shouldn't be a huge performance difference for just having the browser scroll them right? (no redrawing).
Question 3: Is there a preferred method for dealing with a map that is bigger than a canvas object can be? I know various browsers will limit the physical size of the canvas. Is it better to design the map in several big canvas blocks. Load an adjacent block into a brand new canvas element when the player gets near the edge. or will this cause issues. if I'm working with canvas elements that are say 10,000px by 10,000px and I start placing several of these side by side am i just asking for trouble? am I better off designing it to run in a single canvas that just redraws the map as the player moves and keep the complexity of the map down. and program it so it doesn't think about parts of the map that aren't close to the player.
I've read a bunch about tricks to increase performance like layering canvases and not redrawing the canvas more than the browser is refreshing. but I've been having trouble finding info on good practice for dealing with large maps and performance of moving around them.
I'm really interested to hear everyone's thoughts.
Question 1 is not actually a question, but it's best to create a canvas that is the size of your screen, translate the canvas and redraw PART OF the map with each movement. Do not redraw the whole map, but add methods to redraw a portion of the map that is slightly larger than the viewport, possibly excluding part of the map that is already drawn. Otherwise your browser might crash from memory stress.
Question 2: Right. There will be no difference at all. The browser will simply move the already drawn canvas about behind the viewport.
Question 3: Play around with the element inspector of Chrome on a google map. This is a very good example of a huge map that runs on a tiny phone. You will see that it uses a grid of images (you could uses canvasses). You could cache these blocks outside the viewport for if the user returns back. Play with the cache size to keep the memory usage sane.
I was wondering if there were any performance differences in using drawImage() on a dynamically created canvas (i.e. document.createElement("canvas")) versus a canvas already created in the DOM (i.e. tags on a HTML page).
My thinking is that the "slow" part of drawImage() occurs when it actually has to display visuals to the user, though I could be wrong (can't find too much information on this).
Would it be substantially more expensive to draw a group of objects to a canvas in memory followed by a final draw to the "main" canvas than just drawing straight to the latter? I feel like it'd be better to have multiple canvases, at least for organizational purposes.
Related: Does the size of the canvas impact performance if you're only drawing to a subsection of it?
Talking about Chrome and Firefox I could not find any difference between static and dynamic canvas elements. Mainly the amount of pixels drawImage() handles makes it slow + the current globalCompositeOperation (copy, source-over are fastest). However, the browser has to render the full page, so it is a bad idea to place a stretched (background) image below the canvas.
There is a difference between the canvas attributes width/height and its style width/height attributes. You may have a 300*200 pixels canvas with a style size set to 100%. Then internal drawing speed is same what ever the browsers window size is. Of course display quality is a concern.
You may want to separate drawing (lines, boxes, arcs etc) from blitting (drawImage) and find out what consumes more time in your application. As long there is no need for multiple canvas (image processing, blending videos, etc.) try to avoid drawImage(). Your code - not elements - should help you to deal with 'organizational purposes'.
A fullscreen drawImage() on a 1 GHZ Netbook with 1024x600 pixels takes about 10msec. Doing it twice means there is no way to achieve a display rate of 50Hz. Things getting worse if you target iPhone or Android smartphones.
There is no need to do good-old-double-buffering with canvas, it's already implemented. You are free to update only relevant (dirty) subparts of your canvas element whenever you want and gain essential msecs.
Instead of using multiple canvas there is an option to do all invisible operations on a huge one in different sections - drawImage() with target and source the same. It is easier then to see what is happening while debugging.
Is there a quick and efficient way to move lots of objects in canvas? Basically if there are around 1000 objects and I want to move all of them at once to emulate scrolling, it is very slow to redraw every single object by calling drawImage() 1000+ times.
Is there anyway to optimize this? I have an example link of the problem (and that's only with 100 objects): http://craftyjs.com/isometric/
Since canvas doesn't provide fast low level bitmap copying it's hard to do stuff in multiple layers and scroll for example the whole background at once and then only render the edges.
So what can you do? In short, nothing. Especially not when scrolling, sure you can do tricks with multiple canvases when you have a more or less static background but for moving objects there are hardly any performance improving tricks.
So, you've go to wait for Hardware Acceleration shipping in all majors browsers, I know this sounds ridiculous but I'm too waiting for that :/
The problem is that the canvas was never designed for game stuff. It was designed as, well, basically some kind of on the fly drawing thing, guess the designers had Photoshop clones in mind, but definitely not games, let alone the fact that there's no fast clear operation proves that, there's not even optimization in place when clearing the whole canvas with the same color.
If the images are already composited, not moving relative to one another, and defined by a rectangular region, then using canvas.drawImage() with a canvas as the first parameter and drawing to a sub-region should be significantly faster than re-drawing all the objects.
You could also just layer multiple canvases and slide the top canvas with the objects in HTML to scroll them.
Edit: Having really looked at your example, it seems to me that it should be implemented similar to Google Maps: create tiles of canvases and slide them left/right on the HTML page; once a canvas has been slid off the screen entirely (for example, off the left edge), move it to the other side (to the right edge) and re-use it for drawing. With this you will only need to re-draw whatever objects overlap the canvases that are moving on the edges.
You can draw all objects on a second, off-screen canvas and then only blit the whole canvas (drawImage() accepts canvas element).
However, if you're targeting desktop browsers, then this shouldn't be necessary. I've implemented tile engine (source) that simply redraws whole scene and naive implementation turned out to be pretty fast.
What I did to solve this problem was I had 10 squares on my screen and I wanted to animate them on a white background. So I drew a white rectangle over the canvas to clear the canvas so the animation would work. Does that make sense?
#Ivo By the way I read on http://www.w3.org/TR/html5/the-canvas-element.html that canvas was made for applications like games because it was a solution to get rid of the dependency on a external engine. Canvas is built in so it's kind of like a flash player built into your browser powered by JavaScript. I think it's fascinating.
You can use tiled rendering.
http://www.gamesfrommars.fr/demojsv2/ (better viewed with Chrome)