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 conteúdo em pixels na tela.
  2. Animar efeitos visuais no conteúdo de um estado para outro.
  3. Role em resposta à entrada.
  4. Encaminha a entrada de forma eficiente 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 interface do navegador. E um fluxo de eventos de entrada brutos de telas touch, 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 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, clipe, filtro, opacidade ou mesclagem.

Componentes da arquitetura

No RenderingNG, essas tarefas são divididas de forma 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 etapa. Os estágios são indicados por cor para demonstrar qual linha de execução ou processo eles são executados. Em alguns casos, as fases podem ser executadas em vários lugares, dependendo da circunstância, e é por isso que algumas têm duas cores. Os estágios verdes renderizam a linha de execução principal do processo. Os amarelos são compositores do processo de renderização. Os estágios laranja são o processo de visualização.

A renderização prossegue em um pipeline com várias etapas e artefatos criados ao longo do caminho. Cada fase representa um código que executa 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 etapas são:

  1. Animar:mude estilos computados e transforme árvores de propriedades ao longo do tempo com base em cronogramas declarativos.
  2. Estilo:aplique CSS ao DOM e crie estilos computados.
  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 árvores de propriedades e invalidate todas as listas de exibição e tijolos de textura da GPU existentes, conforme apropriado.
  5. Rolar:atualize o deslocamento de rolagem de documentos e elementos DOM roláveis, modificando as árvores de propriedades.
  6. Paint:computa uma lista de exibição que descreve como rasterizar blocos de textura de GPU do DOM.
  7. Confirmação:copia árvores de propriedades e a lista de exibição para a linha de execução do compositor.
  8. Camada: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:crie um frame do compositor que represente como desenhar e posicionar os blocos de GPU na tela, junto com os 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.

Os estágios do pipeline de renderização podem 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 pular a linha de execução principal.

A renderização da interface do navegador não é mostrada diretamente aqui, mas pode ser considerada uma versão simplificada desse mesmo pipeline e, 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 de processo e 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 toda a entrada para o processo de renderização apropriado. Há um processo de navegador.
  • O processo de visualização agrega a composição de vários processos de renderização e o processo do navegador. Ele rasteriza e desenha usando a GPU. Há um processo de visualização.

Diferentes sites sempre acabam em diferentes processos de renderização.

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 sites diferentes 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 os iframes e as guias entre sites alcançam o isolamento de desempenho um do outro. Além disso, as origens podem ativar ainda mais isolamento.

Há exatamente um processo de visualização para todo o Chromium, já que geralmente há apenas uma GPU e uma tela para renderizar.

