Arquitetura RenderNG

Chris Harrelson
Chris Harrelson

Você verá como as partes do componente RenderingNG são configuradas e como o pipeline de renderização flui por elas.

Começando no nível mais alto, as tarefas de renderização são:

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

O conteúdo a ser renderizado é uma árvore de frames para cada guia do navegador, além da interface do navegador. E um fluxo de eventos de entrada brutos de telas sensíveis ao toque, mouse, 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 mais seu URL. 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 de iframe recursivos.

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

Componentes da arquitetura

No RenderingNG, essas tarefas são divididas de maneira lógica 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 dessas linhas de execução. Cada um desempenha um papel importante para garantir 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.
As setas indicam as entradas e saídas de cada estágio. Os estágios são indicados por cor para demonstrar qual linha de execução ou processo eles são executados. Em alguns casos, os estágios podem ser executados em vários lugares, dependendo da circunstância. Por isso, alguns têm duas cores. As fases verdes da linha de execução principal do processo de renderização, os amarelos são os compositores do processo de renderização. Os estágios laranjas são o processo de visualização.

A renderização ocorre em um pipeline com vários estágios e artefatos criados ao longo do caminho. Cada etapa 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 estágios.

As fases são:

  1. Animar:altere os estilos calculados e as árvores de propriedade ao longo do tempo com base em linhas do tempo declarativas.
  2. Estilo: aplique CSS ao DOM e crie estilos calculados.
  3. Layout:determine o tamanho e a posição dos elementos do DOM na tela e crie a árvore de fragmentos imutável.
  4. Pré-pintura: calcule as árvores de propriedades e invalide as listas de exibição e os blocos de textura da GPU conforme apropriado.
  5. Rolar:atualize o deslocamento de rolagem de documentos e elementos DOM roláveis mudando as árvores de propriedades.
  6. Paint:calcula uma lista de exibição que descreve como fazer a varredura dos blocos de textura da GPU no DOM.
  7. Commit:copie as árvores de propriedades e a lista de exibição para a linha de execução do compositor.
  8. Criar camadas:divida a lista de exibição em uma lista de camadas compostas para rasterização e animação independentes.
  9. Raster, decodificar e pintar worklets: transforma listas de exibição, imagens codificadas e código do worklet de pintura, respectivamente, em blocos de textura da GPU.
  10. Ativar:cria um frame de composição que representa como desenhar e posicionar blocos da GPU na tela, junto com quaisquer efeitos visuais.
  11. Agregação:combina frames do compositor de todos os frames visíveis em um único frame global.
  12. Draw:executa o frame do compositor agregado na GPU para criar pixels na tela.

As etapas do pipeline de renderização podem ser ignoradas se não forem necessárias. 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 estã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 pular a linha de execução principal.

A renderização da interface do navegador não é retratada diretamente aqui, mas pode ser considerada uma versão simplificada desse mesmo pipeline. Na verdade, a implementação compartilha grande parte do código. O vídeo (também não representado diretamente) geralmente é renderizado com código independente que decodifica frames em blocos de textura da GPU que são conectados a frames do compositor e à etapa de exibição.

Estrutura do processo e da linha de execução

Processos da CPU

O uso de vários processos de CPU alcança um isolamento de desempenho e segurança entre sites e do estado do navegador, além do isolamento de estabilidade e 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 para uma única combinação de site e guia. Há vários processos de renderização.
  • O processo do navegador renderiza, anima e encaminha a entrada para a interface do navegador (incluindo a barra de endereço, títulos de guias e ícones) e encaminha todas as entradas restantes para o processo de renderização apropriado. Há um processo de navegador.
  • O processo Viz agrega a composição de vários processos de renderização e o processo do navegador. Ela faz a varredura e desenha usando a GPU. Existe um processo de Viz.

Sites diferentes sempre acabam em processos de renderização diferentes.

Várias guias ou janelas do navegador do mesmo site geralmente passam em processos de renderização diferentes, a menos que as guias estejam relacionadas, como 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 sejam relacionadas.

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

Existe exatamente um processo Viz para todo o Chromium, já que geralmente há apenas uma GPU e tela para desenhar.

Separar o Viz no próprio processo é bom para estabilidade diante de bugs em drivers ou hardwares da GPU. Ele também é bom para o isolamento da segurança, o que é importante para APIs de GPU, como Vulkan e segurança em geral.

