I'm seeing a really odd canvas clearRect performance issue.
In our game, the current move is rendered with a small white square 7px * 7px.
Once that move is over the square is removed with a clearRect(x, y, 7, 7) function.
This performs fine, but leaves a weird white outline behind. Which I believe is sub pixel rounding:
I can solve this, by issuing a different clearRect call, specifically clearRect(x, y - 1, 8, 8) this removes the white residue which kind of confirms the sub-pixel theory, but for some reason the performance is significantly worse.
This is the initial 7*7 clear: https://www.useloom.com/share/9b6bbd00647a4803ad5d0a8a4ce77d3a
And this is the 8x8 clear:
https://www.useloom.com/share/c36f7b2e81e74d1d9bfd9e16124a7503
Should I really be seeing this jank with such a small change?
To put things into context, each frame I have a series of messages to render (the coloured squares / crosses) these could number 1-2000. I also need to render the current message in a different colour (which is on a different canvas).
So I have a function renderMessages responsible for rendering each message each frame.
renderMessages() {
this.messages.forEach((data) => {
this.renderCurrentTask(data)
this.renderCell(data)
})
this.messages = []
window.requestAnimationFrame(() => this.renderMessages())
}
The function where I'm drawing and then clearing the canvas is renderCurrentTask. As a result when I try to use something simple like a clearRect(0,0,w,h) I'm seeing poor perf as I have to call it maybe 2000 per frame.
If it's any help in diagnosing this is my renderCurrentTask function where the small clearRect change has the negative effect:
renderCurrentTask(data) {
if(this.lastCoordinates) {
this.interactiveContext.clearRect(this.lastCoordinates.x,
this.lastCoordinates.y,
this.lastCoordinates.wh,
this.lastCoordinates.wh)
}
const coordinate = this.addressToScreenCoordinate(data.address)
this.interactiveContext.fillStyle = '#ffffff'
this.interactiveContext.fillRect(
coordinate.x,
coordinate.y,
this.cellSize,
this.cellSize)
this.lastCoordinates = {
x: coordinate.x,
y: coordinate. y - 1,
wh: this.cellSize + 2
}
}
If your canvas game has performance issue even after trying different methods to optimize it, you could try using webgl renderer. I suggest using a library for rendering 2d using webgl, for example Pixi.js. This solution could require a lot of rewriting your current code tho.
Related
For a university project I have been tasked with creating a Flappy Bird clone. It's being done using the HTML5 canvas.
The issue doesn't happen very often, but it seems that every 6 or so seconds, the grass will flicker. I'm not sure what's causing this, it could be a performance issue.
Here is a link so you may see the issue: http://canvas.pixcelstudios.uk
Here is the function I'm using to the draw the grass:
var drawGrass = function(cWidth, ctx, minusX)
{
var x = bg_grass.x;
var y = bg_grass.y;
var w = bg_grass.w;
var h = bg_grass.h;
var img = bg_grass.img;
if (minusX[0] >= cWidth)
{
bg_grass.x = 0;
minusX[0] = 0;
}
ctx.drawImage(img, x, y, w, h);
if (minusX[0] > 0)
{
ctx.drawImage(img, w-minusX[0], y, w, h);
}
};
Basically, I'm drawing two grass sprites, each taking up a canvas width. One starts with an X of 0 and the other starts at the end of the canvas. Both are decremented each frame, then one is completely off the screen, it's completely reset to keep it looping.
I don't think it's anything to do with my update loop which is as follows:
this.update = function()
{
clearScreen();
updateBackground();
updatePositions();
checkCollisions();
render();
requestAnimFrame(gameSpace.update);
};
I've done a little bit of reading and I've read about having a second canvas to act as a buffer. Apparently this can stop flickering and improve performance? But all of the examples I've seen show the parts being drawn into the canvas out of a loop and I can't really see how doing it within a game loop (moving parts and all) would increase performance rather than decrease it. Surely the same operations are being performed, except now you also have to draw the second canvas onto the first?
Please let me know if you need any more information (although you should be able to see the whole source from the web link).
Thanks!
Okay I found the issue! Was just a simple mistake in my drawGrass function.
Due to the ordering, there'd be just a single frame where I'd set my shorthand X variable to bg_grass.x and THEN set bg_grass.x to something else, therefore drawing the wrong value.
I've now set my shorthand variables after the first if-statement.
However, if anyone could provide any insight into the second part of the question regarding a buffer canvas, I'd still much appreciate that.
)
I have a small web-application which uses jquery and jcanvas (http://calebevans.me/projects/jcanvas/) to print some shapes onto a canvas.
It's basically a map. The user can zoom into it and drag it around and whenever he does so everything has to be drawn again.
As you may see in the code below I remove and recreate layers pretty often (whenever the user drags the map, zooms or resizes his window). I need the layers to handle hover- and click-events. My question is whether there is a big performance impact of this way to handle events in comparison to other solutions. If this is the case, how could I optimize my performance?
var posX = 0, posY = 0;
var zoom = 100;
var points = []; //array of up to 1000 points retrieved by ajax
function draw(){
$("canvas").removeLayers();
$("canvas").clearCanvas();
var xp, yp, ra;
var name;
$.each(points, function(index) {
xp = (this["x"]-posX)/zoom;
yp = (this["y"]-posY)/zoom;
ra = 1000/zoom;
$("#map").drawArc({
layer:true,
fillStyle: "black",
x: xp,
y: yp,
radius: ra,
mouseover: function(layer) {
$(this).animateLayer(layer, {
fillStyle: "#c33",
scale: 1.0
},200);
},
mouseout: function(layer) {
$(this).animateLayer(layer, {
fillStyle: "black",
scale: 1.0
},200);
},
mouseup: function(layer){
context(index,layer.x,layer.y);
}
});
});
}
Thank you for your time :-)
Optimization is in general a case-to-case thing but I'll try to give some general points for this case:
Keep operations to a minimum
Creating and removing "layers" (canvas elements) are costly operations - try to avoid if possible.
Rather move content around using drawImage() on itself or use clearRect() if you need to clear the whole canvas.
The same with a little more details:
You can for example use drawImage() to move the content to one of the sides, if we wanted to move everything to the left by 10 pixels we could do:
context.drawImage(canvas, 10, 0, canvas.width - 10, canvas.height,
0, 0, canvas.width - 10, canvas.height);
This will clip only the part we want to move/scroll and then redraw it in the new position.
Finally draw in the 10px gap at right with new graphics (you would probably want to cache the width and height but frankly, in the modern JavaScript engines this does not matter so much anymore as it did in the past). Test your points to see what points actually needs to be drawn to avoid drawing all of them again which otherwise renders this step pointless.
If you want to clear the canvas, rather than removing the canvas element and create a new canvas use the method clearRect() or use fillRect() if you want a pattern, color etc. Removing and creating elements are a costly operation from a optimization perspective especially if they affect the browser's flow (if it need to reflow the content). It triggers a bunch of operations from layout, css parsing, repaint etc. Reuse if you can.
I've got an issue with an experiment I'm working on.
My plan is to have a beautiful and shining stars Background on a whole page.
Using that wondeful tutorial (http://timothypoon.com/blog/2011/01/19/html5-canvas-particle-animation/) I managed to get the perfect background.
I use a static canvas to display static stars and an animated canvas for the shining ones.
The fact is it's very memory hungry! On chrome and opera it runs quite smoothly, but on firefox IE or tablet, it was a total mess 1s to render each frame etc... It is worse on pages where HEIGHT is huge.
So i went into some optimisations:
-Using a buffer canvas, the problem was createRadialGradient which was called 1500 times each frame
-Using a big buffer canvas, and 1 canvas for each stars with an only call to createRadialGradient at init.
-Remove that buffer canvas and drawing every stars canvas to the main one
That last optimisation was the best i could achieve so i wrote a fiddle displaying how is the code right now.
//Buffering the star image
this.scanvas = document.createElement('canvas');
this.scanvas.width=2*this.r;
this.scanvas.height=2*this.r;
this.scon=this.scanvas.getContext('2d');
g = this.scon.createRadialGradient(this.r,this.r,0,this.r,this.r,this.r);
g.addColorStop(0.0, 'rgba(255,255,255,0.9)');
g.addColorStop(this.stop, 'rgba('+this.color.r+','+this.color.g+','+this.color.b+','+this.stop+')');
g.addColorStop(1.0, 'rgba('+this.color.r+','+this.color.g+','+this.color.b+',0)');
this.scon.fillStyle = g;
this.scon.fillRect(0,0,2*this.r,2*this.r);
That's the point where I need you:
-A way to adjust the number of shining stars according to the user perfomance
-Optimisation tips
Thanks in advance to everyone minding to help me and I apologize if I made grammar mistakes, my english isn't perfect.
EDIT
Thanks for your feedbacks,
Let me explains the whole process,
Every stars has it's own different gradient and size, that's why I stored it into a personal canvas, the shining effect is only done by scaling that canvas on the main one with drawImage.
I think the best would be to prerender 50 or 100 different stars in a buffer canvas then picking and drawing a random one, don't you think?
EDIT2
Updated fiddle according to Warlock great advises, one prerendered star, scaled to match the current size. The stars are less pretty, but the whole thing runs a lot smoother.
EDIT3
Updated fiddle to use a sprite sheet. Gorgeous!!!!
//generate the star strip
var len=(ttlm/rint)|0;
scanvas = document.createElement('canvas');
scanvas.width=len*2*r;
scanvas.height=2*r;
scon=scanvas.getContext('2d');
for(var i=0;i<len;i++){
var newo = (i/len);
var cr = r*newo;
g = scon.createRadialGradient(2*r*i+r,r,0,2*r*i+r,r,(cr <= 2 ? 2 : cr));
g.addColorStop(0.0, 'rgba(200,220,255,'+newo+')');
g.addColorStop(0.2, 'rgba(200,220,255,'+(newo*.7)+')');
g.addColorStop(0.4, 'rgba(150,170,205,'+(newo*.2)+')');
g.addColorStop(0.7, 'rgba(150,170,205,0)');
scon.fillStyle = g;
scon.fillRect(2*r*i,0,2*r,2*r);
}
EDIT 4(Final)
Dynamic stars creations
function draw() {
frameTime.push(Date.now());
con.clearRect(0,0,WIDTH,HEIGHT);
for(var i = 0, len = pxs.length; i < len; i++) {
pxs[i].fade();
pxs[i].draw();
}
requestAnimationFrame(draw);
if(allowMore === true && frameTime.length == monitoredFrame)
{
if(getAvgTime()<threshold && pxs.length<totalStars )
{
addStars();
}
else
{
allowMore=false;
static=true;
fillpxs(totalStars-pxs.length,pxss);
drawstatic();
static=false;
}
}
}
Here is the updated and final fiddle, with spritesheet, dynamic stars creation and several optimisations. If you see anything else i should update don't hesitate.
POST EDIT Reenabled shooting stars/Prototyped object/got rid of Jquery
http://jsfiddle.net/macintox/K8YTu/32/
Thanks everyone who helped me, that was really kind and instructive, and I hope it will help somebody sometimes.
Aesdotjs.
PS: I'm so happy. After testing, that script run smoothly on every browser even IE9. Yatta!!
Adopting to browser performance
To measure capability of the user's setup you can implement a dynamic star creator which stops at a certain threshold.
For example, in your code you define a minimum number of stars to draw. Then in your main loop you measure the time and if the time spent drawing the stars are less than your max threshold you add 10 more stars (I'm just throwing out a number here).
Not many are aware of that requestAnimationFrame gives an argument (DOMHighResTimeStamp) to the function it calls with time in milliseconds spent since last request. This will help you keep track of load and as we know that 60 fps is about 16.7 ms per frame we can set a threshold a little under this to be optimal and still allow some overhead for other browser stuff.
A code could look like this:
var minCount = 100, /// minimum number of stars
batchCount = 10, /// stars to add each frame
threshold= 14, /// milliseconds for each frame used
allowMore = true; /// keep adding
/// generate initial stars
generateStarts(minCount);
/// timeUsed contains the time in ms since last requestAnimationFrame was called
function loop(timeUsed) {
if (allowMore === true && timeUsed < threshold) {
addMoreStars(batchNumber);
} else {
allowMore = false;
}
/// render stars
requestAnimationFrame(loop);
}
Just note that this is a bit simplified. You will need to run a few rounds first and measure the average to have this work better as you can and will get peak when you add stars (and due to other browser operations).
So add stars, measure a few rounds, if average is below threshold add stars and repeat.
Optimizations
Sprite-sheets
As to optimizations sprite-sheets are the way to go. And they don't have to just be the stars (I'll try to explain below).
The gradient and arc is the costly part of this applications. Even when pre-rendering a single star there is cost in resizing so many stars due to interpolation etc.
When there becomes a lot of costly operations it is better to do a compromise with memory usage and pre-render everything you can.
For example: render the various sizes by first rendering a big star using gradient and arc.
Use that star to draw the other sizes as a strip of stars with the same cell size.
Now, draw only half of the number of stars using the sprite-sheet and draw clipped parts of the sprite-sheet (and not re-sized). Then rotate the canvas 90 degrees and draw the canvas itself on top of itself in a different position (the canvas becoming a big "sprite-sheet" in itself).
Rotating 90 degrees is not so performance hungry as other degrees (0, 90, 180, 270 are optimized). This will give you the illusion of having the actual amount of stars and since it's rotated we are not able to detect a repetitive pattern that easy.
A single drawImage operation of canvas is faster than many small draw operations of all the stars.
(and of course, you can do this many times instead of just once up to a point right before where you start see patterns - there is no key answer to how many, what size etc. so to find the right balance is always an experiment).
Integer numbers
Other optimizations can be using only integer positions and sizes. When you use float numbers sub-pixeling is activated which is costly as the browser need to calculate anti-alias for the offset pixels.
Using integer values can help as sub-pixeling isn't needed (but this doesn't mean the image won't be interpolated if not 1:1 dimension).
Memory bounds
You can also help the underlying low-lowel bitmap handling a tiny bit by using sizes and positions dividable on 4. This has to do with memory copy and low-level clipping. You can always make several sprite-sheet to variate positions within a cell that is dividable on 4.
This trick is more valuable on slower computers (ie. typical consumer spec'ed computers).
Turn off anti-aliasing
Turn off anti-aliasing for images. This will help performance but will give a little more rough result of the stars. To turn off image anti-aliasing do this:
ctx.webkitEnableImageSmoothing = false;
ctx.mozEnableImageSmoothing = false;
ctx.enableImageSmoothing = false;
You will by doing this see a noticeable improvement in performance as long as you use drawImage to render the stars.
Cache everything
Cache everything you can cache, being the star image as well as variables.
When you do this stars.length the browser's parser need to first find stars and then traverse that tree to find length - for each round (this may be optimized in some browsers).
If you first cache this to a variable var len = stars.length the browser only need to traverse the tree and branch once and in the loop it will only need to look up the local scope to find variable len which is faster.
Resolution reduction
You can also reduce resolution in half, ie. do everything at half the target size. In the final step draw your render enlarged to full size. This will save you initially 75% render area but give you a bit low-res look as a result.
From the professional video world we often use low-resolution when things are animated (primarily moving) as the eye/brain patch up (or can't detect) so much details when objects are moving and therefor isn't so noticeable. If this can help here must be tested - perhaps not since the stars aren't actually moving, but worth a try for the second benefit: increased performance.
How about just creating a spritesheet of a star in its various stages of radial glow.
You could even use canvas to initially create the spritesheet.
Then use context.drawImage(spritesheet,spriteX,spriteY,starWidth,starHeight) to display the star.
Spritesheet images can be drawn to the screen very quickly with very little overhead.
You might further optimize by breaking the spritesheet into individual star images.
Good luck on your project :)
1. Minimize operations, related to the DOM;
In the LINE 93 you are creating canvas:
this.scanvas = document.createElement('canvas');
You need only one canvas instead of this. Move canvas creation to the initialization step.
2. Use integer coordinates for canvas;
3. Use Object Pool design pattern to improve performance.
4. In for loops cache the length variable:
for(var i = 0; i < pxs.length; i++) {...
}
Better:
for(var i = 0, len = pxs.length; i < len; i++) {
...
}
Note: don't mix jquery with native js.
Noticed something I didn't expect when playing with animation in Canvas. I have a fairly easy animation of five images in different sizes moving from bottom to top of screen in a loop.
I have all my image data in an array and draw them onto the canvas via a loop, then use window.requestAnimationFrame to do a new draw on next frame.
Here is the interesting part, at first I just cleared the canvas for each frame rendered using context.clearRect(0, 0, canvas.width, canvas.height);. Then I though that this must be a waste of computing, to clear the entire canvas for each render even thou only parts of the screen actually changed.
So I rewrote the part clearing the canvas to only clear the trace of the old image draw, using something like this:
for (var key in _images) {
context.clearRect(_images[key].x-1, _images[key].y+_images[key].height, _images[key].width+2, 5);
}
But too my surprise this seems to be slower... First I had a frame rate at 49-60, and after 47-57. Any idea to why? And is there any other way to optimize this?
0) i would rather write :
for (var i=0, len=_images.length; i<len; i++) {
var thisImage = _images[i];
context.clearRect(thisImage.x, thisImage.y, thisImage.width, thisImage.height);
}
1) when requestAnimationFrame triggers, draw before updating, so you are right in sync with the screen.
2) round the coordinates to the nearest 64 bits, i.e. use &(~3) on each x coordinates can speed things up.
3) You might group some clearRect together (= all the bullets / a tiled horizontal floor, ...)
4) i think the main question here is the overhead of those methods.
I did a jsperf to know more :
http://jsperf.com/overhead-of-fillrect-and-clearrect/2
!! be sure to have the canvas in sight during the tests !!
Results :
Firefox, clearRect has very small overhead, and time seems
almost proportionnal with pixel count.
fillRect is like 60% slower and seems quite proportionnal also
with area covered.
64 bit Unalignment seems of no effet on clearRect, it makes performances
fall badly on fillRect.
some times from jsPerf (precision is so-so) :
208ns for fullscreen clearRect
55ns for halfscreen clearRect -> 4 times makes 220ns vs 208
13ns for quarter screen clearRect -> 16 times makes 208 (!!!)
588ns fullscreen fillRect
1290ns halfscreen fillRect --> 4 times makes 1160
41ns quarterscreen fillrect --> 16 times makes 656.
Safari, everything the results are quite the same, except there is
more overhead : it is only 3 times faster to draw 4 times less points.
I know the the precision is so-so but it seems that on firefox
and Safari, the conclusion is :
the less part of your screen you clear, the faster you erase.
And prefer clearRect to fillRect -
Chrome : (date 05 / 14) fillRect is much faster than clearRect.
IE11 : fillRect and clearRect are on par.
((If anyone can see on other Browser, i'll edit -when i have some time-))
( provided your images are 4 pixels aligned, the code with
64 align is :
for (var i=0, len=_images.length; i<len; i++) {
var thisImage = _images[i];
var x = thisImage.x ;
context.clearRect(x & (~3), thisImage.y, thisImage.width + ((x&3)&&(16-(x&3))), thisImage.height);
}
)
Since people answered in comments and I do feel they covered my questions. Here's a summery:
Each clearRect() takes up roughly the same amount of time, so it is better to have few than many.
To optimize one should calculate the area that needs to be cleared, if not 100%, and clear that.
I'm making an HTML5 game engine, and I want my Camera object to have a zoom property. In the renderer, I thought that I could easily implement it, like this:
context.save();
context.scale(camera.zoom, camera.zoom);
draw();
context.restore();
There is a problem, though. When I first tested this, the camera seemed to zoom forever! I figured that context.save() and context.restore() probably aren't working as expected, and that the context's internal scaling factor is getting multiplied by camera's zoom ad infinitum.
This fixed the situation:
context.save();
context.scale(camera.zoom, camera.zoom);
draw();
context.scale(1/camera.zoom, camera.zoom);
context.restore();
This works now, but I'm afraid that this isn't the most elegant/fast solution. Also, I think it is possible that, because of the floating point calculation imprecision, the scaling factor always changes slightly. That is, 1/camera.zoom might not always produce the same results.
So, two questions:
Why wont the context.restore() set the scale of the context back to (1, 1)?
How can I manually manipulate the scaling of the context?
Edit:
It was pointed out that the number of context.save()'s and context.restore()'s might be different, but that is not the case.
Here is how I measured it:
renderer.context.save = (function()
{
var original = renderer.context.save;
return function()
{
renderer.saved ++;
original.call(renderer.context);
}
})();
renderer.context.restore = (function()
{
var original = renderer.context.restore;
return function()
{
renderer.saved --;
original.call(renderer.context);
}
})();
The renderer.saved value is 1 right before the context is restored one last time (after draw), and 0 after each rendering.
It seems like I have accidentally solved the problem. It works now.
The main suspect is this part of the code:
renderer.context.save();
//Erase everything.
renderer.context.globalAlpha = 1;
renderer.context.fillStyle = renderer.settings.fillStyle;
renderer.context.fillRect(0, 0, renderer.width, renderer.height);
//Zoom.
renderer.context.scale(camera.zoom, camera.zoom);
I believe that I used to zoom before actually saving the context, resulting in restoring having no effect on the zoom.