Renderização acelerada no Chrome

O modelo de camada

Tom Wiltzius
Tom Wiltzius

Introdução

Para a maioria dos desenvolvedores Web, o modelo fundamental de uma página da Web é o DOM. A renderização é o processo geralmente obscuro de transformar essa representação de uma página em uma imagem na tela. Nos últimos anos, os navegadores modernos mudaram a forma como a renderização funciona para aproveitar as placas de vídeo. Isso é vagamente chamado de "aceleração de hardware". Ao falar sobre uma página da Web normal (ou seja, não Canvas2D ou WebGL), o que esse termo realmente significa? Este artigo explica o modelo básico que sustenta a renderização acelerada por hardware de conteúdo da Web no Chrome.

Advertências grandes e gordas

Estamos falando sobre o WebKit e, mais especificamente, sobre a porta do Chromium para o WebKit. Este artigo aborda os detalhes de implementação do Chrome, e não os recursos da plataforma Web. A plataforma da Web e os padrões não codificam esse nível de detalhes de implementação, portanto, não há garantias de que nada neste artigo será aplicado a outros navegadores, mas o conhecimento dos aspectos internos pode ser útil para depuração avançada e ajuste de desempenho.

Além disso, este artigo aborda uma parte essencial da arquitetura de renderização do Chrome, que está mudando rapidamente. Este artigo tenta abordar apenas questões que provavelmente não serão alteradas, mas não há garantia de que elas ainda serão aplicadas dentro de seis meses.

É importante entender que o Chrome já tem dois caminhos de renderização diferentes há algum tempo: o caminho acelerado por hardware e o caminho de software mais antigo. No momento, todas as páginas seguem o caminho de aceleração de hardware no Windows, ChromeOS e Chrome para Android. No Mac e no Linux, somente páginas que precisam ser compostas para parte de seu conteúdo seguem o caminho acelerado (veja abaixo mais informações sobre o que exigiria composição), mas em breve todas as páginas também seguirão esse caminho acelerado.

Por fim, vamos examinar os bastidores do mecanismo de renderização e analisar os recursos dele que têm um grande impacto no desempenho. Ao tentar melhorar o desempenho do seu próprio site, pode ser útil entender o modelo de camadas, mas também é fácil se concretizar: as camadas são construções úteis, mas a criação de muitas delas pode gerar overhead em toda a pilha de gráficos. Considere que você está preparado!

Do DOM para a tela

Introdução às camadas

Depois que uma página é carregada e analisada, ela é representada no navegador como uma estrutura com que muitos desenvolvedores da Web estão familiarizados: DOM. No entanto, ao renderizar uma página, o navegador tem uma série de representações intermediárias que não são diretamente expostas aos desenvolvedores. A mais importante dessas estruturas é uma camada.

No Chrome, há vários tipos diferentes de camadas: RenderLayers, responsáveis pelas subárvores do DOM, e GraphicsLayers, que são responsáveis pelas subárvores de RenderLayers. A última opção é mais interessante aqui, porque as camadas gráficas são enviadas à GPU como texturas. Agora, vou dizer “layer” (camada) que se refere a GraphicsLayer.

Sobre a terminologia da GPU: o que é uma textura? Pense nela como uma imagem de bitmap movida da memória principal (ou seja, RAM) para a memória de vídeo (ou seja, VRAM, na GPU). Quando estiver na GPU, é possível mapeá-lo para uma geometria de malha. Em videogames ou programas CAD, essa técnica é usada para dar “pele” aos modelos 3D esqueléticos. O Chrome usa texturas para transferir pedaços de conteúdo da página da Web para a GPU. As texturas podem ser mapeadas para diferentes posições e transformações de maneira econômica, aplicando-as a uma malha retangular muito simples. É assim que o CSS 3D funciona, e ele também é ótimo para rolagem rápida, mas vamos falar mais sobre ambos posteriormente.

Vamos conferir alguns exemplos para ilustrar o conceito de camada.

