2

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.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
Kruptein
  • 25
  • 3

1 Answers1

2

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.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 1
    The skepticism is fair, there is reason to it however. As I mentioned I've done multiple experiments so far and one of the earlier attempts indeed involved just running rAF in each worker and drawing on the transferred canvas directly. The problem with that approach is that they can become out of sync quite easily when one of the workers does not perform fast enough, resulting in some visual oddities between the canvases. The above code was part of an attempt where I do some custom events to dictate when to render. – Kruptein Apr 27 '23 at 07:32