Principais estruturas de dados e as respectivas funções na renderização

Chris Harrelson
Chris Harrelson
Daniel Cheng
Daniel Cheng
Philip Rogers
Philip Rogers
Koji Ishi
Koji Ishi
Ian Kilpatrick
Ian Kilpatrick
Kyle Charbonneau
Kyle Charbonneau

As postagens anteriores desta série deram uma visão geral das metas, propriedades principais e partes de componentes de alto nível da arquitetura RenderingNG. Agora vamos analisar as principais estruturas de dados que são entradas e saídas para o pipeline de renderização.

Essas estruturas de dados são:

  • Árvores de frames, que são compostas por nós locais e remotos que representam quais documentos da Web estão em qual processo de renderização e qual renderizador de Blink estão.
  • A árvore de fragmentos imutáveis representa a saída e a entrada do algoritmo de restrição de layout.
  • Árvores de propriedades, que representam as hierarquias de transformação, recorte, efeito e rolagem de um documento da Web e são usadas em todo o pipeline.
  • Listas de exibição e blocos de pintura são as entradas para os algoritmos de varredura e de camadas.
  • Os frames de composição encapsulam superfícies, superfícies de renderização e blocos de textura da GPU usados para desenhar usando a GPU.

Antes de passar por essas estruturas de dados, quero mostrar o seguinte exemplo simples que se baseia em uma da postagem anterior. Vou usar esse exemplo algumas vezes nesta postagem, mostrando como as estruturas de dados se aplicam a ele.

<html>
  <div style="overflow: hidden; width: 100px; height: 100px;">
    <iframe style="filter: blur(3px);
      transform: rotateZ(1deg);
      width: 100px; height: 300px"
      id="one" src="foo.com/etc"></iframe>
  </div>
  <iframe style="top:200px;
    transform: scale(1.1) translateX(200px)"
    id="two" src="bar.com"></iframe>
</html>

Árvores de molduras

Às vezes, o Chrome pode renderizar um frame de origem cruzada em um processo de renderização diferente do frame pai.

No exemplo da introdução, há três frames no total:

Um frame pai foo.com, contendo dois iframes.

Com o isolamento de sites, o Chromium usará dois processos de renderização para renderizar essa página da Web. Cada processo de renderização tem sua própria representação da árvore de frames dessa página da Web:

Duas árvores de frames que representam os dois processos de renderização.

Um frame renderizado em um processo diferente é representado como um frame remoto. Um frame remoto contém as informações mínimas necessárias para atuar como um marcador na renderização, como as dimensões dele. Caso contrário, o frame remoto não contém as informações necessárias para renderizar o conteúdo real.

Por outro lado, um frame local representa um frame que passará pelo pipeline de renderização padrão descrito nas postagens anteriores. O frame local contém todas as informações necessárias para transformar os dados dele (como a árvore DOM e os dados de estilo) em algo que possa ser renderizado e exibido.

O pipeline de renderização opera com a granularidade de um fragmento de árvore de frame local. Considere um exemplo mais complicado com foo.com como frame principal:

<iframe src="bar.com"></iframe>

E o seguinte subframe bar.com:

<iframe src="foo.com/etc"></iframe>

Embora ainda haja apenas dois renderizadores, agora há três fragmentos da árvore de frames locais, com dois no processo de renderização para foo.com e um no processo de renderização para bar.com:

Uma representação das duas renderizações e dos três fragmentos da árvore de frames.

Para produzir um frame de compositor para a página da Web, o Viz solicita simultaneamente um frame de compositor do frame raiz de cada uma das três árvores de frame locais e, em seguida, as agrega. Consulte também a seção de frames do compositor mais adiante nesta postagem.

O frame principal foo.com e o subframe foo.com/other-page fazem parte da mesma árvore de frames e são renderizados no mesmo processo. No entanto, os dois frames ainda têm ciclos de vida de documentos independentes, já que fazem parte de diferentes fragmentos da árvore de frames locais. Por esse motivo, não é possível gerar um frame do compositor para ambos em uma única atualização. O processo de renderização não tem informações suficientes para compor o frame do compositor gerado para foo.com/other-page diretamente no frame do compositor para o frame principal foo.com. Por exemplo, o frame pai bar.com fora do processo pode afetar a exibição do iframe foo.com/other-url, transformando o iframe com CSS ou ocultando partes do iframe com outros elementos no DOM.

