Detalhes de renderização de BlinkNG

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

Blink refere-se à implementação do Chromium da plataforma da Web e abrange todas as fases de renderização antes da composição, culminando na confirmação do compositor. Leia mais sobre a arquitetura de renderização por piscar em um artigo anterior desta série.

O Blink começou a vida como uma ramificação do WebKit, que é uma ramificação do KHTML, que data de 1998. Ele contém alguns dos códigos mais antigos e críticos do Chromium e, em 2014, já estava mostrando sua idade. Nesse ano, embarcamos em um conjunto de projetos ambiciosos sob o que chamamos de BlinkNG, com o objetivo de abordar deficiências persistentes na organização e estrutura do código Blink. Este artigo explorará o BlinkNG e os projetos que o compõem: por que fizemos isso, o que ele realizou, os princípios orientadores que moldaram o design e as oportunidades para melhorias futuras que eles proporcionam.

O pipeline de renderização antes e depois do BlinkNG.

Renderização pré-NG

O pipeline de renderização no Blink sempre foi dividido conceitualmente em fases (style, layout, paint e assim por diante), mas as barreiras de abstração estavam vazando. Em termos gerais, os dados associados à renderização consistiam em objetos mutáveis e de longa duração. Esses objetos podiam ser (e eram) modificados a qualquer momento e eram frequentemente reciclados e reutilizados por sucessivas atualizações de renderização. Era impossível responder de forma confiável a perguntas simples como:

  • A saída de estilo, layout ou pintura precisa ser atualizada?
  • Quando esses dados receberão seu "final" valor?
  • Quando é possível modificar esses dados?
  • Quando esse objeto será excluído?

Há muitos exemplos disso, incluindo:

Style geraria ComputedStyles com base em folhas de estilo. mas ComputedStyle não era imutável; em alguns casos, ele seria modificado pelos estágios posteriores do pipeline.

O Style geraria uma árvore de LayoutObject, e o layout anotaria esses objetos com informações de tamanho e posicionamento. Em alguns casos, o layout até modificaria a estrutura de árvore. Não havia uma separação clara entre as entradas e saídas do layout.

O estilo gerava estruturas de dados complementares que determinavam o curso da composição, e essas estruturas eram modificadas em cada fase após o estilo.

Em um nível inferior, a renderização de tipos de dados consiste em árvores especializadas (por exemplo, a árvore DOM, a árvore de estilo, a árvore de layout e a árvore de propriedades de pintura); e renderização são implementadas como caminhadas recursivas em árvore. Idealmente, uma caminhada em árvore deve ser contida: ao processar um determinado nó de árvore, não devemos acessar nenhuma informação fora da subárvore com raízes nesse nó. Isso nunca acontecia antes da renderizaçãoNG. caminha as informações acessadas com frequência dos ancestrais do nó que está sendo processado. Isso tornava o sistema muito frágil e sujeito a erros. Também era impossível começar uma caminhada pela árvore de qualquer lugar além da raiz da árvore.

Por fim, havia muitos acessos ao pipeline de renderização espalhados por todo o código: layouts forçados acionados por JavaScript, atualizações parciais acionadas durante o carregamento de documentos, atualizações forçadas para se preparar para a segmentação de eventos, atualizações programadas solicitadas pelo sistema de exibição e APIs especializadas expostas apenas para testar códigos. Havia até alguns caminhos recursivos e reentrantes no pipeline de renderização (ou seja, pulando para o início de um estágio a partir do meio de outro). Cada um desses on-ramps teve seu próprio comportamento idiossincrático e, em alguns casos, a saída da renderização dependeria da maneira como a atualização de renderização foi acionada.

O que mudamos

