Renderização acelerada no Chrome

O modelo de camadas

Tom Wiltzius
Tom Wiltzius

Introdução

Para a maioria dos desenvolvedores da Web, o modelo fundamental de uma página é 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 vantagens das placas de vídeo: muitas vezes é chamada vagamente de "aceleração de hardware". Em se tratando de uma página da Web normal (não de Canvas2D ou WebGL), o que esse termo significa? Este artigo explica o modelo básico que sustenta a renderização acelerada por hardware de conteúdo da Web no Chrome.

Avisos "grandes e gordos"

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

Além disso, observe que este artigo inteiro aborda uma parte fundamental da arquitetura de renderização do Chrome, que está mudando muito rapidamente. Este artigo tenta abordar apenas coisas que provavelmente não mudarão, mas não há garantias de que tudo ainda será aplicado em seis meses.

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

Por último, estamos conferindo os bastidores do mecanismo de renderização e analisando os recursos que têm 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 dar uma pitada de si: camadas são construções úteis, mas criar muitas delas pode introduzir sobrecarga em toda a pilha de gráficos. Fique atento!

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. Ao renderizar uma página, no entanto, o navegador tem uma série de representações intermediárias que não são expostas diretamente aos desenvolvedores. A mais importante dessas estruturas é uma camada.

No Chrome, existem vários tipos diferentes de camadas: RenderLayers, que são responsáveis pelas subárvores do DOM, e GraphicsLayers, que são responsáveis pelas subárvores de RenderLayers. O último é o mais interessante aqui, porque GraphicsLayers são enviados para a GPU como texturas. Vou dizer "camada" daqui em diante para significar GraphicsLayer.

Um rápido comentário sobre a terminologia da GPU: o que é uma textura? Pense nisso como uma imagem em bitmap que é movida da memória principal (por exemplo, RAM) para a memória de vídeo (ou seja, VRAM, na sua GPU). Quando ele estiver na GPU, é possível fazer o mapeamento dela 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 colocar pedaços de conteúdo da página da Web na GPU. É possível mapear texturas de maneira econômica para diferentes posições e transformações aplicando-as a uma malha retangular realmente simples. É assim que o CSS 3D funciona. Além disso, ele é ótimo para rolagem rápida, mas vamos falar sobre ambos posteriormente.

Vamos analisar alguns exemplos para ilustrar o conceito de camadas.

Uma ferramenta muito útil ao 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 ativá-la. Essas capturas de tela e exemplos foram extraídos do Chrome Canary mais recente, o Chrome 27, no momento da elaboração deste artigo.

Figura 1: 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 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 os blocos, que são subunidades de uma camada que o Chrome usa para fazer upload de partes de uma camada grande de cada vez para a GPU. Elas não são muito 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 na <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 uma camada própria? A heurística do Chrome evoluiu ao longo do tempo e continua evoluindo, mas atualmente há uma das seguintes formas de criação de camada de acionador:

  • Propriedades CSS transformadas em 3D ou perspectiva
  • <video> elementos usando decodificação de vídeo acelerada
  • Elementos <canvas> com um contexto 3D (WebGL) ou 2D acelerado
  • Plug-ins compostos (por exemplo, Flash)
  • Elementos com animação CSS para sua opacidade ou usando uma transformação animada
  • Elementos com filtros CSS acelerados
  • O elemento tem um descendente que tem uma camada combinável, em outras palavras, 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 composta (em outras palavras, ele é renderizado sobre uma camada composta)

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

Também podemos mover 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 antes, as camadas são realmente úteis para se mover pelo 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 o upload para a GPU como uma textura. Se esse conteúdo não for alterado no futuro, ele não precisará ser repintado. Isso é bom: a repintura leva tempo que pode ser gasto em outras coisas, como 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 das Ferramentas para Desenvolvedores: não há operações de pintura enquanto essa camada está girando para frente e para trás.

Captura de tela da linha do tempo das Ferramentas para desenvolvedores durante a animação
Captura de tela da linha do tempo das Ferramentas para Desenvolvedores durante a animação

Inválido! Nova pintura

Porém, se o conteúdo da camada mudar, ela terá que ser pintada de novo.

Figura 4: camadas de repintura

<!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 mais largo. Isso causa um novo layout e uma nova pintura de todo o elemento, que nesse caso é uma camada inteira.

Uma boa maneira de ver o que está sendo pintado é com a ferramenta “show paint rects” nas Ferramentas para Desenvolvedores, também abaixo do título “Renderização” nas configurações do Dev Tools. 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;show paint rects&quot;
Captura de tela da caixa de seleção "show paint rects"

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

Captura de tela da linha do tempo das Ferramentas para desenvolvedores redesenhando uma camada
Captura de tela da repintura de uma camada na linha do tempo das Ferramentas para desenvolvedores

O Chrome nem sempre precisa repintar toda a camada. Ele tenta ser inteligente para repintar 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 responder exaustivamente, porque há muitos casos extremos que podem forçar invalidações. A causa mais comum é sujar o DOM manipulando estilos CSS ou causando um novo layout. Tony Gentilcore tem uma ótima postagem no blog sobre o que causa a reformulação do layout, e Stoyan Stefanov tem um artigo que aborda a pintura em mais detalhes (mas termina com apenas a pintura, não com esse material sofisticado).

A melhor maneira de descobrir se isso está afetando algo em que você está trabalhando é usar as ferramentas Dev Tools Timeline e Show Paint Rects para ver se você está repintando quando não está. Em seguida, tente identificar onde você sujei o DOM logo antes de refazer o layout ou repintar. Se a pintura é inevitável, mas está demorando demais, consulte o artigo de Eberhard Gräther sobre o modo de pintura contínua no Dev Tools (Ferramentas para desenvolvedores).

Em conjunto: DOM a Screen

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

  1. Pegue 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. Combina as várias camadas na imagem final da tela.

Tudo isso precisa acontecer na primeira vez que o Chrome produz um frame de uma página da Web. Mas pode haver alguns atalhos para 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 diferentes propriedades de composição (por exemplo, em diferentes posições, com diferentes opacidades etc.).
  2. Se parte de uma camada for invalidada, será repintada e reenviada. Se o conteúdo permanecer o mesmo, mas os atributos compostos mudarem (por exemplo, se ele for traduzido ou a opacidade mudar), o Chrome poderá deixá-lo na GPU e recomposto para criar um novo frame.

Como agora já deve ficar claro, o modelo de composição baseado em camada tem implicações profundas no desempenho de renderização. A composição é comparativamente barata quando nada precisa ser pintado, portanto, evitar repinturas de camadas é um bom objetivo geral ao tentar depurar o desempenho de renderização. Os desenvolvedores experientes conferem a lista de gatilhos de composição acima e percebem que é possível forçar facilmente a criação de camadas. Mas tome cuidado ao criá-los às vezes, porque eles não são livres. Eles consomem memória na RAM do sistema e na GPU (especialmente limitado em dispositivos móveis) e ter muitos deles pode gerar outra sobrecarga na lógica que controla quais são visíveis. Muitas camadas também podem aumentar o tempo gasto na varredura se as camadas forem grandes e se sobrepuserem bastante, levando ao que às vezes é chamado de "overdraw". Portanto, use seu conhecimento com sabedoria.

Isso é tudo por enquanto. Não perca os próximos artigos sobre as implicações práticas do modelo de camadas.

Outros recursos