Como o navegador pode ter muitas guias e janelas e todas elas têm pixels de interface do navegador para desenhar, você pode se perguntar: por que existe exatamente um processo de navegador? O motivo é que apenas um deles é focado por vez. Na verdade, as guias do navegador não visíveis são, na maioria das vezes, desativadas e descartam toda a memória da GPU. No entanto, recursos complexos de renderização da interface do navegador do navegador também estão sendo implementados em processos de renderização também (conhecidos como WebUI). Isso não é 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, o processo de renderização e do navegador são compartilhados quando usados em uma WebView. Em geral, isso não se aplica ao Chromium no Android, apenas ao WebView. Na WebView, o processo do navegador também é compartilhado com o app de incorporação, e a WebView tem apenas 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 é descrito nos diagramas anteriores.

Conversas

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

Diagrama do processo de renderização.

  • A linha de execução principal executa scripts, o loop de evento de renderização, o ciclo de vida do documento, testes de hit, envio de eventos de script e análise de HTML, CSS e outros formatos de dados.
    • Os principais auxiliares de linha de execução 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 OffscreenCanvas.
  • A linha de execução de composição processa eventos de entrada, realiza rolagem e animações de conteúdo da Web, calcula a em camadas ideal do 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 criador coordenam tarefas de varredura do Viz e executam tarefas de decodificação de imagem, worklets de pintura e varredura de substituto.
  • Linhas de execução de mídia, demuxer ou 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.

Separar as linhas de execução principal e do compositor é fundamental para 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, existe um 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 da linha de execução principal.

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

O número de threads de worker do compositor depende dos recursos do dispositivo. Por exemplo, os computadores geralmente usam mais linhas de execução, porque têm mais núcleos de CPU e têm menos restrições de bateria do que os dispositivos móveis. Este é um exemplo de escalonamento vertical e horizontal.

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: envie 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 simultâneas. O assistente de linha de execução principal e as linhas de execução auxiliares do compositor são bons exemplos dessa técnica.
  • Armazenamento em buffer múltiplo: mostra conteúdo já renderizado enquanto renderiza um novo conteúdo para ocultar a latência de renderização. O thread do compositor usa essa técnica.
  • Carregamento em paralelo do pipeline:execute 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, a rolagem e a animação podem ser executadas em paralelo.

Processo do navegador

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

  • A linha de execução de renderização e composição responde à entrada na interface do navegador, encaminha outras entradas para o processo de renderização correto, 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 substitutos.

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

Processo de visualização

O processo do Viz inclui a linha de execução principal da GPU e a linha de execução do compositor de exibição.

  • Os varreduras da linha de execução principal da GPU exibem listas e frames de vídeo em blocos de textura da GPU e desenha frames do compositor na tela.
  • A linha de execução de exibição do compositor 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.

Raster e desenho geralmente acontecem na mesma linha de execução, porque ambos dependem de recursos da GPU, e é difícil fazer o uso confiável da GPU em várias linhas de execução. O acesso mais fácil a várias linhas de execução à GPU é uma das motivações para desenvolver o novo padrão Vulkan. No Android WebView, há uma linha de execução de renderização separada no nível do SO para desenhar devido à forma como as WebViews são incorporadas a um app nativo. Outras plataformas provavelmente terão uma linha de execução como essa 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 nenhuma 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ódigos que não são do Chromium, como drivers de GPU específicos do fornecedor, que podem ser lentas de maneiras difíceis de prever.

Estrutura do componente

Em cada linha de execução principal do processo de renderização ou do compositor, 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.

No renderizador do Blink:

  • O fragmento de árvore de frames local representa a árvore de frames locais e o DOM nos frames.
  • O componente das 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é e incluindo a etapa de confirmação.
  • O componente de teste e envio de hits de eventos de entrada executa testes de hits para descobrir qual elemento DOM é segmentado por um evento e executa o evento de entrada que envia algoritmos e comportamentos padrão.

O programador e executor do loop de eventos de renderização decidem o que e quando executar no loop de eventos. Ele programa a renderização para que aconteça em uma cadência correspondente à tela do dispositivo.

Um diagrama da árvore de frames.

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

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

Um fragmento de árvore de frames local é um componente conectado da mesma cor em uma árvore de frames. Há quatro árvores de frames locais na imagem: duas para o site A, uma para o site B e uma para o site C. Cada árvore de frames local recebe seu 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. Ele é determinado pela forma como os processos de renderização são selecionados, conforme descrito anteriormente.

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