O BlinkNG é composto por muitos subprojetos, grandes e pequenos, todos com o objetivo comum de eliminar os déficits arquitetônicos descritos anteriormente. Esses projetos compartilham alguns princípios orientadores projetados para tornar o pipeline de renderização mais parecido com um pipeline real:

  • Ponto de entrada uniforme: é preciso sempre inserir o pipeline no início.
  • Estágios funcionais: cada estágio precisa ter entradas e saídas bem definidas, e o comportamento dele precisa ser funcional, ou seja, determinístico e repetível, e as saídas precisam depender apenas das entradas definidas.
  • Entradas constantes: as entradas de qualquer cenário precisam ser efetivamente constantes enquanto ele estiver em execução.
  • Saídas imutáveis: quando um cenário é concluído, as saídas não podem ser alteradas durante o restante da atualização da renderização.
  • Consistência do ponto de verificação: ao final de cada etapa, os dados de renderização produzidos até o momento precisam estar em um estado de consistência própria.
  • Eliminação de duplicação de trabalho: calcule cada coisa apenas uma vez.

Uma lista completa de subprojetos do BlinkNG deixaria uma leitura tediosa, mas a seguir temos algumas consequências específicas.

O ciclo de vida do documento

A classe DocumentLifecycle acompanha nosso progresso por meio do pipeline de renderização. Isso nos permite fazer verificações básicas que aplicam as invariantes listadas anteriormente, como:

  • Se estivermos modificando uma propriedade do ComputedStyle, o ciclo de vida do documento precisará ser kInStyleRecalc.
  • Se o estado do DocumentLifecycle for kStyleClean ou posterior, NeedsStyleRecalc() precisará retornar false para qualquer nó anexado.
  • Ao entrar na fase do ciclo de vida da pintura, o estado dele precisa ser kPrePaintClean.

Ao implementar o BlinkNG, eliminamos sistematicamente os caminhos de código que violavam essas invariantes e distribuímos muito mais declarações em todo o código para garantir que não haja regressões.

Se você já esteve procurando um código de renderização de baixo nível, pode se perguntar: "Como eu cheguei até aqui?" Como mencionado anteriormente, há vários pontos de entrada no pipeline de renderização. Antes, isso incluía caminhos de chamadas recursivas e reentrantes e locais em que entramos no pipeline em uma fase intermediária, em vez de começar do início. Durante o BlinkNG, analisamos esses caminhos de chamadas e determinamos que todos eram redutíveis a dois cenários básicos:

  • Todos os dados de renderização precisam ser atualizados. Por exemplo, ao gerar novos pixels para exibição ou ao fazer um teste de hit para a segmentação de eventos.
  • Precisamos de um valor atualizado para uma consulta específica que possa ser respondida sem atualizar todos os dados de renderização. Isso inclui a maioria das consultas JavaScript, como node.offsetTop.

Agora, existem apenas dois pontos de entrada no pipeline de renderização, que correspondem a esses dois cenários. Os caminhos de código reentrantes foram removidos ou refatorados, e não é mais possível entrar no pipeline a partir de uma fase intermediária. Isso eliminou muito mistério sobre exatamente quando e como as atualizações de renderização acontecem, tornando muito mais fácil raciocinar sobre o comportamento do sistema.

Estilo, layout e pré-pintura do pipeline

Coletivamente, as fases de renderização antes da paint são responsáveis pelo seguinte:

  • Execução do algoritmo de cascata de estilo para calcular as propriedades de estilo finais dos nós do DOM.
  • Gerar a árvore de layout que representa a hierarquia de caixas do documento.
  • Determinando as informações de tamanho e posição para todas as caixas.
  • Arredondar ou ajustar a geometria de subpixels a limites de pixels inteiros para pintura.
  • determinar as propriedades das camadas compostas (transformação afins, filtros, opacidade ou qualquer outra coisa que possa ser acelerada por GPU);
  • Determinar qual conteúdo mudou desde a fase anterior de pintura e que precisa ser pintado ou repintado (invalidação de pintura).