A hierarquia de atualização da propriedade visual

As propriedades visuais, como o fator de escala do dispositivo e o tamanho da janela de visualização, afetam a saída renderizada e precisam ser sincronizadas entre os fragmentos da árvore de frames local. A raiz de cada fragmento da árvore de frames local tem um objeto de widget associado a ela. As atualizações de propriedade visual vão para o widget do frame principal antes de serem propagadas para os widgets restantes de cima para baixo. Por exemplo, quando o tamanho da janela de visualização é alterado:

Diagrama do processo explicado no texto anterior.

Esse processo não é instantâneo. Portanto, as propriedades visuais replicadas também incluem um token de sincronização. O compositor do Viz usa esse token de sincronização para aguardar que todos os fragmentos da árvore de frames locais enviem um frame do compositor com o token de sincronização atual. Esse processo evita a mistura de frames do compositor com diferentes propriedades visuais.

A árvore de fragmentos imutáveis

A árvore de fragmentos imutáveis é a saída do estágio de layout do pipeline de renderização. Ele representa a posição e o tamanho de todos os elementos na página (sem transformações aplicadas).

Representação dos fragmentos em cada árvore, com um deles sendo marcado como precisando de layout.

Cada fragmento representa uma parte de um elemento DOM. Normalmente, há apenas um fragmento por elemento, mas poderá haver mais se ele for dividido em páginas diferentes durante a impressão, ou em colunas em um contexto de várias colunas.

Após o layout, cada fragmento se torna imutável e nunca é alterado de novo. Também aplicamos algumas restrições adicionais. O que não fazemos:

  • Permita todas as referências "para cima" na árvore. Um filho não pode ter um ponteiro para o pai.
  • Dados de "balão" abaixo da árvore (um filho só lê informações de seus filhos, não de seu pai).

Essas restrições permitem reutilizar um fragmento para um layout subsequente. Sem essas restrições, precisaríamos gerar novamente toda a árvore, o que é caro.

A maioria dos layouts costuma ser atualizações incrementais, por exemplo, um app da Web atualizando uma pequena parte da interface em resposta ao clique do usuário em um elemento. Idealmente, o layout deve funcionar apenas proporcionalmente ao que realmente mudou na tela. Podemos fazer isso reutilizando o máximo possível de partes da árvore anterior. Isso significa (normalmente) que só precisamos reconstruir a lombada da árvore.

No futuro, esse design imutável nos permitirá fazer coisas interessantes, como transmitir a árvore de fragmentos imutável entre os limites da linha de execução, se necessário (para executar fases subsequentes em uma linha de execução diferente), gerar várias árvores para uma animação de layout suave ou executar layouts especulativos paralelos. Isso também nos proporciona o potencial do próprio layout com várias linhas de execução.

Itens de fragmento inline

O conteúdo inline (principalmente texto estilizado) usa uma representação ligeiramente diferente. Em vez de uma estrutura de árvore com caixas e ponteiros, representamos o conteúdo in-line em uma lista simples que representa a árvore. O principal benefício é que uma representação de lista simples para inline é rápida, útil para inspecionar ou consultar estruturas de dados inline e eficiência de memória. Isso é extremamente importante para o desempenho da renderização da Web, já que a renderização de texto é muito complexa e pode facilmente se tornar a parte mais lenta do pipeline, a menos que seja altamente otimizada.

É uma observação histórica interessante. Isso é muito semelhante à forma como o Internet Explorer representava o DOM, já que foi criado inicialmente de maneira semelhante como um editor de texto.

A lista simples é criada para cada contexto de formatação inline na ordem de uma pesquisa em profundidade da subárvore de layout in-line. Cada entrada na lista é uma tupla de (objeto, número de descendentes). Por exemplo, considere este DOM:

