I'm experimenting with using OffscreenCanvas in webworkers and trying various strategies. In my original code I'm drawing on multiple canvas elements (stacked on top of each other) sequentially in a single requestAnimationFrame and was looking to see if I can do this in parallel using webworkers.
My current experiment involves calling canvas.transferControlToOffscreen()
and passing that to a webworker. In that same webworker I use a separate OffscreenCanvas
not attached to the DOM and would like to copy that content to the transferred canvas using a bitmaprenderer context, but I'm not succeeding in getting this copy to work without using a different context.
The following alternative code works:
const targetCanvasContext = targetCanvas.getContext("2d")!;
targetCanvasContext.clearRect(0, 0, 1920, 1080);
targetCanvasContext.drawImage(originCanvas, 0, 0);
but this is quite a lot of work compared to transferring the ImageBitmap directly using bitmaprenderer as I tried by doing the following instead:
const targetCanvasContext = targetCanvas.getContext("bitmaprenderer")!;
const bmp = originCanvas.transferToImageBitmap();
targetCanvasContext.transferFromImageBitmap(bmp);
The latter is however not producing any visual result nor is it throwing any errors.
What am I missing here?
There is this SO question that was asked 3 years ago (Is 'bitmaprenderer' not a valid value for off-screen canvas rendering context in Chrome?), which was resolved because at the time ImageBitmapRenderingContext was not supported in a webworker context, but as can be seen on mdn (https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmapRenderingContext) this should be the case today. Also my code is not throwing an error on the getContext request and actually returning a proper ImageBitmapRenderingContext whereas the related SO question did have errors thrown.
Yes, it is supposed to work and it does in Firefox. So this is a Chrome bug, which can even be reproduced without a Worker:
const ctx = new OffscreenCanvas(300,150).getContext("2d");
ctx.fillRect(50, 50, 50, 50);
const bmp = ctx.canvas.transferToImageBitmap();
const placeholder = document.querySelector("canvas").transferControlToOffscreen();
placeholder.getContext("bitmaprenderer").transferFromImageBitmap(bmp);
// in Firefox you can see the black square, not in Chrome
<canvas></canvas>
Given it's already been reported, there isn't much more you can do.
You already found a workaround: use a 2D context and drawImage()
instead. You could also transfer the ImageBitmap
to the main thread and create the bitmap-renderer from the HTMLCanvasElement
directly, but you would lose the ability to have your placeholder updated even when the main thread is locked.
However, I must note that I'm a bit skeptical about the usefulness of that intermediary bitmap-renderer context here. Since you need a final context from which you'll grab the ImageBitmap
, you could directly invoke that context on the placeholder's OffscreenCanvas
and just avoid these to-bitmap -> transfer-from-bitmap steps.