Essa lista não mudou, mas antes do BlinkNG grande parte desse trabalho era feito de forma ad hoc, espalhada por várias fases de renderização, com muitas funcionalidades duplicadas e ineficiências integradas. Por exemplo, a fase style sempre foi a principal responsável pelo cálculo das propriedades de estilo finais dos nós. No entanto, em alguns casos especiais, os valores das propriedades de estilo finais só são determinados após a conclusão da fase style. Não havia um ponto formal ou aplicável no processo de renderização em que pudéssemos dizer com certeza que as informações de estilo estavam completas e imutáveis.

Outro bom exemplo de problema pré-BlinkNG é a invalidação de pintura. Antes, a invalidação de pintura era espalhada em todas as fases de renderização que antecederam a pintura. Ao modificar o código de estilo ou layout, era difícil saber quais mudanças na lógica de invalidação de pintura eram necessárias, e foi fácil cometer um erro que levava a bugs de sub ou invalidação excessiva. Você pode ler mais sobre as complexidades do antigo sistema de invalidação de pintura no artigo desta série dedicada ao LayoutNG.

Ajustar a geometria do layout de subpixels a limites de pixels inteiros para pintura é um exemplo de quando tínhamos várias implementações da mesma funcionalidade e fizemos muito trabalho redundante. Havia um caminho de código de ajuste de pixels usado pelo sistema de pintura e um caminho de código totalmente separado usado sempre que precisávamos de um cálculo único e imediato das coordenadas alinhadas aos pixels fora do código de pintura. Não é preciso dizer que cada implementação teve os próprios bugs, e os resultados nem sempre correspondem. Como não havia armazenamento em cache dessas informações, o sistema às vezes executava exatamente o mesmo cálculo repetidamente, o que prejudicava o desempenho.

Aqui estão alguns projetos significativos que eliminaram os déficits arquitetônicos das fases de renderização antes da pintura.

Project Squad: traçando a fase de estilo

Esse projeto abordou dois principais déficits na fase de estilo que a impediam de serem plenamente canalizadas:

Há duas saídas principais para a fase de estilo: ComputedStyle, que contém o resultado da execução do algoritmo em cascata CSS na árvore do DOM. e uma árvore de LayoutObjects, que estabelece a ordem das operações para a fase de layout. Conceitualmente, a execução do algoritmo em cascata precisa acontecer estritamente antes de gerar a árvore de layout. mas anteriormente, essas duas operações eram intercaladas. O Project Squad conseguiu dividir as duas em fases sequenciais e distintas.

Anteriormente, ComputedStyle nem sempre recebia o valor final durante o recálculo de estilo. havia algumas situações em que ComputedStyle era atualizado durante uma fase posterior do pipeline. O Project Squad refatorou esses caminhos de código para que o ComputedStyle nunca seja modificado após a fase de estilo.

LayoutNG: veiculação da fase de layout

Esse projeto monumental, um dos pilares do RenderingNG, foi uma reescrita completa da fase de renderização de layout. Não faremos justiça a todo o projeto aqui, mas há alguns aspectos notáveis para o projeto geral do BlinkNG:

  • Anteriormente, a fase de layout recebia uma árvore de LayoutObject criada pela fase de estilo e a anotava informações de tamanho e posição. Portanto, não houve uma separação clara das entradas e das saídas. O LayoutNG introduziu a árvore de fragmentos, que é a principal saída somente leitura do layout e serve como entrada principal para as fases de renderização subsequentes.
  • O LayoutNG trouxe a propriedade de contenção para o layout: ao calcular o tamanho e a posição de uma determinada LayoutObject, não consideramos mais fora a subárvore com raízes nesse objeto. Todas as informações necessárias para atualizar o layout de um determinado objeto são calculadas previamente e fornecidas como uma entrada somente leitura para o algoritmo.
  • Anteriormente, havia casos extremos em que o algoritmo de layout não era estritamente funcional: o resultado do algoritmo dependia da atualização anterior do layout mais recente. O LayoutNG eliminou esses casos.