<div style="width: 0;">
  <span style="color: blue; position: relative;">Hi</span> <b>there</b>.
</div>

A propriedade width está definida como 0 para que a linha fique entre "Hi" e "there". Quando o contexto de formatação in-line para essa situação é representado como uma árvore, ele tem esta aparência:

{
  "Line box": {
    "Box <span>": {
      "Text": "Hi"
    }
  },
  "Line box": {
    "Box <b>": {
      "Text": "There"
    }
  },
  {
    "Text": "."
  }
}

A lista simples tem esta aparência:

  • (Caixa de linha, 2)
  • (Caixa <span>, 1)
  • (Texto "Oi", 0)
  • (Caixa de linha, 3)
  • (Caixa <b>, 1)
  • (Texto "lá", 0)
  • (Texto ".", 0)

Há muitos consumidores dessa estrutura de dados: APIs de acessibilidade e APIs de geometria, como getClientRects, e contenteditable. Cada um tem requisitos diferentes. Esses componentes acessam a estrutura simples de dados por meio de um cursor de conveniência.

O cursor tem APIs como MoveToNext, MoveToNextLine, CursorForChildren. Essa representação de cursor é muito eficiente para conteúdo de texto, por vários motivos:

  • A iteração na ordem de pesquisa em profundidade é muito rápida. Isso é usado com muita frequência porque é semelhante aos movimentos do acento circunflexo. Como é uma lista simples, a pesquisa em profundidade está apenas incrementando o deslocamento da matriz, fornecendo iterações rápidas e localidade de memória.
  • Ela oferece uma pesquisa que prioriza a amplitude, o que é necessário ao, por exemplo, pintar o plano de fundo de caixas de linha e inline.
  • Saber o número de descendentes agiliza a migração para o próximo irmão. Basta incrementar o deslocamento da matriz com esse número.

Árvores de propriedade

Como você sabe, o DOM é uma árvore de elementos (mais nós de texto) e o CSS pode aplicar vários estilos aos elementos.

Elas vêm principalmente em quatro variações de efeito:

  • Layout:entradas para o algoritmo de restrição de layout.
  • Paint:como pintar e fazer a varredura do elemento (mas não dos descendentes).
  • Visual:efeitos de varredura/desenho aplicados à subárvore do DOM, como transformações, filtros e recorte.
  • Rolagem:recorte e rolagem de cantos arredondados e alinhados ao eixo da subárvore contida.

Árvores de propriedades são estruturas de dados que explicam como os efeitos visuais e de rolagem se aplicam aos elementos DOM. Eles fornecem os meios para responder a perguntas como: onde, em relação à tela, há um determinado elemento DOM, considerando o tamanho e a posição do layout? E qual sequência de operações de GPU deve ser usada para aplicar efeitos visuais e de rolagem?

Os efeitos visuais e de rolagem na web são muito complicados em sua glória. Portanto, a coisa mais importante que as árvores de propriedades fazem é traduzir essa complexidade em uma única estrutura de dados que representa com precisão a estrutura e o significado, ao mesmo tempo que elimina o restante da complexidade do DOM e do CSS. Isso nos permite implementar algoritmos para composição e rolagem com muito mais confiança. Especificamente, faça o seguinte:

  • A geometria potencialmente propensa a erros e outros cálculos podem ser centralizados em um só lugar.
  • A complexidade de criar e atualizar árvores de propriedades é isolada em um estágio de pipeline de renderização.
  • É muito mais fácil e rápido enviar árvores de propriedades para linhas de execução e processos diferentes do que o estado completo do DOM, o que permite usá-las em muitos casos de uso.
  • Quanto mais casos de uso houver, mais vantagens poderemos ter com o armazenamento em cache de geometria criado na parte superior, já que eles podem reutilizar os caches uns dos outros.

O RenderingNG usa árvores de propriedades para muitas finalidades, incluindo:

  • Separação da composição da pintura e composição da linha de execução principal.
  • Determinar uma estratégia de composição / desenho ideal.
  • Medindo a geometria do IntersectionObserver.
  • Evitar trabalho com elementos fora da tela e blocos de textura da GPU.
  • Invalidar a pintura e a varredura de maneira eficiente e precisa.
  • Medir a mudança de layout e a maior exibição de conteúdo nas Core Web Vitals.