Um diagrama mostrando 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, decodificação e ativação do pipeline de renderização. Lembre-se de que a animação e a rolagem podem ocorrer tanto na linha de execução principal quanto no compositor.
  • Um gerenciador de testes de hit e de entrada 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.

Exemplo de arquitetura na prática

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>

Guia 2: bar.com

<html>
 …
</html>

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

O processo, linha de execução e estrutura de componentes dessas guias tem a seguinte aparência:

Diagrama do processo das guias.

Vamos examinar um exemplo de cada uma das quatro tarefas principais de renderização. Lembrete:

  1. Renderizar o conteúdo em pixels na tela.
  2. Animar efeitos visuais no conteúdo de um estado para outro.
  3. Rolar em resposta à entrada.
  4. Rotear a entrada de forma eficiente para os lugares certos, para que os scripts do desenvolvedor e outros subsistemas possam responder.

Para renderizar o DOM alterado para a guia 1:

  1. Um script de desenvolvedor altera o DOM no processo de renderização para foo.com.
  2. O renderizador Blink informa ao compositor que ele precisa de uma renderização para ocorrer.
  3. O compositor informa ao Viz que é necessária uma renderização para 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 de loop do evento 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 do 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 a varredura do conteúdo na GPU.
  11. O Viz confirma a conclusão da tarefa de varredura. Observação: o Chromium geralmente não espera a varredura terminar. Em vez disso, ele 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 do compositor é enviado para o Viz.
  13. O Viz agrega os frames do compositor para o processo de renderização de foo.com, o processo de renderização de iframe bar.com e a interface do navegador.
  14. O Viz programa um desenho.
  15. O Viz desenha o frame do compositor agregado na tela.

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

  1. A linha de execução do compositor para o processo de renderização de bar.com marca uma animação no loop de eventos do compositor mutando as árvores de propriedades existentes. Em seguida, isso executa novamente o ciclo de vida do compositor. Tarefas de varredura e decodificação podem ocorrer, mas não estão descritas aqui.
  2. Um frame do compositor é enviado para o Viz.
  3. O Viz agrega os frames do compositor para o processo de renderização de foo.com, para o processo de renderização bar.com e para a interface do navegador.
  4. O Viz programa um desenho.
  5. O Viz desenha o frame do compositor agregado na tela.

Para rolar a página da Web na terceira guia:

  1. Uma sequência de eventos input (mouse, toque ou teclado) ocorre no processo do navegador.
  2. Cada evento é roteado para o thread 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. Se necessário, o evento é enviado 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 para o 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 novamente ao thread do compositor do processo de renderização de baz.com.
  10. A rolagem é aplicada lá, e a linha de execução do compositor para o processo de renderização bar.com marca uma animação no loop de evento do compositor. Em seguida, ele modifica o deslocamento de rolagem nas árvores de propriedade 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 do compositor é enviado para o Viz.
  12. O Viz agrega os frames do compositor para o processo de renderização de foo.com, o processo de renderização de bar.com e a interface do navegador.
  13. O Viz programa um desenho.
  14. O Viz desenha o frame do compositor agregado na tela.

Para encaminhar um evento click em um hiperlink no iframe #two na primeira guia:

  1. Um evento input (mouse, toque ou teclado) chega ao processo do navegador. Ele executa um teste de hit aproximado para determinar se o processo de renderização de iframe 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 evento de renderização para processá-lo.
  3. O processador de eventos de entrada dos testes de hit da linha de execução principal de bar.com para determinar qual elemento DOM do iframe recebeu um clique e dispara um evento click para que os scripts observem. Ou seja, não ouvirá preventDefault e a pessoa navegará para o hiperlink.
  4. Após o carregamento da página de destino do hiperlink, o novo estado é renderizado, com etapas semelhantes ao exemplo anterior "renderizar o DOM alterado". Essas alterações subsequentes não são descritas aqui.

Para viagem

Pode levar muito tempo para lembrar e internalizar como a renderização funciona.

A conclusão mais importante é que o pipeline de renderização, 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 componente desempenha um papel crítico para ativar o desempenho e os recursos de apps da Web modernos.

Continue lendo sobre as estruturas de dados principais, que são tão importantes para o RenderingNG quanto nos componentes de código.


Ilustrações de Una Kravets.