Uma ferramenta muito útil para estudar camadas no Chrome é a sinalização "mostrar bordas de camadas compostas" nas configurações (ou seja, um pequeno ícone de engrenagem) nas Ferramentas para Desenvolvedores, sob o título “renderização”. Ele simplesmente destaca onde as camadas estão na tela. Vamos ativar esse recurso. Essas capturas de tela e exemplos foram extraídos do Chrome Canary mais recente, Chrome 27 quando este artigo foi escrito.

Figura 1: uma página de camada única

<!doctype html>
<html>
<body>
  <div>I am a strange root.</div>
</body>
</html>
Captura de tela das bordas de renderização da camada composta ao redor da camada de base da página
Captura de tela das bordas de renderização da camada composta ao redor da camada de base da página

Esta página tem apenas uma camada. A grade azul representa blocos, que você pode considerar como subunidades de uma camada que o Chrome usa para fazer upload de partes de uma camada grande de uma vez para a GPU. Eles não são importantes aqui.

Figura 2: um elemento na própria camada

<!doctype html>
<html>
<body>
  <div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
    I am a strange root.
  </div>
</body>
</html>
Captura de tela das bordas de renderização da camada rotacionada
Captura de tela das bordas de renderização da camada rotacionada

Ao colocar uma propriedade CSS 3D no <div> que a gira, podemos ver como ele fica quando um elemento recebe sua própria camada. Observe a borda laranja, que delineia uma camada nessa visualização.

Critérios de criação de camadas

O que mais tem a própria camada? A heurística do Chrome aqui evoluiu ao longo do tempo e continua a evoluir, mas atualmente qualquer uma das seguintes criação de camadas de gatilho:

  • Propriedades CSS de transformação em 3D ou perspectiva
  • <video> elementos usando decodificação de vídeo acelerada
  • Elementos <canvas> com um contexto 3D (WebGL) ou contexto 2D acelerado
  • Plug-ins compostos (por exemplo, Flash)
  • Elementos com animação CSS para opacidade ou usando uma transformação animada.
  • Elementos com filtros CSS acelerados
  • O elemento tem um descendente que tem uma camada combinável (ou seja, se o elemento tem um elemento filho que está na própria camada)
  • O elemento tem um irmão com um Z-index menor que tem uma camada combinável (em outras palavras, ele é renderizado sobre uma camada composta)

Implicações práticas: animação

Também podemos mover as camadas, o que as torna muito úteis para animações.

Figura 3: camadas animadas

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div>I am a strange root.</div>
</body>
</html>

Como mencionado anteriormente, as camadas são muito úteis para movimentar o conteúdo estático da Web. No caso básico, o Chrome pinta o conteúdo de uma camada em um bitmap de software antes de fazer upload dele para a GPU como uma textura. Se esse conteúdo não mudar no futuro, não precisará ser reformulado. Essa é uma coisa boa: a repintura leva um tempo que pode ser gasto em outras coisas, como a execução de JavaScript, e se a pintura for longa, isso causará problemas ou atrasos nas animações.

Veja, por exemplo, esta visualização da linha do tempo do Dev Tools: não há operações de pintura enquanto a camada está girando para frente e para trás.

Captura de tela da linha do tempo do Dev Tools durante a animação
Captura de tela da linha do tempo do Dev Tools durante a animação
.

Inválido! Repintura

No entanto, se o conteúdo da camada mudar, ela precisará ser repintada.

Figura 4: repintura de camadas

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div id="foo">I am a strange root.</div>
  <input id="paint" type="button" value="repaint">
  <script>
    var w = 200;
    document.getElementById('paint').onclick = function() {
      document.getElementById('foo').style.width = (w++) + 'px';
    }
  </script>
</body>
</html>

Cada vez que o elemento de entrada é clicado, o elemento giratório fica 1 px de largura. Isso causa um novo layout e nova pintura de todo o elemento, que neste caso é uma camada inteira.

Uma boa maneira de ver o que está sendo pintado é com a ferramenta “show paint rects” das Ferramentas para Desenvolvedores, também sob o título “Renderização” das configurações das Ferramentas para Desenvolvedores. Depois de ligá-lo, observe que o elemento animado e o botão piscam em vermelho quando o botão é clicado.

