Low-latency rendering with the desynchronized hint
Stylus-based drawing applications built for the web have long suffered from latency issues because a web page has to synchronize graphics updates with the DOM. In any drawing application, latencies longer than 50 milliseconds can interfere with a user's hand-eye coordination, making applications difficult to use.
The desynchronized
hint for canvas.getContext()
invokes a different code
path that bypasses the usual DOM update
mechanism.
Instead the hint tells the underlying system to skip as much compositing as it
is able and in some cases, the canvas's underlying buffer is sent directly to
the screen's display controller. This eliminates the latency that would be
caused by using the renderer compositor queue.
How good is it?
If you want to get to the code, scroll ahead. To see it in action, you need a device with a touch screen, and preferably a stylus. (Fingers work too.) If you have one, try the 2d or webgl samples. For the rest of you check out this demo by Miguel Casas, one of the engineers who implemented this feature. Open the demo, press play, then move the slider back and forth randomly and quickly.
This example uses a one-minute, twenty-one second clip from the short film
Sintel by Durian, the Blender open movie
project. In this example, the movie is played in a <video>
element whose
contents are simultaneously rendered to a <canvas>
element. Many devices can
do this without tearing, though devices with front buffer rendering such as
Chrome OS for example may have tearing. (The movie is great, but heartbreaking.
I was useless for an hour after I saw it. Consider yourself warned.)
Using the hint
There's more to using low latency than adding desynchronized
to
canvas.getContext()
. I'll go over the issues one at a time.
Create the canvas
On another API I'd discuss feature detection first. For the desynchronized
hint you must create the canvas first. Call canvas.getContext()
and pass it
the new desynchronized
hint with a value of true
.
const canvas = document.querySelector('myCanvas');
const ctx = canvas.getContext('2d', {
desynchronized: true,
// Other options. See below.
});
Feature detection
Next, call getContextAttributes()
. If the returned attributes object has a
desynchronized
property, then test it.
if (ctx.getContextAttributes().desynchronized) {
console.log('Low latency canvas supported. Yay!');
} else {
console.log('Low latency canvas not supported. Boo!');
}
Avoiding flicker
There are two instances where you can cause flicker if you don't code correctly.
Some browsers including Chrome clear WebGL canvases between frames. It's
possible for the display controller to read the buffer while it's empty causing
the image being drawn to flicker. To avoid this is to set
preserveDrawingBuffer
to true
.
const canvas = document.querySelector('myCanvas'); const ctx = canvas.getContext('webgl', { desynchronized: true, preserveDrawingBuffer: true });
Flicker can also occur when you clear the screen context in your own drawing code. If you must clear, draw to an offscreen framebuffer then copy that to the screen.
Alpha channels
A translucent canvas element, one where alpha is set to true, can still be desynchronized, but it must not have any other DOM elements above it.
There can be only one
You cannot change the context attributes after the first call to
canvas.getContext()
. This has always been true, but repeating it might save
you some frustration if you're unaware or have forgotten .
For example, let's say that I get a context and specify alpha as false, then
somewhere later in my code I call canvas.getContext()
a second time with alpha
set to true as shown below.
const canvas = document.querySelector('myCanvas');
const ctx1 = canvas.getContext('2d', {
alpha: false,
desynchronized: true,
});
//Some time later, in another corner of code.
const ctx2 = canvas.getContext('2d', {
alpha: true,
desynchronized: true,
});
It's not obvious that ctx1
and ctx2
are the same object. Alpha is still false and a
context with alpha equal to true is never created.
Supported canvas types
The first parameter passed to getContext()
is the contextType
. If you're
already familiar with getContext()
you're no doubt wondering if anything
other than '2d' context types are supported. The table below shows the context
types that support desynchronized
.
contextType | Context type object |
---|---|
|
|
|
|
|
|
Conclusion
If you want to see more of this, check out the samples. In addition to the video example already described there are examples showing both '2d' and 'webgl' contexts.