Até recentemente, eu trabalhava na equipe Yandex.Browser e fiz uma apresentação na conferência YaTalks após essa experiência. O relatório era sobre o que o navegador tem sob o capô e como suas páginas se transformam em pixels na tela. Frontend mínimo, apenas o interior do navegador, apenas hardcore.

- Olá pessoal, meu nome é Kostya. Surpreendentemente, agora trabalho na equipe da rede virtual Yandex.Cloud. Antes disso, eu trabalhava na equipe do navegador por mais de cinco anos, então hoje vou falar sobre coisas que são comuns a nós e a você.
Como você pode imaginar, eu não entendo muito bem no front end. Se você falar comigo sobre o React ou qualquer outra coisa assim, provavelmente não vou entender você. Mas fiz muitas coisas no navegador: decodificação de vídeo, lógica de negócios. Inclusive eu passei muito tempo fazendo coisas diferentes na renderização do navegador. Hoje teremos um programa educacional, digamos, sobre o dispositivo interno do navegador. Vou tentar passar pelas coisas mais interessantes que fizemos no Yandex.Browser ou no Google no Chromium.

Se falarmos sobre renderização no navegador, isso é algo muito complicado, que consiste em um grande número de componentes. Primeiro de tudo, você deve baixar os recursos para mostrá-los. Então você precisa analisá-los, criar uma árvore DOM, estilos, layouts etc. Os três primeiros pontos provavelmente lhe são familiares. Meu relatório se concentrará mais nas outras três partes: Pintura, Rasterização e Composição - o que acontece quando você já escreveu o layout. Somente em palavras pode parecer que é a mesma coisa - na verdade, esses são componentes completamente diferentes.


Vamos começar com Pintura e composição. O que é isso tudo? Vamos voltar muitos, muitos anos atrás, quando a web não era tão complexa como agora, quando não havia todos os tipos de 3D, animações CSS e outras coisas. Como então desenhou o navegador? Imagine que você tem sua página, nela alguns elementos maravilhosos, imagens etc. O navegador pintou tudo isso em uma textura pesada, em um grande bloco de memória. Ele sabia como desenhar cada elemento e, se tivermos alguma alteração, elas são destacadas em amarelo, algo como o seguinte aconteceu.
O navegador os agregou em áreas indicadas aqui em azul. Houve uma mudança nessa área, vamos redesenhar. Tudo nesta área foi simplesmente redesenhado e copiado para a textura.
Funcionou para si. Então pessoas inteligentes criaram animação em CSS 3D, outras coisas. Poderíamos ter muitas representações em lugares diferentes. Se girarmos um botão giratório, redesenhar toda a torta de elementos que estão localizados embaixo dele não será muito eficaz.

Outras pessoas inteligentes decidiram refazer tudo um pouco. Nós temos algum tipo de árvore DOM, nós a construímos na memória. Estes são mais objetos, eles são comparados com o layout que você escreveu.

E então a mágica começa a acontecer. O navegador converte a árvore DOM inteira em uma árvore Render Object. Essa coisa sabe como desenhar cada elemento DOM específico. Ou seja, ela sabe o que precisa ser feito para que algo apareça na tela em vez da sua árvore ou elemento P.

A próxima árvore é a árvore de camadas. O que é isso Cada um dos nossos elementos pode ser associado a qualquer camada e uma camada de renderização pode conter vários objetos ao mesmo tempo. Por que isso é feito? Isso é muito bem mostrado aqui. Geramos um conjunto de camadas, cada uma delas com certos elementos. Agora, suponha que algo mude em uma das camadas - uma animação ocorre, um elemento de carimbo voa. Em seguida, redesenhamos apenas uma camada e o restante, por exemplo, o plano de fundo, permanece inalterado. Em seguida, basta colá-los em compósitos e, na saída, obtemos a imagem final - o quadro atual da animação.

Há um conjunto fixo de razões para a criação de novas camadas. Por exemplo, uma camada é criada para renderizar animação em CSS 3D, uma tela, um elemento de vídeo nela - em geral, algo relacionado à animação pesada, adequada para redesenhar separadamente de todos os outros conteúdos.

Mas essa abordagem tem vários problemas. Agora você precisa ligar o combustível. Pense no que será retratado aqui? Existem apenas dois elementos.

Aqui. Embora, ao que parece, os elementos estejam localizados um a um. Por que a tela voa? Em nosso país, eles são arranjados sequencialmente; eu não estabeleci nenhuma ordem.

