Visão geral da arquitetura do RenderNG

Chris Harrelson
Chris Harrelson

Em uma postagem anterior, apresentamos uma visão geral das metas e das principais propriedades da arquitetura do RenderingNG. Nesta postagem, explicaremos como as partes dos componentes são configuradas e como o pipeline de renderização flui por elas.

Começando no nível mais alto e indo em detalhes a partir daí, as tarefas de renderização são:

  1. Renderize conteúdo em pixels na tela.
  2. Animar efeitos visuais no conteúdo de um estado para outro.
  3. Role em resposta à entrada.
  4. Encaminhe entradas com eficiência para os lugares certos para que os scripts de desenvolvedor e outros subsistemas possam responder.

O conteúdo a ser renderizado é uma árvore de frames para cada guia do navegador, além da IU do navegador. E um fluxo de eventos de entrada brutos de telas sensíveis ao toque, mouses, teclados e outros dispositivos de hardware.

Cada frame inclui:

  • Estado DOM
  • CSS
  • Telas
  • Recursos externos, como imagens, vídeos, fontes e SVG

Um frame é um documento HTML e o URL dele. Uma página da Web carregada em uma guia do navegador tem um frame de nível superior, frames filhos para cada iframe incluído no documento de nível superior e seus descendentes recursivos de iframe.

Um efeito visual é uma operação gráfica aplicada a um bitmap, como rolagem, transformação, recorte, filtro, opacidade ou mesclagem.

Componentes da arquitetura

No RenderingNG, essas tarefas são divididas logicamente em vários estágios e componentes de código. Os componentes acabam em vários processos de CPU, linhas de execução e subcomponentes dentro delas. Cada um desempenha um papel importante para alcançar a confiabilidade, o desempenho escalonável e a extensibilidade para todo o conteúdo da Web.

Estrutura do pipeline de renderização

Diagrama do pipeline de renderização, conforme explicado no texto a seguir.

A renderização prossegue em um pipeline com vários estágios e artefatos criados. Cada estágio representa o código que realiza uma tarefa bem definida na renderização. Os artefatos são estruturas de dados que são entradas ou saídas dos cenários. No diagrama, as entradas ou saídas são indicadas por setas.

Esta postagem do blog não traz muitos detalhes sobre os artefatos. Isso será discutido na próxima postagem: Principais estruturas de dados e os papéis delas no RenderingNG.

As etapas do pipeline

No diagrama anterior, os estágios são indicados com cores que indicam em qual linha de execução ou processo eles são executados:

  • Verde:linha de execução principal do processo de renderização
  • Amarelo:compositor do processo de renderização
  • Laranja: processo de visualização

Em alguns casos, eles podem ser executados em vários lugares, dependendo das circunstâncias. Por isso, alguns têm duas cores.

As etapas são:

  1. Animar: altere estilos calculados e modifique árvores de propriedades ao longo do tempo com base em linhas do tempo declarativas.
  2. Estilo: aplicar CSS ao DOM e criar estilos calculados.
  3. Layout:determina o tamanho e a posição dos elementos DOM na tela e cria a árvore de fragmentos imutáveis.
  4. Pré-pintura:calcule as árvores de propriedades e invalidate as listas de exibição e os blocos de textura da GPU, conforme apropriado.
  5. Scroll:atualiza o deslocamento de rolagem de documentos e elementos DOM roláveis modificando árvores de propriedades.
  6. Paint:calcule uma lista de exibição que descreva como fazer a varredura dos blocos de textura da GPU no DOM.
  7. Commit:copia as árvores de propriedades e a lista de exibição para a linha de execução do compositor.
  8. Colocar em camadas:divida a lista de exibição em uma lista composta de camadas para varredura e animação independentes.
  9. Worklets raster, decodificação e pintura:transforme listas de exibição, imagens codificadas e código de worklet de pintura, respectivamente, em blocos de textura da GPU.
  10. Activate:cria um frame compositor que representa como desenhar e posicionar blocos da GPU na tela, junto com efeitos visuais.
  11. Aggregate:combina frames de todos os frames compositor visíveis em um único frame global.
  12. Draw:executa o frame agregado do compositor na GPU para criar pixels na tela.

