Search code examples
javascripthtmlcanvasweb-workeroffscreen-canvas

Is Bitmaprenderer supposed to work on OffscreenCanvas in a webworker?


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.


Solution

  • 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.