Todo documento da Web tem quatro árvores de propriedades separadas: transformar, recortar, efeito e rolagem.(*) A árvore de transformação representa transformações e rolagem CSS. Uma transformação de rolagem é representada como uma matriz de transformação 2D. A árvore de clipes representa clipes excedentes. A árvore de efeitos representa todos os outros efeitos visuais: opacidade, filtros, máscaras, modos de combinação e outros tipos de clipes, como clip-path. A árvore de rolagem representa informações sobre rolagem, por exemplo, como rola a chain em conjunto. Ela é necessária para executar a rolagem na linha de execução do compositor. Cada nó em uma árvore de propriedades representa uma rolagem ou um efeito visual aplicado por um elemento DOM. Se isso tiver vários efeitos, pode haver mais de um nó da árvore de propriedades em cada árvore para o mesmo elemento.

A topologia de cada árvore é como uma representação esparsa do DOM. Por exemplo, se houver três elementos DOM com clipes excedentes, haverá três nós da árvore de corte, e a estrutura dela seguirá a relação de bloco que contém os clipes excedentes. Há também ligações entre as árvores. Esses links indicam a hierarquia DOM relativa e, portanto, a ordem de aplicação dos nós. Por exemplo, se uma transformação em um elemento do DOM estiver abaixo de outro elemento do DOM com um filtro, a transformação será aplicada antes do filtro.

Cada elemento DOM tem um estado de árvore de propriedades, que é um conjunto de quatro tuplas (transformação, recorte, efeito, rolagem) que indica os nós da árvore de efeito, transformação e recorte ancestral mais próximos que entram em vigor nesse elemento. Isso é muito conveniente, porque, com essas informações, sabemos exatamente a lista de clipes, transformações e efeitos que se aplicam a esse elemento e em que ordem. Isso nos diz onde ele está na tela e como desenhá-lo.

Exemplo

(fonte)

<html>
  <div style="overflow: scroll; width: 100px; height: 100px;">
    <iframe style="filter: blur(3px);
      transform: rotateZ(1deg);
      width: 100px; height: 300px"
  id="one" srcdoc="iframe one"></iframe>
  </div>
  <iframe style="top:200px;
      transform: scale(1.1) translateX(200px)" id=two
      srcdoc="iframe two"></iframe>
</html>

Para o exemplo anterior, que é um pouco diferente do da introdução, veja os principais elementos das árvores de propriedades geradas:

Um exemplo dos vários elementos na árvore de propriedades.

Listas de exibição e blocos de pintura

Um item de exibição contém comandos de desenho de baixo nível (clique aqui) que podem ser rasterizados com o Skia. Os itens de exibição costumam ser simples, com apenas alguns comandos de desenho, como desenhar uma borda ou um plano de fundo. A caminhada da árvore de pintura é iterada sobre a árvore de layout e os fragmentos associados seguindo a ordem de pintura do CSS para produzir uma lista de itens de exibição.

Exemplo:

Uma caixa azul com as palavras &quot;Hello world&quot; dentro de um retângulo verde.

<div id="green" style="background:green; width:80px;">
    Hello world
</div>
<div id="blue" style="width:100px;
  height:100px; background:blue;
  position:absolute;
  top:0; left:0; z-index:-1;">
</div>

Esse HTML e CSS produziriam a seguinte lista de exibição, em que cada célula é um item de exibição:

Plano de fundo da visualização #blue em segundo plano #green em segundo plano #green texto inline
drawRect com tamanho 800 x 600 e cor branca. drawRect com tamanho 100 x 100 na posição 0,0 e cor azul. drawRect com tamanho 80 x 18 na posição 8,8 e cor verde. drawTextBlob com a posição 8,8 e o texto "Hello world".