A fase de pré-pintura

Antes, não havia uma fase formal de renderização pré-pintura, apenas um pacote de operações pós-layout. A fase de pré-pintura surgiu do reconhecimento de que havia algumas funções relacionadas que poderiam ser melhor implementadas como uma travessia sistemática da árvore de layout após a conclusão do layout. o mais importante:

  • Emissão de invalidações de pintura: é muito difícil fazer a invalidação de pintura corretamente durante o curso do layout, quando temos informações incompletas. É muito mais fácil acertar e pode ser muito eficiente se for dividido em dois processos distintos: durante o estilo e o layout, o conteúdo pode ser marcado com uma sinalização booleana simples, porque "possivelmente precisa de invalidação de pintura". Durante a pré-pintura da árvore, verificamos essas sinalizações e emitimos invalidações conforme necessário.
  • Geração de árvores de propriedades de pintura: um processo descrito em mais detalhes posteriormente.
  • Computação e gravação de locais de pintura com pixels de pixels: os resultados registrados podem ser usados pela fase de pintura e também por qualquer código downstream que precise deles, sem qualquer computação redundante.

Árvores de propriedade: geometria consistente

As árvores de propriedades foram introduzidas no início do RenderingNG para lidar com a complexidade da rolagem, que na Web tem uma estrutura diferente de todos os outros tipos de efeitos visuais. Antes das árvores de propriedades, o compositor do Chromium usava uma única "camada" hierarquia para representar a relação geométrica do conteúdo composto, mas que rapidamente se desassociou à medida que todas as complexidades de atributos como posição:fixo se tornaram aparentes. A hierarquia de camadas cresceu mais ponteiros não locais indicando a "rolagem pai" ou "clip parent" de uma camada e, em pouco tempo, era muito difícil entender o código.

As árvores de propriedades corrigiram o problema representando os aspectos de rolagem e de corte do conteúdo separadamente de todos os outros efeitos visuais. Isso permitiu modelar corretamente a verdadeira estrutura visual e de rolagem dos sites. Em seguida, "todos" tivemos que implementar algoritmos sobre as árvores de propriedades, como a transformação do espaço de tela das camadas compostas, ou determinar quais camadas rolaram e quais não rolaram.

Na verdade, logo percebemos que havia muitos outros lugares no código em que questões geométricas semelhantes eram levantadas. A postagem com as principais estruturas de dados tem uma lista mais completa. Vários deles tinham implementações duplicadas da mesma coisa que o código do compositor estava fazendo. e todas tinham um subconjunto diferente de bugs, e nenhuma delas modelou corretamente a verdadeira estrutura do site. A solução, então, ficou clara: centralizar todos os algoritmos de geometria em um só lugar e refatorar todo o código para usá-lo.

Todos esses algoritmos dependem das árvores de propriedades. Por isso, as árvores de propriedades são uma estrutura de dados chave (ou seja, usada em todo o pipeline) do RenderingNG. Portanto, para alcançar esse objetivo de código de geometria centralizado, precisávamos introduzir o conceito de árvores de propriedades muito antes no pipeline (na pré-pintura) e alterar todas as APIs que agora dependiam delas para exigir a execução da pré-pintura antes de serem executadas.

Essa história é mais um aspecto do padrão de refatoração do BlinkNG: identificar cálculos importantes, refatorar para evitar duplicações e criar estágios de pipeline bem definidos que criam as estruturas de dados que os alimentam. Calculamos as árvores de propriedades exatamente no ponto em que todas as informações necessárias estão disponíveis. e garantimos que as árvores de propriedades não possam ser alteradas enquanto os estágios de renderização posteriores estiverem em execução.

Composto após a tinta: tinta para pipeline e composição