Vamos complicar. Aqui temos outra div, como esta.

Em geral, o comportamento esperado. Temos uma div em cima da div, mas a tela, por algum motivo, está em cima. A magia! Ok, vamos complicar este exemplo novamente.

Exatamente uma linha mudou, eu adicionei transform.

E agora temos tudo localizado corretamente - pelo menos em termos de tela e div. Mas essa div ainda está localizada abaixo, embora tenha sido o próximo elemento em nosso layout.
Este é o chamado bug de composição fundamental. Se você procurar o rastreador Chromium, verá vários bugs que estão vinculados a um antigo. Ele é chamado assim.

Então o que aconteceu? Como eu disse, alguns elementos são renderizados na Camada de renderização, outros não. Alguns são desenhados juntos com outros. Aqui aconteceu o seguinte: os elementos div permanecem na mesma camada que o plano de fundo. O Canvas trava em uma camada separada. E a ordenação z é realizada apenas entre as camadas. Devido ao fato de termos um background e div em uma camada e canvas em outra, obtemos um erro: a tela sobrepõe a div.
Mas assim que movemos esse elemento div para uma camada separada e ele começa a usar a ordem z normalmente, ele também começa a entender quem está por trás de quem. E aqui tudo é tornado "normal".

E uma das últimas iniciativas que vem desenvolvendo há vários anos é a chamada Slimming Paint, que deve corrigi-la. Seu significado é que precisamos separar a Pintura da encenação, ou seja, entender o que precisa ser feito para desenhar esses elementos, de como então compô-los. Se temos um layout tão simples, ele se transforma em algo assim. Há uma lista simples de comandos que você precisa executar para obter o conteúdo da página. E se voltarmos a este exemplo, será algo parecido com isto.


Ou seja, dissemos: aqui está o Paint para você, aqui está o conteúdo para você - por favor, me dê uma coisa. Ele fornece uma lista para renderização, que é direcionada ao Compositor, e o Compositor entende como dividir todo o conteúdo em camadas para que eles normalmente estejam localizados um em relação ao outro.
E se você não percebeu, esta é uma captura de tela do Chrome. Eu fiz isso cerca de duas semanas atrás, ou seja, o bug ainda está vivo. O projeto ainda não está concluído, está agora em processo de desenvolvimento.


Ou seja, o compositor desta lista e algum conhecimento secreto que os passes de plink podem entender como colocar tudo em camadas corretamente.


Além do fato de que essa abordagem basicamente corrige esse bug, também obtemos alterações bastante baratas na renderização. Suponha que tenhamos uma lista de comandos de desenho e que ocorram alterações - digamos que o elemento B saia, o elemento E seja adicionado. Em seguida, podemos simplesmente manter as duas listas juntas sem se preocupar com árvores etc. Na saída, obtemos uma nova lista de elementos para renderização e, possivelmente, uma nova lista de camadas que serão compiladas no futuro.
Esta foi uma pequena história sobre o que acontece quando o navegador implementa o Paint e o que acontece depois que ele tenta compor as camadas.

Vamos para outro tópico: Rasterização. Apenas na Rasterização no Yandex.Browser, muitas coisas foram feitas, e eu também fiz isso. Qual é o significado da rasterização? Na saída do estágio anterior, quando fizemos o Paint, há uma lista de comandos que devemos implementar para obter algum tipo de imagem. Rasterização é a transformação de uma lista de comandos em pixels reais.


Se você abrir a guia Mais ferramentas → Renderização no inspetor do navegador, haverá uma marca de seleção Bordas da camada. E você vê exatamente essa grade. O que está acontecendo aqui? Quando o navegador desenha a página, ele não faz a coisa toda agora. O navegador pega e divide cada camada em um determinado número de pequenos quadrados. Historicamente, eles têm 256 por 256 pixels de tamanho. Ou seja, cada camada é dividida em tantas texturas separadas. O conteúdo do bloco atual é desenhado em cada textura e, em seguida, todos são colados em uma textura grande.

Isso ajuda muito. Primeiro, podemos redesenhar apenas os blocos que foram alterados. Mas também nos permite priorizar a renderização. Ou seja, antes de tudo, os blocos que o usuário vê, a chamada viewport, devem ser desenhados. Então temos que desenhar logo a fronteira, é isso que está em torno da janela de exibição. A seguir, é a direção do pergaminho: se for rolado para baixo, desenhamos o mais para baixo possível. Se estiver ativo - eleve o máximo possível. Se ainda tivermos uma cota de memória, desenharemos outra coisa, mas não o fato de que ela permanecerá nesse ponto.

