Renderização de baixa latência com a dica dessincronizada

Joe Medley
Joe Medley

Diferenças na renderização da stylus

Os aplicativos de desenho baseados em stylus criados para a Web sempre sofreram com problemas de latência, porque uma página da Web precisa sincronizar atualizações gráficas com o DOM. Em qualquer aplicativo de desenho, latências maiores que 50 milissegundos podem interferir na coordenação mão-olho do usuário, dificultando o uso dos aplicativos.

A sugestão desynchronized para canvas.getContext() invoca um caminho de código diferente que ignora o mecanismo de atualização DOM usual. Em vez disso, a dica informa ao sistema subjacente para pular o máximo possível de composição. Em alguns casos, o buffer subjacente da tela é enviado diretamente para o controlador de exibição da tela. Isso elimina a latência que seria causada pelo uso da fila do compositor do renderizador.

O que você achou?

Renderização simultânea do Sintel

Se você quiser acessar o código, role a tela para baixo. Para conferir em ação, você precisa de um dispositivo com tela touchscreen e, de preferência, uma stylus. Os dedos também funcionam. Se você tiver um, tente as amostras 2d ou webgl. Para os demais, confira esta demonstração de Miguel Casas, um dos engenheiros que implementaram esse recurso. Abra a demonstração, pressione "Play" e mova o controle deslizante para frente e para trás de forma aleatória e rápida.

Este exemplo usa um clipe de um minuto e 21 segundos do curta-metragem Sintel de Durian, o projeto de filme aberto do Blender. Neste exemplo, o filme é reproduzido em um elemento <video> cujo conteúdo é renderizado simultaneamente em um elemento <canvas>. Muitos dispositivos podem fazer isso sem rasgos, embora dispositivos com renderização de buffer frontal, como ChromeOS, por exemplo, possam ter rasgos. O filme é ótimo, mas de partir o coração. Fiquei inútil por uma hora depois de ver isso. Considere isso um aviso.)

Como usar a dica

Há mais coisas para usar a baixa latência do que adicionar desynchronized a canvas.getContext(). Vou analisar os problemas um por um.

Criar a tela

Em outra API, eu discutiria a detecção de recursos primeiro. Para a dica desynchronized, você precisa criar a tela primeiro. Chame canvas.getContext() e transmita a nova sugestão desynchronized com um valor de true.

const canvas = document.querySelector('myCanvas');
const ctx = canvas.getContext('2d', {
  desynchronized: true,
  // Other options. See below.
});

Detecção de recursos

Em seguida, chame getContextAttributes(). Se o objeto de atributos retornado tiver uma propriedade desynchronized, teste-a.

if (ctx.getContextAttributes().desynchronized) {
  console.log('Low latency canvas supported. Yay!');
} else {
  console.log('Low latency canvas not supported. Boo!');
}

Como evitar oscilações

Há duas instâncias em que você pode causar oscilação se não programar corretamente.

Alguns navegadores, incluindo o Chrome, limpam as telas WebGL entre os frames. É possível que o controlador de exibição leia o buffer enquanto ele está vazio, fazendo com que a imagem desenhada pisque. Para evitar isso, defina preserveDrawingBuffer como true.

const canvas = document.querySelector('myCanvas');
const ctx = canvas.getContext('webgl', {
  desynchronized: true,
  preserveDrawingBuffer: true
});

O flicker também pode ocorrer quando você limpa o contexto da tela no seu próprio código de exibição. Se for necessário limpar, desenhe em um framebuffer fora da tela e copie para a tela.

Canais Alfa

Um elemento de tela translúcida, em que o alfa é definido como "true", ainda pode ser desincronizado, mas não pode ter outros elementos DOM acima dele.

Só pode haver um

Não é possível mudar os atributos de contexto após a primeira chamada para canvas.getContext(). Isso sempre foi verdade, mas repetir isso pode evitar frustração se você não souber ou se tiver esquecido .

Por exemplo, digamos que eu receba um contexto e especifique alpha como falso. Em algum momento mais tarde no código, chamo canvas.getContext() uma segunda vez com alpha definido como verdadeiro, conforme mostrado abaixo.

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,
});

Não é óbvio que ctx1 e ctx2 são o mesmo objeto. O Alfa ainda é falso, e um contexto com Alfa igual a "true" nunca é criado.

Tipos de tela compatíveis

O primeiro parâmetro transmitido para getContext() é o contextType. Se você já conhece getContext(), provavelmente está se perguntando se há suporte para outros tipos de contexto além do 2D. A tabela abaixo mostra os tipos de contexto compatíveis com desynchronized.

contextType Objeto do tipo de contexto

'2d'

CanvasRenderingContext2D

'webgl'

WebGLRenderingContext

'webgl2'

WebGL2RenderingContext

Conclusão

Se quiser saber mais sobre isso, confira as amostras. Além do exemplo de vídeo já descrito, há exemplos que mostram os contextos 2D e WebGL.