How efficient is layering multiple canvases? - javascript

I've been wondering if layering canvases is actually an efficient means of increasing performance on a game.
E.g, 2 or 3 canvases of the same size on top of each other.
It's difficult to find results of testing from this, but from what I understand about drawing, if the layer on top of the others is changed, the browser would have to repaint the area to pixels anyway.
Furthermore if those canvases all had to move at the same time, you've multiplied the amount of pixels that would need to be calculated.
I was wondering how the browser handles painting canvases beneath the others if an element above changes.
Is it more efficient due to not having to call the canvas methods themselves again? Wouldn't they still need painting?

My experience is anecdotal, so you really need to test your idea to see if its better.
You're probably going to get the best performance by creating offscreen (in memory) canvases and compositing the different layers of your game with those into a single visible canvas where the game is viewed.

I'm not sure if this will be very helpful, but I'll tell you anyway.
Let's say that you've got 2 canvases and you want your game to switch between them under certain condition. This might be possible by using some HTML, CSS and JavaScript.
<canvas id="canvas1" width=500 height=500 style="border: 2px solid #00F"></canvas>
<canvas id="canvas2" width=500 height=500 style="border: 2px solid #F00"></canvas>
<button id="switcher" onclick="switchcanvas=true">Click here to switch the canvas!</button>
<style>
#canvas1 {
left: 0%;
right: 0%;
display: block;
}
#canvas2 {
left: 0%;
right: 0%;
display: none;
}
</style>
<script>
var switchcanvas = false;
var canvas1 = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
function update() {
if (switchcanvas) {
if (canvas1.style.display === "block") {
canvas1.style.display = "none";
canvas2.style.display = "block";
switchcanvas = false; //if you set this to true or remove it the canvases will keep switching.
} else {
canvas1.style.display = "block";
canvas2.style.display = "none";
switchcanvas = false;
}
}
}
update();
setInterval(update, 1); //function update will update every ms, you can change that though.
</script>
I tested it and it worked. And yes, it should improve your game's performance since each canvas can have its own context.

I don't know if this is correct, but in my experience if you use two Canvases with the same frame (refresh) rate the performance improvement would be minimal, if there is any at all. You would get an improvement if the refesh rate for Canvas 1 is lower than Canvas 2. A good use case for this would be a grid and a moving object. If the grid doesn't change, you don't have to redraw it. This does not apply for the moving object, because you want to see it moving.

Related

Pure Javascript rAF (requestAnimationFrame) - how to update screen at highest available framerate?

I am learning JavaScript, coming from eight years of Python experience, and as an exercise I made a Mandelbrot renderer that generates the fractal and uses putImageData to update the canvas from left to right one single-pixel column at a time.
I found that with this approach the visible image in the browser only updates when the full-screen calculation is complete (rather than seeing it appear gradually from left to right as I wanted). I understand that it is the expected behaviour, so I decided to add the "scan-line" animation by using requestAnimationFrame. (something like seen here: Christian Stigen Larsen renderer)
My expectation is that lighter calculations should render faster (as the next frame is available sooner) and higher-iteration calculations should render slower. What I found with my implementation is that the scan-line is consistently slow.
I have isolated this issue from anything to do with my Mandelbrot calculations, as this behaviour also happens for the minimal case below. I am running this on Chrome 83 and see the canvas populate very slowly at a constant rate (approx 30 pixels per second) from left to right.
Is my implementation of rAF incorrect or are my expectations wrong? The renderer I linked to uses setTimeout() to animate, but I read that it is a widely discouraged practice these days.
How should I implement a left-to-right scan update of my canvas at the highest available frame-rate (I am not worried about limiting it at the moment)?
EDIT: For clarity, the code below draws a single thin rectangle at every frame request by rAF, and takes exactly the same amount of time to paint the full canvas as a 100-iteration Mandelbrot render.
This suggest to me that the slowness of it is not due to amount of calculations taking place between frames.
const canvas = document.querySelector('.myCanvas');
const width = window.innerWidth;
const height = window.innerHeight;
const ctx = canvas.getContext('2d');
function anim(timestamp, i) {
if (i < width) {
ctx.fillRect(i, 0, 1, height);
window.requestAnimationFrame(function(timestamp) {
anim(timestamp, i + 1);
})
}
}
ctx.fillStyle = 'rgb(0,0,0)';
window.requestAnimationFrame(function(timestamp) {
anim(timestamp, 0);
});
<!DOCTYPE html>
<html lang="en">
<head>
<canvas class="myCanvas">
<p>Add suitable fallback here.</p>
</canvas>
<script src="slow%20rAF.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<meta charset="UTF-8">
<title>Mandelbrot</title>
</head>
<body>
</body>
</html>
There is no 'faster' requestAnimationFrame.
Generally the answer to this is to separate calcuations from rendering. Ideally the calculations happen off of the main thread and rendering is handled on the main thread. Even without threading you should be able to find a way to do the calculations outside of the RAF, and just have the RAF render the pixels.