Captura de tela da caixa de seleção &quot;Mostrar retângulos de pintura&quot;
Captura de tela da caixa de seleção "Mostrar retângulos de pintura"

Os eventos de pintura também aparecem na linha do tempo das Ferramentas para Desenvolvedores. Leitores atentos podem notar que há dois eventos de pintura: um para a camada e outro para o próprio botão, que é repintado quando muda de/para seu estado pressionado.

Captura de tela da reformulação de uma camada na Linha do tempo das Ferramentas para desenvolvedores
Captura de tela da reformulação de uma camada na linha do tempo das Ferramentas para desenvolvedores

Observe que o Chrome nem sempre precisa repintar toda a camada. Ele tenta reformular apenas a parte do DOM que foi invalidada. Nesse caso, o elemento DOM que modificamos é o tamanho de toda a camada. Mas em muitos outros casos, haverá muitos elementos DOM em uma camada.

Uma próxima pergunta óbvia é o que causa uma invalidação e força uma nova pintura. É difícil de responder a essa pergunta porque há muitos casos extremos que podem forçar invalidações. A causa mais comum é a alteração do DOM com a manipulação de estilos CSS ou a alteração do layout. Tony Gentilcore tem uma ótima postagem do blog sobre o que causa o novo layout, e Stoyan Stefanov tem um artigo que aborda a pintura em mais detalhes (mas termina apenas com a pintura, e não com essa composição sofisticada).

A melhor maneira de descobrir se isso afeta algo em que você está trabalhando é usar as ferramentas Linha do tempo das Ferramentas para desenvolvedores e Mostrar retângulos de pintura para ver se você está redesenhando quando queria. Em seguida, tente identificar onde você sujou o DOM logo antes de alterar o layout/repintar. Se a pintura for inevitável, mas estiver demorando demais, confira o artigo de Eberhard Gräther sobre o modo de pintura contínua nas ferramentas para desenvolvedores.

Como tudo funciona em conjunto: DOM para tela

Então, como o Chrome transforma o DOM em uma imagem de tela? Conceitualmente, ela:

  1. Pega o DOM e o divide em camadas
  2. Pinta cada uma dessas camadas de forma independente em bitmaps de software.
  3. faz upload deles para a GPU como texturas;
  4. Compõe as várias camadas na imagem final da tela.

Isso precisa acontecer na primeira vez que o Chrome produz um frame de uma página da Web. No entanto, pode ser necessário usar alguns atalhos para os frames futuros:

  1. Se certas propriedades CSS mudarem, não será necessário repintar nada. O Chrome pode simplesmente recompor as camadas existentes que já estão na GPU como texturas, mas com propriedades de composição diferentes (por exemplo, em posições diferentes, com opacidades diferentes etc.).
  2. Se parte de uma camada for invalidada, ela será repintada e reenviada. Se o conteúdo permanece o mesmo, mas os atributos compostos mudam (por exemplo, é traduzido ou sua opacidade muda), o Chrome pode deixá-lo na GPU e recomposto para criar um novo frame.

Como você já deve saber, o modelo de composição baseado em camadas tem implicações profundas no desempenho da renderização. A composição é comparativamente barata quando nada precisa ser pintado, de modo que evitar repinturas de camadas é um bom objetivo geral ao tentar depurar o desempenho da renderização. Desenvolvedores experientes vão analisar a lista de gatilhos de composição acima e perceber que é possível forçar facilmente a criação de camadas. Mas tome cuidado ao criá-los cegamente, já que eles não são sem custo financeiro: eles ocupam memória na RAM do sistema e na GPU (particularmente limitado em dispositivos móveis) e ter muitos deles pode introduzir outra sobrecarga na lógica que monitora quais estão visíveis. Muitas camadas também podem aumentar o tempo gasto na rasterização se forem grandes e se sobrepuserem muito a partir do que não anteriormente, levando ao que às vezes é chamado de “overdraw”. Portanto, use seu conhecimento com sabedoria.

Ficamos por aqui. Fique de olho em mais alguns artigos sobre as implicações práticas do modelo de camadas.

Outros recursos