Os estágios do pipeline de renderização poderão ser ignorados se não forem necessários. Por exemplo, animações de efeitos visuais e rolagem podem pular o layout, a pré-pintura e a pintura. É por isso que a animação e a rolagem são marcadas com pontos amarelos e verdes no diagrama. Se o layout, a pré-pintura e a pintura puderem ser ignorados para efeitos visuais, eles poderão ser executados inteiramente na linha de execução do compositor e ignorar a linha de execução principal.

A renderização da interface do navegador não é descrita aqui, mas pode ser considerada uma versão simplificada desse mesmo pipeline e, na verdade, a implementação dela compartilha grande parte do código. Em geral, vídeos, também não representados diretamente, geralmente são renderizados por um código independente que decodifica frames em blocos de textura da GPU que são conectados aos frames do compositor e à etapa de renderização.

Processo e estrutura da linha de execução

Processos de CPU

O uso de vários processos de CPU alcança o isolamento de desempenho e segurança entre sites e do estado do navegador, além de estabilidade e isolamento de segurança do hardware da GPU.

Diagrama das várias partes dos processos da CPU

  • O processo de renderização renderiza, anima, rola e encaminha a entrada de uma única combinação de site e guia. Há muitos processos de renderização.
  • O processo do navegador renderiza, anima e encaminha as entradas para a IU do navegador (incluindo a barra de URL, os títulos e os ícones das guias) e roteia todas as entradas restantes para o processo de renderização apropriado. Existe exatamente um processo de navegador.
  • O processo do Viz agrega a composição de vários processos de renderização mais o processo do navegador. Ele faz a varredura e desenha usando a GPU. Há exatamente um processo de visualização.

Sites diferentes sempre acabam em processos de renderização diferentes. Na verdade, sempre no computador. Quando possível, em dispositivos móveis. Vou escrever “sempre” abaixo, mas essa ressalva se aplica a todo momento.)

Várias guias ou janelas do navegador do mesmo site geralmente passam por processos de renderização diferentes, a menos que as guias estejam relacionadas (uma abrindo a outra). Sob forte pressão de memória no computador, o Chromium pode colocar várias guias do mesmo site no mesmo processo de renderização, mesmo que não estejam relacionadas.

Em uma única guia do navegador, frames de sites diferentes estão sempre em processos de renderização distintos, mas os frames do mesmo site estão sempre no mesmo processo. Do ponto de vista da renderização, a vantagem importante de vários processos de renderização é que os iframes e as guias entre sites alcançam isolamento de desempenho entre si. Além disso, as origens podem oferecer ainda mais isolamento.

Existe exatamente um processo de Viz para todo o Chromium. Afinal, geralmente há apenas uma GPU e tela para desenhar. Separar o Viz em seu próprio processo é bom para a estabilidade diante de bugs nos drivers ou no hardware da GPU. Também é bom para o isolamento de segurança, o que é importante para APIs de GPU, como a Vulkan. Isso também é importante para a segurança em geral.

Como o navegador pode ter muitas guias e janelas, e todas elas têm pixels de IU do navegador para desenhar, você pode se perguntar: por que existe exatamente um processo do navegador? O motivo é que apenas uma delas é focada por vez. Na verdade, as guias não visíveis do navegador são desativadas e eliminam toda a memória da GPU. No entanto, recursos complexos de renderização da interface do navegador estão sendo cada vez mais implementados em processos de renderização (conhecidos como WebUI). Isso não ocorre por motivos de isolamento de desempenho, mas para aproveitar a facilidade de uso do mecanismo de renderização da Web do Chromium.