Camadas é o processo de descobrir qual conteúdo DOM entra em sua própria camada composta (que, por sua vez, representa uma textura da GPU). Antes do RenderingNG, as camadas são executadas antes da pintura, e não depois. Veja o pipeline atual aqui (link em inglês). Observe a mudança de ordem. Primeiro, decidimos quais partes do DOM entram em qual camada composta e só depois desenhamos listas de exibição para essas texturas. Naturalmente, as decisões dependiam de fatores como quais elementos DOM eram animados ou rolados, ou tinham transformações 3D, e quais elementos eram pintados sobre os quais.

Isso causou grandes problemas, porque exigia mais ou menos que houvesse dependências circulares no código, o que é um grande problema para um pipeline de renderização. Vamos conferir um exemplo. Suponha que precisamos invalidar o paint, o que significa que precisamos desenhar a lista de exibição de novo e fazer a varredura novamente. A necessidade de invalidar pode vir de uma alteração no DOM ou de um estilo ou layout alterado. No entanto, queremos invalidar somente as partes que realmente foram alteradas. Isso significava descobrir quais camadas compostas foram afetadas e, em seguida, invalidar parte ou todas as listas de exibição para essas camadas.

Isso significa que a invalidação dependia de DOM, estilo, layout e decisões anteriores de camadas (passado: significado do frame renderizado anteriormente). Mas a criação de camadas atual depende de todas essas coisas também. E como não tínhamos duas cópias de todos os dados de camadas, era difícil distinguir entre as decisões anteriores e futuras. Então acabamos com muitos códigos com raciocínio circular. Isso às vezes levava a um código ilógico ou incorreto, ou até mesmo falhas ou problemas de segurança, quando não tínhamos muito cuidado.

Para lidar com essa situação, já introduzimos o conceito de objeto DisableCompositingQueryAsserts. Na maioria das vezes, se o código tentasse consultar decisões anteriores de camadas, ele causava uma falha na declaração e causava uma falha no navegador se estivesse no modo de depuração. Isso nos ajudou a evitar a introdução de novos bugs. Em cada caso em que o código precisaria legitimamente consultar decisões de camadas anteriores, colocamos o código para permitir isso alocando um objeto DisableCompositingQueryAsserts.

Nosso plano era eliminar todos os objetos DisableCompositingQueryAssert de sites de chamada e, em seguida, declarar o código seguro e correto. No entanto, descobrimos que era impossível remover algumas chamadas, desde que a criação de camadas acontecesse antes da pintura. Finalmente, conseguimos removê-lo somente recentemente. Este foi o primeiro motivo descoberto pelo projeto Composite After Paint. O que aprendemos foi que, mesmo que você tenha uma fase de pipeline bem definida para uma operação, se ela estiver no lugar errado no pipeline, você acabará ficando travada.

O segundo motivo para o projeto Composite After Paint foi o bug de Composição fundamental. Uma forma de indicar esse bug é que os elementos DOM não são uma boa representação individual de um esquema de criação de camadas eficiente ou completo para conteúdos de páginas da Web. E, como a composição ocorre antes da pintura, ela dependia mais ou menos inerentemente de elementos DOM, e não de listas de exibição ou árvores de propriedades. Isso é muito semelhante à razão pela qual apresentamos árvores de propriedades e, assim como acontece com as árvores de propriedades, a solução será aplicada diretamente se você descobrir a fase certa do pipeline, executá-la no momento certo e fornecer as principais estruturas de dados corretas. Assim como acontece com as árvores de propriedades, essa foi uma boa oportunidade para garantir que, quando a fase de pintura for concluída, a saída será imutável para todas as fases subsequentes do pipeline.

Vantagens

