Modelo de camadas
Introdução
Para a maioria dos desenvolvedores da Web, o modelo fundamental de uma página da Web é o DOM. A renderização é o processo, muitas vezes obscuro, de transformar essa representação de uma página em uma imagem na tela. Os navegadores modernos mudaram a forma como a renderização funciona nos últimos anos para aproveitar as placas gráficas. Isso é frequentemente chamado de "aceleração de hardware". Quando se trata de 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 fundamenta a renderização acelerada por hardware do conteúdo da Web no Chrome.
Big, Fatty Caveats
Estamos falando sobre o WebKit e, mais especificamente, sobre a porta Chromium do WebKit. Este artigo aborda detalhes de implementação do Chrome, não 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 o conteúdo deste artigo se aplique a outros navegadores, mas o conhecimento interno pode ser útil para depuração avançada e ajuste de desempenho.
Além disso, este artigo inteiro discute uma parte importante da arquitetura de renderização do Chrome que está mudando muito rápido. Este artigo tenta abordar apenas as informações que provavelmente não vão mudar, mas não garante que tudo ainda vai ser válido daqui a seis meses.
É importante entender que o Chrome tem dois caminhos de renderização diferentes há algum tempo: o caminho com aceleração de hardware e o caminho de software mais antigo. No momento em que este artigo foi escrito, todas as páginas seguiam o caminho de aceleração de hardware no Windows, ChromeOS e Chrome para Android. No Mac e no Linux, apenas as páginas que precisam de composição para parte do conteúdo seguem o caminho acelerado (consulte abaixo mais informações sobre o que exigiria composição), mas em breve todas as páginas também vão seguir esse caminho.
Por fim, vamos dar uma olhada no mecanismo de renderização e analisar os recursos que têm um grande impacto no desempenho. Ao tentar melhorar o desempenho do seu site, pode ser útil entender o modelo de camadas, mas também é fácil se atrapalhar: as camadas são construções úteis, mas criar muitas delas pode gerar sobrecarga em toda a pilha gráfica. Avisado você está!
Do DOM para a tela
Introdução às camadas
Depois que uma página é carregada e analisada, ela é representada no navegador como uma estrutura que muitos desenvolvedores da Web conhecem: 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, que são responsáveis por subárvores do DOM, e GraphicsLayers, que são responsáveis por subárvores de RenderLayers. O último é mais interessante para nós aqui, porque as GraphicsLayers são enviadas para a GPU como texturas. A partir de agora, vou usar "layer" para me referir a GraphicsLayer.
Uma observação rápida sobre a terminologia de GPU: o que é uma textura? Pense nisso como uma imagem bitmap que é movida da memória principal (RAM) para a memória de vídeo (VRAM, na GPU). Depois de estar na GPU, é possível mapear para uma geometria de malha. Em videogames ou programas de CAD, essa técnica é usada para dar "pele" a modelos esqueléticos 3D. O Chrome usa texturas para transferir partes do conteúdo da página da Web para a GPU. As texturas podem ser mapeadas de forma barata para diferentes posições e transformações, sendo aplicadas a uma malha retangular muito simples. É assim que o CSS 3D funciona, e ele também é ótimo para rolagem rápida, mas falaremos mais sobre isso mais tarde.
Vamos conferir alguns exemplos para ilustrar o conceito de camada.
Uma ferramenta muito útil ao estudar camadas no Chrome é a flag "show composited layer borders" nas configurações (ou seja, o pequeno ícone de engrenagem) nas Ferramentas do desenvolvedor, no título "rendering". Ele destaca de forma simples onde as camadas estão na tela. Vamos ligar. Essas capturas de tela e exemplos foram tirados da versão mais recente do Chrome Canary, o Chrome 27 no momento em que 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>

Esta página tem apenas uma camada. A grade azul representa blocos, que podem ser considerados subunidades de uma camada que o Chrome usa para fazer upload de partes de uma camada grande por 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>