A lista de itens de exibição é ordenada de trás para frente. No exemplo acima, o div verde está antes do div azul na ordem do DOM, mas a ordem de exibição CSS exige que o div azul de índice z negativo seja pintado antes (etapa 3) do div verde (etapa 4.1). Os itens de exibição correspondem aproximadamente às etapas atômicas da especificação da ordem de pintura do CSS. Um único elemento DOM pode resultar em vários itens de exibição. Por exemplo, a forma como #green tem um item de exibição para o plano de fundo e outro para o texto inline. Essa granularidade é importante para representar toda a complexidade da especificação da ordem de pintura CSS, como a intercalação criada pela margem negativa:

Um retângulo verde, com uma caixa cinza parcialmente sobreposta e as palavras &quot;Hello world&quot;.

<div id="green" style="background:green; width:80px;">
    Hello world
</div>
<div id="gray" style="width:35px; height:20px;
  background:gray;margin-top:-10px;"></div>

Isso produziria a seguinte lista de exibição, em que cada célula é um item de exibição:

Plano de fundo da visualização #green em segundo plano #gray em segundo plano #green texto inline
drawRect com tamanho 800 x 600 e cor branca. drawRect com tamanho 80 x 18 na posição 8,8 e cor verde. drawRect com tamanho 35 x 20 na posição 8,16 e cor cinza. drawTextBlob com a posição 8,8 e o texto "Hello world".

A lista de itens de exibição é armazenada e reutilizada por atualizações posteriores. Se um objeto de layout não tiver sido modificado durante a caminhada pela árvore de pintura, os itens de exibição dele serão copiados da lista anterior. Uma otimização adicional depende de uma propriedade da especificação da ordem de pintura do CSS: o empilhamento de contextos é pintado atomicamente. Se nenhum objeto de layout mudar em um contexto de empilhamento, a caminhada da árvore de pintura vai ignorar o contexto de empilhamento e copiar toda a sequência de itens de exibição da lista anterior.

O estado atual da árvore de propriedades é mantido durante a caminhada pela árvore de pintura e a lista de itens de exibição é agrupada em "blocos" de itens de exibição que compartilham o mesmo estado da árvore de propriedades. Isso é demonstrado neste exemplo:

Uma caixa rosa com uma caixa laranja inclinada.

<div id="scroll" style="background:pink; width:100px;
   height:100px; overflow:scroll;
   position:absolute; top:0; left:0;">
    Hello world
    <div id="orange" style="width:75px; height:200px;
      background:orange; transform:rotateZ(25deg);">
        I'm falling
    </div>
</div>

Isso produziria a seguinte lista de exibição, em que cada célula é um item de exibição:

Plano de fundo da visualização #scroll em segundo plano #scroll texto inline #orange em segundo plano #orange texto inline
drawRect com tamanho 800 x 600 e cor branca. drawRect com tamanho 100 x 100 na posição 0,0 e cor rosa. drawTextBlob com a posição 0,0 e o texto "Hello world". drawRect com tamanho 75 x 200 na posição 0,0 e cor laranja. drawTextBlob com a posição 0,0 e o texto "I'mfall"

A árvore de propriedades de transformação e os blocos de pintura seriam então (simplificados para brevidade):

Uma imagem da tabela anterior, as duas primeiras células do bloco 1, a terceira no bloco 2 e as duas últimas células no bloco 3.

A lista ordenada de blocos de pintura, que são grupos de itens de exibição e um estado de árvore de propriedades, são as entradas para a etapa de criação de camadas do pipeline de renderização. A lista inteira de blocos de tinta poderia ser mesclada em uma única camada composta e rasterizada juntas, mas isso exigiria uma varredura cara a cada vez que o usuário rolasse a tela. Uma camada composta poderia ser criada para cada bloco de pintura e rasterizada individualmente para evitar todas as novas varreduras, mas isso esgotaria rapidamente a memória da GPU. A etapa de criação de camadas precisa ter vantagens e desvantagens entre a memória GPU e a redução de custos quando as coisas mudam. Uma boa abordagem geral é mesclar blocos por padrão e não mesclar blocos de pintura com estados de árvore de propriedades que precisam mudar na linha de execução do compositor, como com a rolagem do compositor-thread ou com as animações de transformação do compositor-thread.