Em dispositivos Android mais antigos, os processos de renderização e navegação são compartilhados quando usados em uma WebView. Isso não se aplica ao Chromium no Android, geralmente, apenas à WebView. Na WebView, o processo do navegador também é compartilhado com o app de incorporação, e a WebView só tem um processo de renderização.

Às vezes, também há um processo utilitário para decodificar conteúdo de vídeo protegido. Esse processo não está descrito acima.

Conversas

As linhas de execução ajudam a alcançar o isolamento de desempenho e a capacidade de resposta, apesar das tarefas lentas, do carregamento em paralelo do pipeline e do armazenamento em buffer múltiplo.

Um diagrama do processo de renderização, conforme descrito no artigo.

  • A linha de execução principal executa scripts, o loop de eventos de renderização, o ciclo de vida do documento, o teste de hit, o envio de eventos do script e a análise de HTML, CSS e outros formatos de dados.
    • Os auxiliares de linha de execução principal realizam tarefas como criar bitmaps e blobs de imagem que exigem codificação ou decodificação.
    • Os Web Workers executam um script e um loop de evento de renderização para o OffscreenCanvas.
  • A linha de execução do Compositor processa eventos de entrada, executa rolagem e animações de conteúdo da Web, calcula a criação de camadas ideal de conteúdo da Web e coordena decodificações de imagens, worklets de pintura e tarefas de varredura.
    • Os auxiliares de linha de execução do Compositor coordenam as tarefas de varredura do Viz e executam tarefas de decodificação de imagem, worklets de pintura e varredura de substituto.
  • As linhas de execução de mídia, de multiplexador ou de saída de áudio decodificam, processam e sincronizam streams de vídeo e áudio. Lembre-se de que o vídeo é executado em paralelo com o pipeline de renderização principal.

A separação das linhas de execução principal e do compositor é extremamente importante para o isolamento de desempenho de animação e rolagem do trabalho da linha de execução principal.

Há apenas uma linha de execução principal por processo de renderização, mesmo que várias guias ou frames do mesmo site possam acabar no mesmo processo. No entanto, há isolamento de desempenho do trabalho realizado em várias APIs de navegador. Por exemplo, a geração de bitmaps e blobs de imagem na API Canvas é executada em uma linha de execução auxiliar de linha de execução principal.

Da mesma forma, há apenas uma linha de execução de compositor por processo de renderização. Geralmente, não é um problema que exista apenas uma, porque todas as operações muito caras na linha de execução do compositor são delegadas às linhas de execução do worker ou ao processo do Viz. Esse trabalho pode ser feito em paralelo com roteamento de entrada, rolagem ou animação. As linhas de execução de worker do combinável executam tarefas de coordenadas no processo Viz, mas a aceleração da GPU em qualquer lugar pode falhar por motivos fora do controle do Chromium, como bugs do driver. Nessas situações, a linha de execução de worker faz o trabalho em um modo substituto na CPU.

O número de linhas de execução de worker do compositor depende dos recursos do dispositivo. Por exemplo, computadores geralmente usam mais linhas de execução, já que têm mais núcleos de CPU e consomem menos bateria do que os dispositivos móveis. Este é um exemplo de aumento e redução.

Também é interessante notar que a arquitetura de linha de execução do processo de renderização é uma aplicação de três padrões de otimização diferentes:

  • Linhas de execução auxiliares:enviar subtarefas de longa duração para outras linhas de execução para manter a linha de execução mãe responsiva a outras solicitações que acontecem simultaneamente. As linhas de execução auxiliares de encadeamento principal e de compositor são bons exemplos dessa técnica.
  • Armazenamento em buffer múltiplo: exibição de conteúdo renderizado anteriormente ao renderizar conteúdo novo para ocultar a latência de renderização. A linha de execução do compositor usa essa técnica.
  • Paralelização de pipelines: executar o pipeline de renderização em vários lugares simultaneamente. É assim que a rolagem e a animação podem ser rápidas, mesmo que uma atualização de renderização da linha de execução principal esteja acontecendo, porque a rolagem e a animação podem ser executadas em paralelo.