Portanto, temos uma atualização de conteúdo bastante barata na página. Suponha que pegamos o quadro atual e o usuário desativou algo - por exemplo, texto destacado. Depois, desenhamos apenas os blocos que foram alterados.

Ou seja, azulejos verdes são aqueles que permanecem da renderização anterior, vermelhos que redesenhamos. Mas essa abordagem tem outras vantagens.

Podemos fazer - e o Chrome fez - a chamada otimização de pequenos redesenhos. Suponha que você tenha algum tipo de pulsador, cursor ou algo assim fazendo um pequeno redesenho em um pequeno retângulo. Então não precisamos redesenhar o quadrado inteiro. Isso é lógico. Por exemplo, se o cursor piscar, apenas ele será redesenhado. Isso economiza bastante a CPU.

A próxima otimização que eles fizeram. Onde pode haver ineficiência? Os ladrilhos foram trocados? Boa ideia, mas eu tendem a outra. Aqui está apenas um retângulo branco. Esse é um bloco branco no qual nenhum pixel é desenhado. Mas é uma textura. Ele ocupa 256 por 256 por quatro bytes de memória.

Outra otimização que foi inventada no Chrome: vamos pegar esses blocos de uma cor e codificá-los não com muitos pixels, mas, de fato, com os coordenadores, tamanho e cor. A Internet agora está cheia de páginas com muitas áreas monocromáticas para monitores grandes. E como essas páginas são otimizadas de acordo, obtemos muita economia de memória.
Nós da Yandex fomos um pouco mais longe e decidimos fazer um experimento mais específico. Onde você acha que pode economizar mais?
Nós temos um azulejo. O conteúdo está localizado em alguma área minúscula - uma faixa, a palavra Yandex. Por que precisamos desenhar como um todo, se o conteúdo é muito pequeno e todo o resto é monocromático?

O que fizemos? Isto é o que eu fiz especificamente. Dividimos cada peça em cinco peças. Se o conteúdo estiver apenas no meio, selecionaremos a textura desse conteúdo apenas para o que é feito no meio. Aqui está a área vermelha. Todo o resto codificamos da mesma maneira - tamanho, coordenadas e cores.

Ou seja, especificamente nesta página, todas essas áreas agora se tornaram não texturizadas. Não são usados bytes de memória, mas simplesmente comandos sobre o que precisamos desenhar aqui, preencha com uma cor. Isso nos proporcionou uma economia média de cerca de 40% na memória da GPU por usuário.

Em páginas mais complexas, fica assim. Como as páginas mais complexas usam mais camadas, e cada camada é uma peça separada, você pode economizar um pouco em qualquer camada.
Se você ativar esta marca de seleção agora, não verá uma grade de retângulos, mas esta.

O que é, por que os azulejos são tão largos aqui e por que são poucos? O significado aqui é o seguinte. No Chrome, eles pensaram: por que não fazemos não apenas composição de hardware, mas também renderização de hardware. O que eles estão fazendo? Temos uma lista de comandos sobre o que precisa ser feito: desenhe um retângulo, preencha com cores, etc. Tudo isso vai para a GPU, e a GPU desenha essa textura. O redesenho é muito rápido, então os ladrilhos também podem ser ampliados. Aqui está um pequeno
vídeo sobre chacal, mas mostra muito bem a vantagem que aconteceu nos telefones devido ao fato de a renderização ter se tornado acelerada por hardware. Eu acho que a diferença aqui é muito, muito perceptível.
A comunicação entre desenvolvedores de navegadores e fornecedores de front-end parece muito útil para mim. Isso não acontece com muita frequência, mas oferece muitos benefícios. Portanto, quando nossos colegas de outros departamentos nos procuram e perguntam como tornar o layout mais rápido e melhor, tentamos ajudá-los e conversamos sobre lugares onde algo não é ideal e você pode acelerar.
E não vou me cansar de repetir meu conselho. (Não vou citar aqui, houve um
grande relatório separado sobre esse assunto. - comentário do autor.)
Aqui eu compilei um conjunto de links úteis, sobre renderização e não apenas, mas também um pouco sobre o Yandex.Browser. Obrigada