O exemplo anterior precisa, idealmente, produzir duas camadas compostas:

  • Uma camada composta de 800 x 600 contendo os comandos de desenho:
    1. drawRect com tamanho 800 x 600 e cor branca
    2. drawRect com tamanho 100 x 100 na posição 0,0 e cor rosa
  • Uma camada composta de 144 x 224 que contém os comandos de desenho:
    1. drawTextBlob na posição 0,0 e texto "Hello world"
    2. traduzir 0,18
    3. rotateZ(25deg)
    4. drawRect com tamanho 75 x 200 na posição 0,0 e cor laranja
    5. drawTextBlob com a posição 0,0 e o texto "I'm cachoeiras"

Se o usuário rolar a tela #scroll, a segunda camada composta vai ser movida, mas nenhuma varredura será necessária.

Para o exemplo aqui, da seção anterior sobre árvores de propriedades, há seis blocos de tinta. Junto com os estados da árvore de propriedades (transformação, recorte, efeito e rolagem), eles são:

  • Plano de fundo do documento: rolagem do documento, clipe do documento, raiz, rolagem do documento.
  • Canto horizontal, vertical e de rolagem para div (três blocos de pintura separados): rolagem do documento, clipe do documento, desfoque #one e rolagem do documento.
  • Iframe #one: #one rotação, clipe de rolagem flutuante, #one desfoque, rolagem div.
  • Iframe #two: escala #two, clipe do documento, raiz, rolagem do documento.

Frames do composto: superfícies, superfícies de renderização e blocos de textura da GPU

Conforme discutido na postagem anterior (confira um exemplo prático neste link), os processos de navegador e renderização gerenciam a varredura de conteúdo e enviam frames do compositor para o processo do Viz para a apresentação na tela. Os frames do composto são como o RenderingNG representa como unir o conteúdo rasterizado e desenhá-lo de maneira eficiente usando a GPU.

Blocos

Teoricamente, um processo de renderização ou o compositor de processos do navegador poderia fazer a varredura dos pixels em uma única textura do tamanho total da janela de visualização do renderizador e enviar essa textura ao Viz. Para exibi-la, o compositor de exibição só precisaria copiar os pixels da textura única para a posição adequada no buffer do frame (por exemplo, a tela). No entanto, se esse compositor quisesse atualizar até mesmo um único pixel, ele precisaria refazer a janela de visualização completa e enviar uma nova textura ao Viz.

Em vez disso, a janela de visualização é dividida em blocos. Um bloco de textura de GPU separado retorna cada bloco com os pixels rasterizados em parte da janela de visualização. O renderizador pode atualizar blocos individuais ou até mesmo mudar a posição dos blocos existentes na tela. Por exemplo, ao rolar um site, a posição dos blocos existentes muda para cima e, ocasionalmente, um novo bloco precisaria ser rasterizado para conteúdo mais abaixo na página.

Quatro blocos.

A imagem acima mostra um dia ensolarado, com quatro blocos. Quando ocorre uma rolagem, um quinto bloco começa a aparecer. Um dos blocos tem apenas uma cor (azul-celeste), e um vídeo e um iframe aparecem na parte de cima. O que nos leva ao próximo tópico.

Quadriculados e superfícies

Os blocos de textura da GPU são um tipo especial de quad, que é apenas um nome chique para uma categoria de textura ou outra. Um quad identifica a textura de entrada e indica como transformar e aplicar efeitos visuais a ela. Por exemplo, blocos de conteúdo regular têm uma transformação que indica sua posição x, y na grade de blocos.

Blocos de textura da GPU.

Esses blocos rasterizados são agrupados em uma passagem de renderização, que é uma lista de quads. A passagem de renderização não contém informações de pixel. Em vez disso, ela tem instruções sobre onde e como desenhar cada quadrante para produzir a saída de pixel desejada. Há um quadro de desenho para cada bloco de textura de GPU. O compositor de exibição só precisa iterar a lista de quads, desenhando cada um com os efeitos visuais especificados para produzir a saída de pixel desejada para a passagem de renderização. A composição de quads de desenho para uma passagem de renderização pode ser feita de maneira eficiente na GPU, porque os efeitos visuais permitidos são cuidadosamente escolhidos como os que são mapeados diretamente para os recursos da GPU.