Processo do navegador

Um diagrama do processo do navegador mostrando a relação entre o thread de renderização e composição e o auxiliar de linha de execução de renderização e composição.

  • A linha de execução de renderização e composição responde à entrada na IU do navegador, encaminha outras entradas para o processo de renderização correto e apresenta e pinta a interface do navegador.
  • Os auxiliares de linha de execução de renderização e composição executam tarefas de decodificação de imagens e fazem a varredura ou decodificação de substituto.

A linha de execução de renderização e composição do processo do navegador são semelhantes ao código e à funcionalidade de um processo de renderização, exceto pelo fato de que a linha de execução principal e a do compositor são combinadas em uma só. Há apenas uma linha de execução necessária nesse caso porque não é necessário isolar o desempenho de tarefas de linha de execução principais longas, já que não há nenhuma por padrão.

Processo de visualização

Um diagrama que mostra que o processo do Viz inclui a linha de execução principal da GPU e a linha de execução do compositor de exibição.

  • A linha de execução principal da GPU faz varreduras na exibição de listas e frames de vídeo em blocos de textura da GPU e desenha frames do compositor na tela.
  • A linha de execução do compositor de exibição agrega e otimiza a composição de cada processo de renderização, além do processo do navegador, em um único frame do compositor para apresentação na tela.

O raster e o desenho geralmente acontecem na mesma linha de execução, porque ambos dependem de recursos da GPU, e é difícil usar a GPU com várias linhas de execução de maneira confiável. O acesso mais fácil à GPU é uma motivação para desenvolver o novo padrão Vulkan. No Android WebView, há uma linha de execução de renderização separada no SO para renderização devido à forma como as WebViews são incorporadas a um app nativo. Outras plataformas provavelmente vão ter essa linha de execução no futuro.

O compositor de exibição está em uma linha de execução diferente, porque precisa ser responsivo o tempo todo e não bloquear qualquer fonte possível de lentidão na linha de execução principal da GPU. Uma causa de lentidão na linha de execução principal da GPU são chamadas para código não Chromium, como drivers de GPU específicos do fornecedor, que podem ser lentos de maneiras difíceis de prever.

Estrutura dos componentes

Dentro de cada linha de execução principal ou do compositor do processo de renderização, há componentes de software lógicos que interagem entre si de maneiras estruturadas.

Principais componentes da linha de execução do processo de renderização

Diagrama do renderizador Blink.

  • Renderizador de brilho:
    • O fragmento da árvore de frames locais representa a árvore de frames locais e o DOM nos frames.
    • O componente APIs DOM e Canvas contém implementações de todas essas APIs.
    • O executor do ciclo de vida do documento executa as etapas do pipeline de renderização até a etapa de confirmação.
    • O componente de teste e envio de hits de evento de entrada executa testes de hit para descobrir qual elemento DOM é segmentado por um evento e executa os algoritmos de envio do evento de entrada e os comportamentos padrão.
  • O programador e executor do loop de eventos de renderização decide o que executar no loop de eventos e quando. Ela programa a renderização para ocorrer em uma cadência que corresponda à tela do dispositivo.

Um diagrama da árvore de frames.

Os fragmentos da árvore de frames locais são um pouco complicados de se pensar. Lembre-se de que uma árvore de frames é a página principal e os iframes filhos, recursivamente. Um frame é local para um processo de renderização se for renderizado nesse processo. Caso contrário, será remoto.

Imagine colorir frames de acordo com o processo de renderização. Na imagem anterior, os círculos verdes são todos frames de um processo de renderização. Os laranja estão em um segundo e os azuis estão em um terceiro.

