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 há muito tempo sofrem 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.

A dica desynchronized para canvas.getContext() invoca um caminho de código diferente que ignora o mecanismo comum de atualização do DOM. Em vez disso, a dica instrui o sistema a pular o máximo de composição possível e, em alguns casos, o buffer da tela é enviado diretamente ao 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 de Sintel

Se você quiser acessar o código, role a tela para frente. Para conferir o recurso em ação, você precisa de um dispositivo com touchscreen e, de preferência, uma stylus. Os dedos também funcionam. Se você tiver um, teste as amostras 2d ou webgl. Para os outros, 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 1 minuto e 21 segundos do curta Sintel, de Durian, o projeto de filme aberto do Blender. Neste exemplo, o filme é reproduzido em um elemento <video> cujo conteúdo é renderizado simultaneamente para um elemento <canvas>. Muitos dispositivos podem fazer isso sem ruptura, embora dispositivos com renderização do buffer frontal, como o ChromeOS, por exemplo, possam ter rupturas. (O filme é ótimo, mas comovente. Fiquei inútil por uma hora depois que o vi. Considere-se alertado.

Como usar a dica

Usar a baixa latência é mais importante do que adicionar desynchronized a canvas.getContext(). Falarei sobre um problema de cada vez.

Criar a tela

Em outra API, falarei primeiro sobre a detecção de recursos. Para a dica desynchronized, você precisa criar a tela primeiro. Chame canvas.getContext() e transmita a nova dica desynchronized com um valor 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-o.

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

Evitando oscilação

Há dois casos em que você pode causar oscilações se a programação não for feita 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 oscile. Para evitar isso, defina preserveDrawingBuffer como true.

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

A oscilação também pode ocorrer ao limpar o contexto da tela no seu próprio código de desenho. Se precisar, desenhe em um framebuffer fora da tela e copie na tela.

Canais Alfa

Um elemento de tela translúcido, em que alpha é definido como verdadeiro, ainda pode ser dessincronizado, mas não pode ter outros elementos DOM acima dele.

Só pode haver uma

Não é possível mudar os atributos de contexto após a primeira chamada para canvas.getContext(). Isso sempre foi verdade, mas repeti-lo pode poupar alguma frustração se você não estiver ciente ou tiver esquecido .

Por exemplo, digamos que eu receba um contexto e especifique "Alfa" como falso. Em seguida, em algum lugar mais tarde no meu código eu chamo canvas.getContext() pela segunda vez com o Alfa 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. Alfa ainda é falso, e um contexto com Alfa igual a verdadeiro nunca é criado.

Tipos de tela compatíveis

O primeiro parâmetro transmitido para getContext() é o contextType. Se você já conhece getContext(), com certeza está se perguntando se há suporte para algo diferente de tipos de contexto "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 você quiser ver mais, confira as amostras. Além do exemplo de vídeo já descrito, há exemplos que mostram os contextos '2d' e 'webgl'.