Há outros tipos de quads de desenho além dos blocos rasterizados. Por exemplo, existem quadrângulos de desenho de cor sólida que não são baseados em uma textura ou quadrângulos de desenho de textura para texturas que não sejam blocos, como vídeos ou telas.

Também é possível que um frame de compositor incorpore outro frame de compositor. Por exemplo, o compositor do navegador produz um frame com a interface do navegador e um retângulo vazio em que o conteúdo do compositor de renderização será incorporado. Outro exemplo são iframes com isolamento de sites. Essa incorporação é realizada por plataformas.

Quando um compositor envia um frame, ele é acompanhado por um identificador, chamado de ID de superfície, permitindo que outros frames do compositor o incorporem por referência. O frame do compositor mais recente enviado com um ID de superfície específico é armazenado pelo Viz. Outro frame do compositor pode se referir a ele mais tarde por um quadrático de desenho de superfície. Portanto, o Viz sabe o que desenhar. Observe que os quadrângulos de desenho de superfície contêm apenas IDs de superfície, e não texturas.

Transmissões de renderização intermediárias

Alguns efeitos visuais, como muitos filtros ou modos de combinação avançados, exigem que dois ou mais quadrângulos sejam desenhados em uma textura intermediária. Em seguida, a textura intermediária é desenhada em um buffer de destino na GPU (ou possivelmente em outra textura intermediária), aplicando o efeito visual ao mesmo tempo. Para permitir isso, um frame do compositor contém uma lista de passes de renderização. Há sempre uma passagem de renderização raiz, que é desenhada por último e cujo destino corresponde ao buffer do frame e pode haver mais.

A possibilidade de várias passagens de renderização explica o nome "passagem de renderização". Cada passagem precisa ser executada sequencialmente na GPU, em vários "cartões", enquanto uma única passagem pode ser concluída em um único cálculo de GPU massivamente paralelo.

Agregação

Vários frames do compositor são enviados ao Viz e precisam ser desenhados na tela juntos. Isso é feito por uma fase de agregação que os converte em um único frame compositor agregado. A agregação substitui os quads de desenho da superfície pelos frames do compositor que eles especificam. É também uma oportunidade para otimizar texturas ou conteúdos intermediários desnecessários fora da tela. Por exemplo, em muitos casos, o frame do compositor para um iframe isolado de site não precisa da própria textura intermediária e pode ser renderizado diretamente no buffer de frames usando quads de desenho adequados. A fase de agregação descobre essas otimizações e as aplica com base em conhecimento global que não pode ser acessado por compositores de renderização individuais.

Exemplo

Confira os frames do compositor reais que representam o exemplo do início desta postagem.

  • foo.com/index.html plataforma: id=0
    • Passe de renderização 0: desenho na saída.
      • Quadrado de desenho da passagem de renderização: desenhe com desfoque de 3 px e recorte na passagem de renderização 0.
        • Etapa de renderização 1:
          • Desenhe quadráticos para o conteúdo de blocos do iframe #one, com posições x e y para cada um.
      • Quadrado de desenho da superfície: com ID 2, desenhado com escala e transformação de translação.
  • Plataforma da interface do navegador: ID=1
    • Passe de renderização 0: desenho na saída.
      • Desenhe quads para a interface do navegador (lado a lado)
  • Plataforma bar.com/index.html: ID=2
    • Passe de renderização 0: desenho na saída.
      • Desenhe quadráticos para o conteúdo do iframe #two, com as posições x e y para cada um.

Conclusão

Agradecemos por ler. Em conjunto com as duas postagens anteriores, isso conclui a visão geral do RenderingNG. A seguir, vamos abordar os desafios e a tecnologia de muitos dos subcomponentes do pipeline de renderização, do início ao fim. Eles estarão disponíveis em breve.

Ilustrações de Una Kravets.