Um fragmento da árvore de frames local é um componente conectado da mesma cor em uma árvore de frames. Há quatro árvores de frame locais na imagem: duas para o local A, uma para o local B e uma para o local C. Cada árvore de frames local tem o próprio componente do renderizador Blink. O renderizador Blink de uma árvore de frames local pode ou não estar no mesmo processo de renderização que outras árvores de frames locais. Isso é determinado pela maneira como os processos de renderização são selecionados, conforme descrito anteriormente.

Estrutura da linha de execução do compositor do processo de renderização

Um diagrama que mostra os componentes do compositor do processo de renderização.

Os componentes do compositor do processo de renderização incluem:

  • Um gerenciador de dados que mantém uma lista de camadas compostas, listas de exibição e árvores de propriedades.
  • Um executor do ciclo de vida que executa as etapas de animação, rolagem, composição, varredura e decodificação e ativação do pipeline de renderização. Lembre-se de que a animação e a rolagem podem ocorrer na linha de execução principal e no compositor.
  • Um gerenciador de testes de entrada e hit executa o processamento de entrada e o teste de hits na resolução de camadas compostas para determinar se os gestos de rolagem podem ser executados na linha de execução do compositor e quais testes de hit do processo de renderização precisam ser direcionados.

Um exemplo na prática

Vamos agora concretizar a arquitetura com um exemplo. Neste exemplo, há três guias:

Guia 1: foo.com

<html>
  <iframe id=one src="foo.com/other-url"></iframe>
  <iframe  id=two src="bar.com"></iframe>
</html>

Tab 2: bar.com

<html>
 …
</html>

Guia 3: baz.com html <html> … </html>

A estrutura de processos, linhas de execução e componentes dessas guias vai ficar assim:

Diagrama do processo das guias.

Agora, vamos analisar um exemplo de cada uma das quatro tarefas principais de renderização, que, como você deve se lembrar, são:

  1. Renderize o conteúdo em pixels na tela.
  2. Animar os efeitos visuais no conteúdo de um estado para outro.
  3. Role em resposta à entrada.
  4. Encaminhe a entrada com eficiência para os lugares certos para que os scripts de desenvolvedor e outros subsistemas possam responder.

Para renderizar o DOM alterado da primeira guia:

  1. Um script de desenvolvedor muda o DOM no processo de renderização de foo.com.
  2. O renderizador Blink informa ao compositor que precisa de uma renderização.
  3. O compositor informa ao Viz que uma renderização precisa ocorrer.
  4. O Viz sinaliza o início da renderização de volta para o compositor.
  5. O compositor encaminha o sinal inicial para o renderizador Blink.
  6. O executor do loop de eventos da linha de execução principal executa o ciclo de vida do documento.
  7. A linha de execução principal envia o resultado para a linha de execução do compositor.
  8. O executor de loop de eventos do compositor executa o ciclo de vida de composição.
  9. Todas as tarefas de varredura são enviadas ao Viz para varredura (geralmente, há mais de uma dessas tarefas).
  10. O Viz faz uma varredura do conteúdo na GPU.
  11. A visualização confirma a conclusão da tarefa de varredura. Observação: o Chromium geralmente não aguarda a conclusão da varredura. Em vez disso, usa algo chamado token de sincronização, que precisa ser resolvido por tarefas de varredura antes da execução da etapa 15.
  12. Um frame de compositor é enviado para o Viz.
  13. O Viz agrega os frames do compositor para o processo de renderização do foo.com, o processo de renderização do iframe do bar.com e a interface do navegador.
  14. O Viz programa um empate.
  15. O Viz desenha o frame agregado do compositor na tela.

Para animar uma transição de transformação CSS na guia dois:

  1. A linha de execução do compositor para o processo de renderização do bar.com ativa uma animação no loop de eventos do compositor, modificando as árvores de propriedades existentes. Isso executará novamente o ciclo de vida do compositor. Tarefas de raster e decodificação podem ocorrer, mas não são descritas aqui.
  2. Um frame de compositor é enviado para o Viz.
  3. O Viz agrega os frames do compositor para o processo de renderização do foo.com, o processo de renderização do bar.com e a interface do navegador.
  4. O Viz programa um empate.
  5. O Viz desenha o frame agregado do compositor na tela.