Como você viu, um pipeline de renderização bem definido gera enormes benefícios a longo prazo. Existem mais do que você imagina:

  • Confiabilidade muito melhorada: esta é bem direta. Um código mais limpo com interfaces bem definidas e compreensíveis é mais fácil de entender, escrever e testar. Isso o torna mais confiável. Ela também torna o código mais seguro e estável, com menos falhas e menos bugs de uso após a liberação.
  • Maior cobertura de testes: no BlinkNG, adicionamos vários testes novos ao nosso pacote. Isso inclui testes de unidade que oferecem verificação focada nos componentes internos. testes de regressão que nos impedem de reintroduzir bugs antigos que corrigimos (muitos!); e muitas adições ao público, mantido coletivamente, conjunto de testes de plataformas da Web, que todos os navegadores usam para medir a conformidade com os padrões da Web.
  • Mais fácil de estender: se um sistema for dividido em componentes claros, não será necessário entender outros componentes em nenhum nível de detalhes para progredir em relação ao atual. Isso facilita que todos agregam valor ao código de renderização sem precisar ser especialistas, além de facilitar a compreensão do comportamento de todo o sistema.
  • Desempenho: otimizar algoritmos escritos em código espaguete já é difícil, mas é quase impossível conseguir coisas ainda maiores, como animações e rolagem de linhas de execução universal ou processos e linhas de execução para isolamento de sites sem esse pipeline. O paralelismo pode nos ajudar a melhorar muito o desempenho, mas também é extremamente complicado.
  • Rendimento e contenção: o BlinkNG oferece vários recursos novos que operam o pipeline de maneiras novas e inovadoras. Por exemplo, e se quiséssemos executar o pipeline de renderização apenas até um orçamento expirar? Ou pular a renderização para subárvores conhecidas por não serem relevantes para o usuário no momento? É isso que a propriedade CSS de visibilidade do conteúdo permite. E fazer com que o estilo de um componente dependa do layout? São as consultas de contêiner.

Estudo de caso: consultas em contêineres

As consultas de contêiner são um recurso muito esperado da plataforma da Web. Ele tem sido o recurso mais pedido pelos desenvolvedores de CSS há anos. Se ela é tão boa, por que ainda não existe? O motivo é que uma implementação de consultas de contêiner requer uma compreensão e controle muito cuidadosos da relação entre o código de estilo e de layout. Vamos analisar melhor.

Uma consulta de contêiner permite que os estilos que se aplicam a um elemento dependam do tamanho disposto de um ancestral. Como o tamanho do layout é calculado durante o layout, precisamos executar o recálculo de estilo após o layout. mas o recálculo de estilo é executado antes do layout. Esse paradoxo da galinha-e-ovo é toda a razão pela qual não poderíamos implementar consultas de contêiner antes do BlinkNG.

Como podemos resolver isso? Isso não é uma dependência do pipeline reverso, ou seja, o mesmo problema que projetos como o Composite After Paint resolvem? Pior ainda, e se os novos estilos mudarem o tamanho do ancestral? Isso não levaria a um loop infinito?

Em princípio, a dependência circular pode ser resolvida usando a propriedade CSS "contains", que permite que a renderização fora de um elemento não dependa da renderização dentro da subárvore desse elemento. Isso significa que os novos estilos aplicados por um contêiner não podem afetar o tamanho dele, porque as consultas dele exigem contenção.

Mas, na verdade, isso não foi suficiente, e foi necessário introduzir um tipo mais fraco de contenção do que apenas a contenção de tamanho. Isso ocorre porque é comum querer que um contêiner de consultas de contêiner seja capaz de redimensionar em apenas uma direção (geralmente bloco) com base nas dimensões inline. Com isso, o conceito de contenção de tamanho inline foi adicionado. No entanto, como é possível ver em uma anotação muito longa nessa seção, não estava claro por muito tempo se a contenção de tamanho em linha era possível.

Uma coisa é descrever a contenção na linguagem de especificações abstratas e outra é implementá-la corretamente. Lembre-se de que um dos objetivos do BlinkNG era trazer o princípio de contenção para as caminhadas em árvore, que constituem a principal lógica de renderização: ao atravessar uma subárvore, nenhuma informação deve ser necessária de fora da subárvore. Como acontece (bem, não foi exatamente um acidente), é muito mais limpo e fácil de implementar a contenção do CSS se o código de renderização cumprir o princípio da contenção.