Separar a visualização em um processo próprio é bom para a estabilidade em caso de bugs nos drivers ou no hardware da GPU. Ele também é bom para o isolamento de 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 renderizar, você pode se perguntar: por que há apenas um processo de navegador? O motivo é que apenas uma delas é focada por vez. Na verdade, as guias de navegador não visíveis são desativadas e descartam toda a memória da GPU. No entanto, recursos complexos de renderização da interface do navegador estão sendo implementados cada vez mais em processos de renderização (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. Isso não se aplica ao Chromium no Android em geral, apenas à WebView. No WebView, o processo do navegador também é compartilhado com o app de incorporação, e o 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 é mostrado nos diagramas anteriores.

Linhas de execução

As linhas de execução ajudam a alcançar o isolamento de desempenho e a capacidade de resposta, apesar de tarefas lentas, paralelização de pipeline e vários buffers.

Diagrama do processo de renderização.

  • 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 de script e a análise de HTML, CSS e outros formatos de dados.
    • Os ajudantes da linha de execução principal executam tarefas como a criação de bitmaps de imagem e blobs que exigem codificação ou decodificação.
    • Web Workers executam o script e um loop de eventos de renderização para OffscreenCanvas.
  • A linha de execução do compositor processa eventos de entrada, executa rolagem e animações de conteúdo da Web, calcula a camada ideal de conteúdo da Web e coordena decodificações de imagem, worklets de pintura e tarefas raster.
    • 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.
  • Fios de mídia, demuxer ou saída de áudio decodificam, processam e sincronizam transmissões de áudio e vídeo. 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 é extremamente importante para isolamento de desempenho da 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 do 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 uma linha de execução de compositor por processo de renderização. Geralmente, não é um problema ter apenas uma, porque todas as operações realmente caras na linha de execução do compositor são delegadas para linhas de execução de trabalhadores do compositor ou para o processo de visualização, e esse trabalho pode ser feito em paralelo com o roteamento de entrada, rolagem ou animação. As linhas de execução de worker do compositor coordenam as tarefas executadas no processo de visualização, mas a aceleração de GPU em todos os lugares 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, os computadores geralmente usam mais linhas de execução, porque têm mais núcleos de CPU e são menos restritos à bateria do que os dispositivos móveis. Este é um exemplo de escalonamento vertical e horizontal.

A arquitetura de encadeamento 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 pai responsiva a outras solicitações simultâneas. As linhas de execução auxiliares principais e 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. A linha de execução 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 ocorrendo, a rolagem e a animação podem ser executadas em paralelo.

Processo do navegador

Diagrama de processo do navegador mostrando a relação entre a linha de execução 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 interface do navegador, encaminha outras entradas para o processo de renderização correto e exibe 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 renderização do processo do navegador e a linha de execução de composição são semelhantes 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. Há apenas uma linha de execução necessária nesse caso, porque não é necessário isolar o desempenho de tarefas longas da linha de execução principal, 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.

  • A linha de comando principal da GPU rasteriza listas de exibição e frames de vídeo em blocos de textura da GPU e exibe 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.

Raster e renderização geralmente acontecem na mesma linha de execução, porque ambos dependem de recursos da GPU, e é difícil fazer uso confiável da GPU em vários threads. O acesso mais fácil em vários threads à 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 nível do SO para desenho 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 possível fonte 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 de componentes

Em cada linha principal ou de compositor do processo de renderização, há componentes lógicos de software que interagem entre si de maneira estruturada.

Renderizar componentes da linha de execução principal do processo

Diagrama do renderizador do Blink.

No renderizador Blink:

  • O fragmento de árvore de frames local 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 de acerto e envio de eventos de entrada executa testes de acerto para descobrir qual elemento DOM é direcionado por um evento e executa os algoritmos de envio de eventos de entrada e comportamentos padrão.

O programador e executor do loop de eventos de renderização decide o que executar no loop de eventos e quando. 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. Uma árvore de frames é a página principal e os iframes filhos dela, de forma recursiva. Um frame é local para um processo de renderização se for renderizado nesse processo. Caso contrário, ele é remoto.

Você pode imaginar a coloração de frames de acordo com o processo de renderização. Na imagem anterior, os círculos verdes são todos os frames em um processo de renderização. Os laranjas estão em um segundo e o azul está 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 outra para o site C. Cada árvore de frames local recebe o próprio componente de renderizador do 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 da 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 manipulador de dados que mantém uma lista de camadas compostas, listas de exibição e árvores de propriedades.
  • Um lifecycle runner que executa as etapas de animação, rolagem, composição, rasterização, 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 teste de entrada e de hit executa o processamento de entrada e o teste de hit 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 devem 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>

Ficha 2: bar.com

<html>
 …
</html>

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

A estrutura de processo, linha de execução e componente dessas guias é a seguinte:

Diagrama do processo das guias.

Vamos conferir um exemplo de cada uma das quatro tarefas principais de renderização. Um lembrete:

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

Para renderizar o DOM alterado para a guia 1:

  1. Um script de desenvolvedor muda o DOM no processo de renderização para foo.com.
  2. O renderizador do Blink informa ao compositor que precisa de uma renderização.
  3. O compositor informa a Viz que precisa de uma renderização para ocorrer.
  4. A visualização sinaliza o início da renderização de volta ao compositor.
  5. O compositor encaminha o sinal de início para o renderizador do Blink.
  6. O executor de 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 da composição.
  9. Todas as tarefas de raster são enviadas para o Viz para raster (geralmente há mais de uma dessas tarefas).
  10. A visualização rasteriza o conteúdo na GPU.
  11. O Viz confirma a conclusão da tarefa de varredura. Observação: o Chromium geralmente não espera a conclusão do raster, e usa algo chamado de token de sincronização que precisa ser resolvido por tarefas de raster antes da execução da etapa 15.
  12. Um frame do compositor é enviado para o Viz.
  13. A visualização 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. A visualização mostra o frame do compositor agregado na tela.

Para animar uma transição de transformação do 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. Isso reinicia o ciclo de vida do compositor. As tarefas de rasterização e decodificação podem ocorrer, mas não são mostradas aqui.
  2. Um frame do compositor é enviado para o Viz.
  3. A visualização 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. A visualização programa um sorteio.
  5. A visualização mostra 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) chega ao processo do navegador.
  2. Cada evento é roteado 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 saber se os listeners vão chamar preventDefault no evento.
  6. A linha de execução principal retorna se preventDefault foi chamada 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 novamente para a linha de execução do compositor do processo de renderização do baz.com.
  10. O rolagem é aplicado ali, e a linha de execução do compositor para o processo de renderização do bar.com marca uma animação no loop de eventos 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 informa à linha de execução principal para disparar um evento scroll (não mostrado aqui).
  11. Um frame do compositor é enviado para o Viz.
  12. A visualização 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. A visualização programa um sorteio.
  14. A visualização mostra o frame do compositor agregado na tela.

Para encaminhar um evento click em um hiperlink no iframe #2 na guia 1:

  1. Um evento input (mouse, toque ou teclado) chega ao processo do navegador. Ele executa um teste de acerto aproximado para determinar que 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 para 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. Ao carregar a página de destino do hiperlink, o novo estado é renderizado, com etapas semelhantes ao exemplo anterior de "renderização do DOM alterado". Essas mudanças subsequentes não estão representadas aqui.

Comida 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, com uma 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 paralelas para maximizar desempenho escalonável e oportunidades de extensibilidade.

Cada componente desempenha um papel fundamental para garantir 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.