O modelo de camada
Introdução
Para a maioria dos desenvolvedores 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. Nos últimos anos, os navegadores mais recentes mudaram a forma como a renderização funciona para aproveitar as placas gráficas: isso costuma ser chamado de "aceleração de hardware". Quando falamos 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 sustenta a renderização acelerada por hardware de conteúdo da Web no Chrome.
Ressalvas importantes
Estamos falando do WebKit aqui e, mais especificamente, da porta do Chromium do WebKit. Este artigo aborda detalhes de implementação do Chrome, não recursos da plataforma da Web. A plataforma e os padrões da Web não codificam esse nível de detalhe de implementação. Portanto, não há garantias de que as informações deste artigo se apliquem a outros navegadores. No entanto, o conhecimento dos componentes internos pode ser útil para depuração avançada e ajuste de desempenho.
Além disso, observe que todo este artigo discute uma parte central da arquitetura de renderização do Chrome que está mudando muito rapidamente. Este artigo tenta abordar apenas o que provavelmente não vai mudar, mas não há garantias de que tudo ainda será válido em seis meses.
É importante entender que o Chrome tem dois caminhos de renderização diferentes há algum tempo: o caminho acelerado por 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, no ChromeOS e no Google 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 (veja abaixo mais informações sobre o que exige composição), mas em breve todas as páginas também vão seguir o caminho acelerado.
Por fim, vamos analisar o mecanismo de renderização e os recursos dele 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 cometer erros: as camadas são construções úteis, mas criar muitas delas pode gerar sobrecarga em toda a pilha de gráficos. Considere-se avisado!
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 Web conhecem: o DOM. No entanto, ao renderizar uma página, 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, há vários tipos 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 é o mais interessante para nós aqui, porque as GraphicsLayers são o que é enviado para a GPU como texturas. A partir de agora, vou usar apenas "camada" para me referir a GraphicsLayer.
Uma breve observação sobre a terminologia da GPU: o que é uma textura? Pense nela como uma imagem de bitmap que foi movida da memória principal (ou seja, RAM) para a memória de vídeo (ou seja, VRAM, na GPU). Depois que ela estiver na GPU, você poderá mapeá-la para uma geometria de malha. Em videogames ou programas CAD, essa técnica é usada para dar "pele" a modelos 3D esqueléticos. O Chrome usa texturas para colocar partes do conteúdo da página da Web na GPU. As texturas podem ser mapeadas de forma barata para diferentes posições e transformações, aplicando-as a uma malha retangular realmente simples. É assim que o CSS 3D funciona, e ele também é ótimo para rolagem rápida. Vamos falar mais sobre isso depois.
Vamos analisar alguns exemplos para ilustrar o conceito de camada.
Uma ferramenta muito útil ao estudar camadas no Chrome é a sinalização "mostrar bordas de camadas compostas" nas configurações (ou seja, o ícone de engrenagem) no DevTools, no cabeçalho "renderização". Ele destaca de forma simples onde as camadas estão na tela. Vamos ligar. Estas capturas de tela e exemplos foram todos retirados do Chrome Canary mais recente, 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 os 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. Eles 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 gira, podemos ver como fica quando um elemento recebe 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, no momento, qualquer um dos seguintes aciona a criação de camadas:
- Propriedades CSS de transformação 3D ou de perspectiva
<video>elementos que usam 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 que usam 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 menor 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 é muito útil 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 mover conteúdo da Web estático. 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, ele não precisará ser repintado. Isso é bom: a repintura leva tempo que pode ser gasto em outras coisas, como executar JavaScript, e, se a pintura for longa, ela causa problemas ou atrasos nas animações.
Veja, por exemplo, esta visualização da linha do tempo do DevTools: não há operações de pintura enquanto essa camada está girando para frente e para trás.
Inválido! Repintura
Mas 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>
Sempre que o elemento de entrada é clicado, o elemento giratório fica 1px mais largo. Isso faz com que todo o elemento seja redefinido e repintado, o que, neste caso, é uma camada inteira.
Uma boa maneira de ver o que está sendo pintado é com a ferramenta "Mostrar retângulos de pintura" no DevTools, também no cabeçalho "Renderização" das configurações do DevTools. Depois de ativar, 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 do DevTools. Leitores atentos podem notar que há dois eventos de pintura: um para a camada e outro para o botão, que é repintado quando muda para/do estado pressionado.
O Chrome nem sempre precisa repintar toda a camada. 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.
Uma pergunta óbvia é o que causa uma invalidação e força uma nova pintura. É difícil responder a isso de forma exaustiva porque há muitos casos extremos que podem forçar invalidações. A causa mais comum é a poluição do DOM ao manipular estilos CSS ou causar um novo layout. Tony Gentilcore tem uma ótima postagem no blog sobre o que causa o relayout, e Stoyan Stefanov tem um artigo que aborda a pintura em mais detalhes (mas termina apenas com a pintura, não com essa composição sofisticada).
A melhor maneira de descobrir se isso está afetando algo em que você está trabalhando é usar as ferramentas Linha do tempo e Mostrar retângulos de pintura do DevTools para ver se você está repintando quando não gostaria e, em seguida, tentar identificar onde você sujou o DOM logo antes desse novo layout/repintura. Se a pintura for inevitável, mas parecer estar demorando muito, confira o artigo de Eberhard Gräther sobre o modo de pintura contínua no DevTools.
Como reunir tudo: do DOM à tela
Como o Chrome transforma o DOM em uma imagem de tela? Conceitualmente, ele:
- Pega o DOM e o divide em camadas
- Pinta cada uma dessas camadas de forma independente em bitmaps de software.
- Faz o upload deles para a GPU como texturas.
- 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 ele pode usar alguns atalhos para frames futuros:
- Se determinadas propriedades CSS mudarem, não será necessário repintar nada. O Chrome pode apenas recompor 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 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 no desempenho de renderização. A composição é comparativamente barata quando nada precisa ser pintado. Portanto, evitar repinturas de camadas é uma boa meta geral ao tentar depurar o desempenho de renderização. Desenvolvedores experientes vão analisar a lista de acionadores de composição acima e perceber que é possível forçar facilmente a criação de camadas. Mas não as crie sem pensar, porque elas não vêm sem custo: elas ocupam memória na RAM do sistema e na GPU (que é particularmente limitada em dispositivos móveis), e ter muitas delas 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 onde não havia sobreposição antes, levando ao que às vezes é chamado de "overdraw". Portanto, use seu conhecimento com sabedoria.
Ficamos por aqui. Fique de olho nos próximos artigos sobre as implicações práticas do modelo de camadas.