Ao colocar uma propriedade CSS 3D no <div>
que o faz girar, podemos conferir como fica quando um elemento tem a 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? As heurísticas do Chrome evoluíram ao longo do tempo e continuam evoluindo, mas atualmente qualquer uma das seguintes ações aciona a criação de camadas:
- Propriedades CSS de transformação 3D ou perspectiva
- Elementos
<video>
usando a 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 a opacidade ou usando uma transformação animada
- Elementos com filtros CSS acelerados
- O elemento tem um descendente que tem uma camada de composição (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 mais baixo que tem uma camada de composição (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ção.
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 navegar 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 mudar no futuro, não será necessário repintá-lo. Isso é bom: a repintura leva tempo que pode ser gasto em outras coisas, como a execução do JavaScript. Se a pintura for longa, ela causa falhas ou atrasos nas animações.
Veja, por exemplo, esta visualização da linha do tempo das ferramentas do desenvolvedor: não há operações de pintura enquanto essa camada gira para frente e para trás.

Inválido. Repintura
Mas, se o conteúdo da camada mudar, ela precisará ser pintada novamente.
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>
Sempre que o elemento de entrada é clicado, o elemento giratório fica 1 pixel mais largo. Isso causa o redimensionamento e a repintura de todo o elemento, que, nesse caso, é uma camada inteira.
Uma boa maneira de saber o que está sendo pintado é usar a ferramenta "Mostrar retângulos de pintura" nas DevTools, também na seção "Renderização" das configurações das DevTools. Depois de ativá-lo, observe que o elemento animado e o botão piscam em vermelho quando o botão é clicado.

Os eventos de pintura também aparecem na linha do tempo das ferramentas do desenvolvedor. Os leitores atentos podem notar que há dois eventos de pintura: um para a camada e outro para o botão, que é pintado novamente quando muda para/de seu estado pressionado.

O Chrome não precisa sempre repintar a camada inteira. Ele tenta ser inteligente e 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.
A próxima pergunta óbvia é o que causa uma invalidação e força uma nova pintura. Isso é difícil de responder de forma exaustiva porque há muitos casos extremos que podem forçar invalidações. A causa mais comum é sujar o DOM manipulando estilos CSS ou causando relayout. Tony Gentilcore tem uma ótima postagem de blog sobre o que causa o redimensionamento, e Stoyan Stefanov tem um artigo que aborda a pintura com mais detalhes (mas ele termina apenas com a pintura, não com a composição).
A melhor maneira de descobrir se isso está afetando algo em que você está trabalhando é usar as ferramentas Timeline e Show Paint Rects das ferramentas do desenvolvedor para saber se você está repintando quando não deveria e, em seguida, identificar onde você sujou o DOM logo antes do redimensionamento/repintura. Se a pintura for inevitável, mas parecer que está demorando muito, confira o artigo de Eberhard Gräther sobre o modo de pintura contínua nas Ferramentas do desenvolvedor.
Como fazer isso: DOM para tela
Como o Chrome transforma o DOM em uma imagem de tela? Conceitualmente, ele:
- Recebe o DOM e o divide em camadas
- Pinta cada uma dessas camadas de forma independente em bitmaps de software.
- Faz o upload delas para a GPU como texturas
- Compõe 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 ele pode usar alguns atalhos para frames futuros:
- Se algumas propriedades CSS mudarem, não será necessário repintar nada. O Chrome pode recomportar as camadas 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.).
- Se parte de uma camada for invalidada, ela será repintada e reenviada. Se o conteúdo permanecer o mesmo, mas os atributos compostos mudarem (por exemplo, se ele for traduzido ou se a opacidade mudar), o Chrome poderá deixá-lo na GPU e recompor para criar um novo frame.
Como já deve estar claro, o modelo de composição baseado em camadas tem implicações profundas para a performance de renderização. A composição é relativamente barata quando nada precisa ser pintado. Portanto, evitar repintagens de camadas é uma boa meta geral ao tentar depurar o desempenho da renderização. Os desenvolvedores experientes vão analisar a lista de acionadores de composição acima e perceber que é possível forçar a criação de camadas. Mas não crie-as cegamente, porque elas não são sem custo financeiro: elas ocupam memória na RAM do sistema e na GPU (particularmente limitada em dispositivos móveis) e ter muitas delas pode gerar outra sobrecarga na lógica que mantém o controle das que estão visíveis. Muitas camadas também podem aumentar o tempo gasto na rasterização se forem grandes e se sobreporem muito onde não o faziam antes, o que às vezes é chamado de "overdraw". 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.