Futuro: composição fora da linha de execução principal... e além!

O pipeline de renderização mostrado aqui está um pouco à frente da implementação atual do RenderingNG. Ela mostra a criação de camadas como estando fora da linha de execução principal, mas ela ainda está na linha de execução principal. No entanto, é apenas uma questão de tempo até que isso seja feito, agora que a fase Composto após a tinta foi enviada e a criação de camadas ocorre após a pintura.

Para entender por que isso é importante e aonde mais isso pode nos levar, precisamos considerar a arquitetura do mecanismo de renderização de um ponto de vista um pouco superior. Um dos obstáculos mais duradouros para melhorar o desempenho do Chromium é o simples fato de que a linha de execução principal do renderizador lida com a lógica principal do aplicativo (ou seja, a execução do script) e a maior parte da renderização. Como resultado, a linha de execução principal fica frequentemente saturada de trabalho, e o congestionamento dela costuma ser o afunilamento de todo o navegador.

A boa notícia é que não precisa ser assim. Esse aspecto da arquitetura do Chromium remonta aos dias do KHTML, quando a execução de linha de execução única era o modelo de programação dominante. Quando os processadores multinúcleo se tornaram comuns em dispositivos voltados para consumidores, a suposição de uma linha de execução única estava totalmente incorporada ao Blink (anteriormente WebKit). Queríamos introduzir mais linhas de execução no mecanismo de renderização há muito tempo, mas isso era simplesmente impossível no sistema antigo. Um dos principais objetivos da renderização de NG era nos aprofundarmos nesse buraco e possibilitar a transferência do trabalho de renderização, parcial ou integralmente, para outra linha de execução (ou linhas de execução).

Agora que o BlinkNG está se aproximando, já estamos começando a explorar essa área. A Non-Blocking Commit é um primeiro esforço para alterar o modelo de linha de execução do renderizador. Confirmação do compositor (ou apenas commit) é uma etapa de sincronização entre a linha de execução principal e a linha de execução do compositor. Durante a confirmação, fazemos cópias dos dados de renderização produzidas na linha de execução principal para serem usadas pelo código de composição downstream executado na linha de execução do compositor. Enquanto essa sincronização ocorre, a execução da linha de execução principal é interrompida enquanto a cópia do código é executada no thread do compositor. Isso é feito para garantir que a linha de execução principal não modifique os dados de renderização enquanto a linha de execução do compositor os copia.

A confirmação sem bloqueio eliminará a necessidade da linha de execução principal parar e aguardar o término do estágio de confirmação. A linha de execução principal continuará funcionando enquanto a confirmação é executada simultaneamente na linha de execução do compositor. O efeito líquido da confirmação sem bloqueio será uma redução do tempo dedicado à renderização do trabalho na linha de execução principal, o que vai diminuir o congestionamento nela e melhorar o desempenho. Até o momento (março de 2022), temos um protótipo funcional do compromisso sem bloqueio e estamos nos preparando para fazer uma análise detalhada do impacto dele na performance.

Espera nas asas está a composição fora da linha de execução principal, com o objetivo de fazer com que o mecanismo de renderização corresponda à ilustração movendo a layerização da linha de execução principal para uma linha de execução de worker. Assim como a confirmação sem bloqueio, ela vai reduzir o congestionamento na linha de execução principal, diminuindo a carga de trabalho de renderização. Um projeto como esse nunca teria sido possível sem as melhorias arquitetônicas do Composite After Paint.

E há mais projetos no pipeline (trocadilho intencional). Finalmente temos uma base que possibilita testar o trabalho de renderização de redistribuição, e estamos muito animados para ver o que é possível!