1

I want to be able to send my canvas offscreen to a webworker more than once.

Here's an example code:

render() {
  const worker = new Worker("some url");
  const offscreen = this.canvasRef.current.transferControlToOffscreen();
  this.worker.postMessage({
    offscreen
  }, [offscreen]);

  return (
    <canvas ref={this.canvasRef} height="800" width="1000" />
  );
}

The idea is to be able to kill the web worker if user decides to cancel the drawing. Put when I repost the message to a new web worker, I get the following error:

DataCloneError: Failed to execute 'postMessage' on 'Worker':
An OffscreenCanvas could not be cloned because it was detached.
Kaiido
  • 123,334
  • 13
  • 219
  • 285
Will
  • 1,718
  • 3
  • 15
  • 23

1 Answers1

2

You could send it more than once, but for this, you'd need to transfer it back from the worker, which kind of defeats your purpose I guess since the worker would need to be free to be able to handle that request, and if it is, there is no need to "kill" it.

So instead, you may prefer to create a standalone OffscreenCanvas, using the eponymous constructor that will stay in the Worker and to draw it on a BitmapRenderingContext:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('bitmaprenderer');

const stop_btn = document.getElementById('stop_btn');

document.getElementById('start_btn').onclick = e => {
  console.log( 'starting a new drawing' );
  const worker = new Worker(getWorkerURL());
  worker.onmessage = e => {
    console.log('drawing complete');
    // pass it to the visible canvas
    ctx.transferFromImageBitmap(e.data);
    start_btn.disabled = false;
    stop_btn.disabled = true;
  };
  stop_btn.onclick = e => {
    console.log('drawing canceled');
    worker.terminate();
    start_btn.disabled = false;
    stop_btn.disabled = true;
  };
  start_btn.disabled = true;
  stop_btn.disabled = false;
};

function getWorkerURL() {
  const el = document.getElementById('worker_script');
  const blob = new Blob([el.textContent]);
  return URL.createObjectURL(blob);
}
<button id="start_btn">start</button>
<button id="stop_btn">stop</button><br>

<canvas id="canvas" width="500" height="500"></canvas>
<script id="worker_script" type="ws">
  const canvas = new OffscreenCanvas(500, 500);
  const gl = canvas.getContext('webgl');
  gl.viewport(0, 0,
      gl.drawingBufferWidth, gl.drawingBufferHeight);
  gl.enable(gl.SCISSOR_TEST);
  // make some slow noise (we're in a Worker)
  for(let y=0; y<gl.drawingBufferHeight; y++) {
    for(let x=0; x<gl.drawingBufferWidth; x++) {
      gl.scissor(x, y, 1, 1);
      gl.clearColor(Math.random(), Math.random(), Math.random(), 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);
    }
  }
  // make it last a bit longer than really needed
  const start = Date.now();
  while ( Date.now() - start < 5000 ) { }

  const img = canvas.transferToImageBitmap();
  postMessage(img, [img]);

</script>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • this approach works, except for two things: 1) user does not get to see the image being drawn, I'm OK with that. 2) it takes a long time for the canvas to build the bitmap (e.g, this command "transferToImageBitmap"). I was under the impression as soon as I'm done with drawing pixels on the canvas, the canvas is ready for visualization. I'm seeing major lag time between finish context drawing and actual render in the browser. – Will Jan 06 '20 at 07:16
  • "user does not get to see the image being drawn" What is this supposed to mean? For the "long time" thing, did you remove the `while ( Date.now() - start < 5000 ) { }` line? – Kaiido Jan 06 '20 at 07:21
  • 1. With my original code, as the canvas is being drawn, the browser actually is updated immediately. But I'm OK with seeing the image updated at the end only. As for the long delay, I ran my code, not sure. I notice when I draw a lot of lines to the canvas, the transferToImageBitmap command takes forever to return. – Will Jan 06 '20 at 07:26
  • What code? With the code you gave, nothing is drawn ever. – Kaiido Jan 06 '20 at 07:28
  • That's only the render code. The idea is when posting transferControlToOffscreen to the worker, it draws directly to the canvas in the main browser. – Will Jan 06 '20 at 07:42
  • Interestinly, I use your approach and draw my image to a another OffscreenCanvas in the web worker. Every 1000 lines, I draw that canvas to my main canvas. The performance improved significantly times. For some reason, the buffer in the 2d context is kept in memory and when I call the draw canvas at the end, all that stuff in memory get flush/processed at one time. But if I flush it every 1000 lines of drawing, no more delay at the end. – Will Jan 06 '20 at 08:59
  • Once again, to help you on this issue, you'll need to provide more details, starting with showing your code. This answer is answering the current question and will only do that. I already told you on your [other question](https://stackoverflow.com/questions/59596826/canvas-drawing-is-slow). – Kaiido Jan 06 '20 at 09:04