jQuery resize window background [duplicate]

What I'd like to do is draw my graphics on a buffer and then be able to copy it as is to the canvas so I can do animation and avoid flickering. But I couldn't find this option. Anyone know how I can go about this?
A very simple method is to have two canvas-elements at the same screen location and set visibility for the buffer that you need to show. Draw on the hidden and flip when you are done.
Some code:
CSS:
canvas { border: 2px solid #000; position:absolute; top:0;left:0;
visibility: hidden; }
Flipping in JS:
Buffers[1-DrawingBuffer].style.visibility='hidden';
Buffers[DrawingBuffer].style.visibility='visible';
DrawingBuffer=1-DrawingBuffer;
In this code the array 'Buffers[]' holds both canvas-objects. So when you want to start drawing you still need to get the context:
var context = Buffers[DrawingBuffer].getContext('2d');
The following helpful link, in addition to showing examples and advantages of using double buffering, shows several other performance tips for using the html5 canvas element. It includes links to jsPerf tests, which aggregate test results across browsers into a Browserscope database. This ensures that the performance tips are verified.
https://web.dev/canvas-performance/
For your convenience, I have included a minimal example of effective double buffering as described in the article.
// canvas element in DOM
var canvas1 = document.getElementById('canvas1');
var context1 = canvas1.getContext('2d');
// buffer canvas
var canvas2 = document.createElement('canvas');
canvas2.width = 150;
canvas2.height = 150;
var context2 = canvas2.getContext('2d');
// create something on the canvas
context2.beginPath();
context2.moveTo(10,10);
context2.lineTo(10,30);
context2.stroke();
//render the buffered canvas onto the original canvas element
context1.drawImage(canvas2, 0, 0);
Browsers I've tested all handle this buffering for you by not repainting the canvas until the code that draws your frame has completed. See also the WHATWG mailing list: http://www.mail-archive.com/whatwg#lists.whatwg.org/msg19969.html
You could always do
var canvas2 = document.createElement("canvas");
and not append it to the DOM at all.
Just saying since you guys seem so obsessed with display:none;
it just seems cleaner to me and mimicks the idea of double buffering way more accurately than just having an awkwardly invisible canvas.
More than two years later:
There is no need for 'manually' implement double buffering. Mr. Geary wrote about this in his book "HTML5 Canvas".
To effectively reduce flicker use requestAnimationFrame()!
For the unbelievers, here's some flickering code. Note that I'm explicitly clearing to erase the previous circle.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function draw_ball(ball) {
ctx.clearRect(0, 0, 400, 400);
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.arc(ball.x, ball.y, 30, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
var deltat = 1;
var ball = {};
ball.y = 0;
ball.x = 200;
ball.vy = 0;
ball.vx = 10;
ball.ay = 9.8;
ball.ax = 0.1;
function compute_position() {
if (ball.y > 370 && ball.vy > 0) {
ball.vy = -ball.vy * 84 / 86;
}
if (ball.x < 30) {
ball.vx = -ball.vx;
ball.ax = -ball.ax;
} else if (ball.x > 370) {
ball.vx = -ball.vx;
ball.ax = -ball.ax;
}
ball.ax = ball.ax / 2;
ball.vx = ball.vx * 185 / 186;
ball.y = ball.y + ball.vy * deltat + ball.ay * deltat * deltat / 2
ball.x = ball.x + ball.vx * deltat + ball.ax * deltat * deltat / 2
ball.vy = ball.vy + ball.ay * deltat
ball.vx = ball.vx + ball.ax * deltat
draw_ball(ball);
}
setInterval(compute_position, 40);
<!DOCTYPE html>
<html>
<head><title>Basketball</title></head>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
</body></html>
Josh asked (a while back) about how the browser knows "when the drawing process ends" so as to avoid flicker. I would have commented directly to his post but my rep isn't high enough. Also this is just my opinion. I don't have facts to back it up, but I feel fairly confident about it and it may be helpful to others reading this in the future.
I'm guessing the browser doesn't "know" when you're done drawing. But just like most javascript, as long as your code runs without relinquishing control to the browser, the browser is essentially locked up and won't/can't update/respond to its UI. I'm guessing that if you clear the canvas and draw your entire frame without relinquishing control to the browser, it won't actually draw your canvas until you're done.
If you set up a situation where your rendering spans multiple setTimeout/setInterval/requestAnimationFrame calls, where you clear the canvas in one call and draw elements on your canvas in the next several calls, repeating the cycle (for example) every 5 calls, I'd be willing to bet you'd see flicker since the canvas would be updated after each call.
That said, I'm not sure I'd trust that. We're already at the point that javascript is compiled down to native machine code before execution (at least that's what Chrome's V8 engine does from what I understand). I wouldn't be surprised if it wasn't too long before browsers started running their javascript in a separate thread from the UI and synchronizing any access to UI elements allowing the UI to update/respond during javascript execution that wasn't accessing UI. When/if that happens (and I understand there are many hurdles that would have to be overcome, such as event handlers kicking off while you're still running other code), we'll probably see flicker on canvas animation that aren't using some kind of double-buffering.
Personally, I love the idea of two canvas elements positioned over top of each other and alternating which is shown/drawn on each frame. Fairly unintrusive and probably pretty easily added to an existing application with a few lines of code.
There is no flickering in web browsers! They already use dbl buffering for their rendering. Js engine will make all your rendering before showing it. Also, context save and restore only stack transformational matrix data and such, not the canvas content itself.
So, you do not need or want dbl buffering!
Rather than rolling your own, you're probably going to get the best mileage by using an existing library for creating clean and flicker-free JavaScript animation:
Here's a popular one: http://processingjs.org
you need 2 canvas: (notice the css z-index and position:absolute)
<canvas id="layer1" width="760" height="600" style=" position:absolute; top:0;left:0;
visibility: visible; z-index: 0; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<canvas id="layer2" width="760" height="600" style="position:absolute; top:0;left:0;
visibility: visible; z-index: 1; solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
you can notice that the first canvas is visible and the second it's hidden the idea it's to draw on the hidden after that we will hide the visible and make the hidden canvas visible. when it's hidden 'clear hidden canvas
<script type="text/javascript">
var buff=new Array(2);
buff[0]=document.getElementById("layer1");
buff[1]=document.getElementById("layer2");
ctx[0]=buff[0].getContext("2d");
ctx[1]=buff[1].getContext("2d");
var current=0;
// draw the canvas (ctx[ current ]);
buff[1- current ].style.visibility='hidden';
buff[ current ].style.visibility='visible';
ctx[1-current].clearRect(0,0,760,600);
current =1-current;
In most situations, you don't need to do this, the browser implements this for you. But not always useful!
You still have to implement this when your drawing is very complicated.
Most of the screen update rate is about 60Hz, it means the screen updates per 16ms. The browser's update rate may near this number. If your shape need 100ms to be completed, you'll see a uncompleted shape. So you can implement double buffering in this situation.
I have made a test: Clear a rect, wait for some time, then fill with some color. If I set the time to 10ms, I won't see flickering. But if I set it to 20ms, the flickering happens.
Opera 9.10 is very slow and shows the drawing process. If you want to see a browser not use double buffering, try Opera 9.10 out.
Some people suggested that browsers are somehow determining when the drawing process ends but can you explain how that can work? I haven't noticed any obvious flicker in Firefox, Chrome, or IE9 even when the drawing is slow so it seems like that is what they are doing but how that is accomplished is a mystery to me. How would the browser ever know that it is refreshing the display just before more drawing instructions are to be executed? Do you think they just time it so if an interval of more than 5ms or so goes by without executing a canvas drawing instruction, it assumes it can safely swap buffers?
This is an example of double buffering with image data.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5 Canvas Demo of PixelBuffer</title>
<style>
html,
body {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<canvas id="buffer1"></canvas>
<script>
const canvas = document.getElementById('buffer1');
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(100, 100);
const pixelBuffer = new Uint32Array(imageData.data.buffer);
for (let i = 0; i < pixelBuffer.length; i++) {
pixelBuffer[i] = 0xFF0000FF;
}
ctx.putImageData(imageData, 0, 0);
</script>
</body>
</html>

How to manage memory in case of multiple fabric js canvas?

In my application, I have multiple Fabric.js canvases, There is no limit on the number of canvases. I'll render heavy JSON via loadFromJson method of Fabric.js.
So I want to release the fabric object memory if the canvas is not in use. How can I do that?
At a time only one canvas will be visible. But I have to render all the canvases as the page loads. Canvas is actually a page and user can switch between pages via clicking on page number or something else.
Remember user can come back to any canvas any time and try to doodle or use any other Fabric.js functionality.
Here is my HTML structure:
<style>
.fabricCanvas {
border: 1px solid green;
margin: 5px;
}
.canvas-container {
margin: 5px;
}
</style>
<canvas class="fabricCanvas" width="300" height="300"></canvas>
<canvas class="fabricCanvas" width="300" height="300"></canvas>
<canvas class="fabricCanvas" width="300" height="300"></canvas>
<canvas class="fabricCanvas" width="300" height="300"></canvas>
My JS code to store fabric instances
var canvasInstances = [];
$('canvas.fabricCanvas').each(function () {
var fabricCanvasObj = new fabric.Canvas(this, {
isDrawingMode: true
});
canvasInstances.push(fabricCanvasObj);
fabricCanvasObj.renderAll();
});
console.log(canvasInstances[0]);
I am storing instances so that I can use them later. I want this for better memory management, basically loading and unloading instances as and when needed.
Sample situation DEMO is here. In this demo consider that the canvases are over each other using z-indexes but they are the part of DOM and has already been rendered on page load.
Let me know in case of any doubt, I can explain further.
When ever there are more than 5 canvases iPad browser crashes which I think is the memory issue.
You might be interested in 3 things (in the order of significance/destruction):
canvas.clear() — removes all canvas objects from it.
canvas.dispose() — removes all canvas objects AND removes all event listeners
$(canvas.wrapperEl).remove() (using jQuery for illustrative purposes) — to remove canvas wrapper element (which contains upper and lower canvases used by Fabric). This can be done AFTER you call dispose, if the goal is to completely remove Fabric canvas from a document.

Performance Issues KineticJS

I'm quite new to canvas so I need some input.
I wan't to create a library overview with its stocks and so I have to handle about 2k+ rectangles. The problem is, the performance on drag and on scale is not very good, the fps drop below 10. Thats plain ugly, so I would appreciate some input what to do better or in other ways toe improve the performance of my (basic) script.
http://jsfiddle.net/kHGvh/13/embedded/result/
http://jsfiddle.net/kHGvh/13/
I just tried the same thing using Fabric.js, out of curiosity.
I do see noticeably better performance — http://jsfiddle.net/M7n4p/
Note that I'm using experimental "group_rewrite" branch — https://github.com/kangax/fabric.js/branches
FWIW, here's the code used to create it (just to give you comparison with Kinetic.js).
Markup:
<canvas id="c" width="1200" height="600" style="border:1px solid #ccc"></canvas>
JS:
var canvas = new fabric.Canvas('c');
var rects = [ ];
for (var i = 1; i <= 47; i++) {
for (var j = 1; j <= 42; j++) {
var rect = new fabric.Rect({
left: i*28,
top: j*18,
width: 20,
height: 10,
fill: 'green'
});
rects.push(rect);
}
}
canvas.add(new fabric.Group(rects));
I'm having the same issues as well when creating a draggable grid. However, I think there is little one can do about it.
You could think about lessening the number of cells you have. 2k+ rectangles is at least 2k+ objects that track the shape on the canvas. with the drag event happening at least 10 frames per second, you have 20k+ calculations and object accesses per second! The shear number of shapes it causing problems.

Implementing Layers in HTML5 Canvas

I am about to implement Photoshop-like Layers in HTML5 Canvas. Currently I have two ideas. The first and maybe the simpler idea is to have a Canvas element for each layer like:
<canvas id="layerName" width="320" height="240" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
<canvas id="layerName" width="320" height="240" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
<canvas id="layerName" width="320" height="240" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
This way when you draw to a layer -- it actually goes to that "layer". Layers with transparent positions can be seen through to below layers (Canvases). Layer stacking is controlled with z-index property.
The second idea is to use a single Canvas element and implement some logic to handle layers like in this case:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script>
window.addEventListener('load', function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var order = 0;
function drawLayer1() {
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);
}
function drawLayer2() {
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect (30, 30, 55, 50);
}
function draw() {
ctx.clearRect(0, 0, 256, 256);
if (order === 0) {
drawLayer1();
drawLayer2();
}
else {
drawLayer2();
drawLayer1();
}
}
setInterval(draw, 250);
setInterval(function() {
order = 1 - order;
}, 200);
}, false);
</script>
</head>
<body>
<canvas id="canvas" width="256px" height="256px"></canvas>
</body>
</html>
In the above code the two layers will change stacking order every 200msec.
So, the question is that which way would be the best way? What are the pros and cons of both approaches?
If you want to use a single canvas element and have multiple layers inside it, you might want to look at my library:
https://github.com/ant512/CanvasLayers
It uses a damaged rect system to reduce the amount of repainting done every time the canvas changes, so not only do you get layers (which can be nested), but you also get optimised redraws.
Using multiple canvases should be faster, because the canvas gets drawn off-screen and then just blitted to the screen by the browser. You put the burden of switching layers on the browser, which just has to move some rectangles of graphics data around.
If you do the layering yourself, you have more control, but the burden is on the JS and the JS engine to do all the work. I would avoid this if I had a choice, but if you're going for layer effects that work on the underlying layers, this might be your only choice.
Setting the container div relative ought to have prevented that layer-overwrite issue. Try setting the position on the "occluded text" - e.g. if it's currently absolue it will obvious go in the same region as the top left of the relative stuff.
And it's probably obvious but, by the order of divs in the html you can eliminate the use of the z axis. If you want your stuff to be generic (and for other developers too), use the z axis but store a baseline to which you add your layer indices (so that baseline can be tweaked when using other code using z-axis in a problematic way).
Using jCanvas,see its layer API: http://projects.calebevans.me/jcanvas/docs/layerAPI/

Categories