JavaScript Canvas - OffscreenCanvas |
Written by Ian Elliot | ||||
Monday, 16 September 2019 | ||||
Page 3 of 3
The UI thread is now very simple: var worker = new Worker("animate.js"); var ctx = document.body.appendChild( You can see that all the UI thread has to do is respond to the event sent by the worker. It draws the image to the canvas and updates the text box. If you try this out you will discover that it doesn’t quite work. The reason is that when you clear the OffscreenCanvas all of the pixels are set to transparent black, i.e. background pixels. When you draw the bitmap onto the canvas only the foreground pixels change the canvas. What this means is that you don’t get a clearing of the canvas at each frame. There are a number of different ways around the problem. If you are using drawImage then the simplest is to change the composition rule to: ctx.globalCompositeOperation="copy"; With this change the OffscreenCanvas replaces all of the pixels in the DOM canvas. You can see the complete modified program at www.iopress.info. This raises the question of why the OffscreenCanvas cannot be used to replace the current bitmap displayed by the canvas without the need to use compositing. The latest part of the standard provides a new graphics context for the canvas and a new method that allows the bitmap being displayed to be swapped to another. To use this you have to change the UI graphics context to: var ctx = document.body.appendChild( Currently only Chrome supports the “bitmaprenderer” context. With this change we can now transfer the bitmap without compositing and at maximum speed: worker.addEventListener("message", function (e) { ctx.transferFromImageBitmap(e.data.frame); fps.value =e.data.fps; }); TransferControlToOffScreenThere is another way to organize things that makes animation very simple. As the use of an OffscreenCanvas to update a DOM canvas is so common, there is a way to link an OffscreenCanvas and a DOM canvas so that updates to the OffscreenCanvas are automatically transferred to the DOM canvas. Simply create a DOM canvas and use the transferControlToOffScreen method to return an OffscreenCanvas that is linked to it. You can pass this OffscreenCanvas to a web worker and it can draw on it. Every time the browser does a repaint the OffscreenCanvas is automatically used to update the DOM canvas. The only downside is that you cannot make use of the DOM canvas directly – it is essentially just a “front” for the object doing all the work, the OffscreenCanvas. This is potentially the fastest way to use an OffscreenCanvas to display changes at the highest frame rate. So how would we change the previous bouncing ball program to make use of this? You can see the complete modified program at www.iopress.info. The main program in the UI thread is just: var worker = new Worker("animate.js"); var offCanvas = document.body.appendChild( That is all you need as the worker thread does everything else. It needs an event handler to accept the message that gives it the OffscreenCanvas and make use of it: var ctx; var ballImage; this.addEventListener("message", function (e) { ctx = e.data.canvas.getContext("2d"); var ctx2 = new OffscreenCanvas(40, 40).getContext("2d"); var path = new Path2D(); var r = 20; path.arc(20, 20, r, 0, 2 * Math.PI); ctx2.fill(path); ballImage = ctx2.canvas.transferToImageBitmap(); var noBalls = 80; var balls = []; for (i = 0; i < noBalls; i++) { balls[i] = new Ball( Notice that this means the whole of the web worker’s main program is now in the event handler. The worker can’t start doing anything until it has the OffscreenCanvas provided by the GUI thread and, once it has it, it can get on with the animation as before. The only changes needed are to keep ctx and ballImage global so that other methods can access them. The Animation.run method is now reduced to: Animation.run = function (t) { Animation.frameRate(t); Animation.clearCanvas(); for (var i = 0; i < Animation.spriteList.length; i++){ var ball1 = Animation.spriteList[i]; ball1.update(); ball1.render(); } requestAnimationFrame(Animation.run); }; Notice that now there is no need to post any data back to the UI. All that has to be done is to wait for the next animation frame. The update to the UI thread is performed at the next page render. If you try this out you will find that there is no longer any frame rate feedback because the web worker is no longer sending any data to the UI thread. This is easy to fix by making the frameRate method post the data back to the UI thread: Animation.frameRate = function (t) { if (typeof t !== "undefined") { Animation.frameRate.temp = The UI thread simply needs an event handler to show the frame rate data: worker.addEventListener("message", function (e) { fps.value = e.data.fps; }); With this change it all works as before. The advantage is that the browser is responsible for optimizing the update of the display and this should minimize any inefficient moving of data between bitmaps. You can see the complete modified program at www.iopress.info. Summary
Now available as a paperback or ebook from Amazon.JavaScript Bitmap Graphics
|
||||
Last Updated ( Monday, 16 September 2019 ) |