Para rolar a página da Web na guia três:

  1. Uma sequência de eventos input (mouse, toque ou teclado) chega ao processo do navegador.
  2. Cada evento é encaminhado para a linha de execução do compositor do processo de renderização do baz.com.
  3. O compositor determina se a linha de execução principal precisa saber sobre o evento.
  4. O evento é enviado, se necessário, para a linha de execução principal.
  5. A linha de execução principal dispara listeners de eventos input (pointerdown, touchstar, pointermove, touchmove ou wheel) para ver se os listeners vão chamar preventDefault no evento.
  6. A linha de execução principal retorna se preventDefault foi chamado para o compositor.
  7. Caso contrário, o evento de entrada será enviado de volta ao processo do navegador.
  8. O processo do navegador a converte em um gesto de rolagem, combinando-a com outros eventos recentes.
  9. O gesto de rolagem é enviado mais uma vez para o encadeamento do compositor do processo de renderização do baz.com.
  10. A rolagem é aplicada nesse local, e a linha de execução do compositor para o processo de renderização bar.com ativa uma animação no loop de eventos do compositor. Isso modifica o deslocamento de rolagem nas árvores de propriedades e executa novamente o ciclo de vida do compositor. Ele também instrui a linha de execução principal a disparar um evento scroll (não mostrado aqui).
  11. Um frame de compositor é enviado para o Viz.
  12. O Viz agrega os frames do compositor para o processo de renderização do foo.com, o processo de renderização do bar.com e a interface do navegador.
  13. O Viz programa um empate.
  14. O Viz desenha o frame agregado do compositor na tela.

Para encaminhar um evento click em um hiperlink no iframe #dois na primeira guia, faça o seguinte:

  1. Um evento input (mouse, toque ou teclado) chega ao processo do navegador. Ele realiza um teste de hit aproximado para determinar se o processo de renderização do iframe do bar.com precisa receber o clique e o envia para lá.
  2. A linha de execução do compositor para bar.com encaminha o evento click para a linha de execução principal de bar.com e programa uma tarefa de loop de eventos de renderização para processá-lo.
  3. O processador de eventos de entrada dos testes de hit da linha de execução principal do bar.com determina qual elemento DOM no iframe foi clicado e dispara um evento click para os scripts observarem. Não ouve preventDefault e navega para o hiperlink.
  4. Após o carregamento da página de destino do hiperlink, o novo estado é renderizado, com etapas semelhantes ao exemplo "renderizar DOM alterado" acima. Essas mudanças subsequentes não são descritas aqui.

Conclusão

Uau, foram muitos detalhes. Como você pode ver, a renderização no Chromium é bem complicada. Pode levar muito tempo para lembrar e internalizar todas as peças, então, não se preocupe se isso parecer complicado.

A conclusão mais importante é que há um pipeline de renderização conceitualmente simples, que, por meio de modularização cuidadosa e atenção aos detalhes, foi dividido em vários componentes independentes. Esses componentes foram divididos em processos e linhas de execução paralelos para maximizar as oportunidades de desempenho escalonável e extensibilidade.

Cada um desses componentes desempenha um papel fundamental para possibilitar o desempenho e os recursos necessários aos aplicativos da Web modernos. Em breve, publicaremos informações detalhadas sobre cada um deles e as funções importantes que desempenham.

Mas, antes disso, também vou explicar como as principais estruturas de dados mencionadas nesta postagem (as indicadas em azul nas laterais do diagrama do pipeline de renderização) são tão importantes para o RenderingNG que os componentes de código.

Agradecemos a atenção. Aguarde!

Ilustrações de Una Kravets.