[whatwg] Synchronizing Canvas updates in a worker to DOM changes in the UI thread

(This is a spin-off thread from "Canvas in workers".  Some of this is
written in terms of the WorkerCanvas proposal, but it works fine with the
current CanvasProxy API.  I'm skipping some steps here and going straight
to a proposal, since my main goal at the moment is to detangle this from
the other thread...)

Here's a way to synchronize updates to DOM changes, so scenes rendered in a
worker only appear when the UI thread is ready for them to be.

- Add a flag to the Canvas to enable this.  For now, let's call this
explicitpresent, eg. <canvas explicitpresent>.
- When a script finishes rendering (eg. calls commit()), the buffer is not
automatically displayed.  Instead, it's simply made available to be
displayed.
- Add a method, Canvas.present(), to present the most recently-available
frame.

To describe this in terms of triple-buffering, you have three buffers: a
rendering buffer (aka the backbuffer), a display buffer (aka the
front-buffer), and a ready buffer.  You render (possibly in a worker) to
the rendering buffer.  When you're finished, you call commit(), and the
rendering buffer and the ready buffer are swapped.  Now that a new frame is
ready, you can call canvas.present() to swap the ready buffer and the
display buffer.  Essentially, that's it.

You don't actually need to allocate a third buffer, as long as the user
doesn't start rendering a new frame before present()ing the previous one.
This could be a behind-the-scenes optimization to avoid the extra memory
cost--only allocate a third buffer if actually needed.

It must not be possible for the UI thread to detect whether present() did
anything--if there's no frame in the ready buffer, nothing changes and the
UI thread can't detect this.  Similarly, it must not be possible for the
rendering thread to detect if the ready frame has been presented.  These
rules are to prevent exposing asynchronous behavior.

Example:

<canvas id=canvas explicitpresent>
<script>
var canvas = document.querySelector("#canvas");
var worker = createWorker();
worker.postMessage({
    cmd: "init",
    canvas: canvas.getWorkerCanvas(),
});

worker.onmessage = function(e)
{
    // The worker told us that a frame has been committed.  Present it for
display.
    canvas.present();

    // Tell the worker that it should start rendering the next frame.
    worker.postMessage({cmd: "update"});

    // Do any DOM changes here, to synchronize them with displaying the new
canvas.
    updateUI();
}
</script>

Worker:

onmessage = function(e)
{
    // On initialization only:
    if(e.data.cmd == "init")
        canvas = e.data.canvas;

    // Render our scene.
    renderFrame(canvas);

    // Commit the scene.
    canvas.commit();

    // Tell the main thread that the frame is ready.
    postMessage("present");
}
function renderFrame(workerCanvas) { }

-- 
Glenn Maynard

Received on Sunday, 20 October 2013 15:33:38 UTC