Entendendo o Impeller: Um mergulho profundo no mecanismo de renderização do Flutter

Tempo de leitura: 11 minutes

No mundo do Flutter, nos últimos meses, houve muita conversa sobre o Impeller. Lançamento estável do Impeller aqui, lançamento prévio do Impeller ali…

Mas apenas algumas pessoas falam sobre a mecânica subjacente do Impeller.

Mas é importante saber como o Impeller funciona!

Por que você pergunta?

Porque, ao saber como o Impeller funciona, podemos tomar decisões informadas sobre como otimizar o desempenho do nosso aplicativo – uma das métricas mais importantes no desenvolvimento de aplicativos!

É por isso que, hoje, vamos aprender o seguinte:

O que é um mecanismo de renderização?
Como funciona um mecanismo de renderização em geral?
O problema com o Skia
O que há de tão especial no Impeller?
Mergulhe fundo na arquitetura do Impeller

Vamos começar!

O que é um mecanismo de renderização?

Os mecanismos de renderização são softwares que transformam instruções abstratas do código de um aplicativo em imagens na tela. Eles estão por trás dos gráficos e das interfaces de usuário de aplicativos móveis, videogames e sites.

Esse software funciona nos bastidores, convertendo o código em pixels, cores, formas e animações. É o que permite que os desenvolvedores criem as interfaces e experiências com as quais os usuários interagem em seus dispositivos.

Em sua essência, um mecanismo de renderização é responsável por desenhar a interface do usuário de um aplicativo. Isso envolve um processo complexo de interpretação do código do aplicativo, que define o layout, a aparência e o comportamento de vários elementos e, em seguida, renderiza essas informações como imagens que podem ser exibidas em uma tela. O mecanismo deve lidar com uma variedade de tarefas, incluindo cálculos de layout, mapeamento de textura e animações, garantindo que tudo tenha a aparência desejada, independentemente do tamanho ou da resolução da tela do dispositivo.

A importância de um mecanismo de renderização não pode ser exagerada. Ele afeta diretamente o desempenho e a aparência de um aplicativo. Um mecanismo bem otimizado pode renderizar gráficos de forma rápida e eficiente, resultando em animações suaves e interfaces responsivas. Isso torna a experiência do usuário mais agradável e pode contribuir significativamente para o sucesso de um aplicativo. Consequentemente, os desenvolvedores geralmente precisam escolher o mecanismo de renderização certo que atenda às necessidades de seu projeto, equilibrando fatores como velocidade, qualidade e compatibilidade com diferentes dispositivos e plataformas.

Como funciona um mecanismo de renderização em geral?

Um mecanismo de renderização segue um processo sistemático, começando com a interpretação de instruções de alto nível definidas pelos desenvolvedores, como o layout, as cores e as animações da interface de usuário de um aplicativo. Em seguida, essas instruções são divididas em tarefas menores, como exibir um botão aqui, um campo de texto ali ou animar um objeto que se move pela tela.

Lembre-se de que examinaremos mais detalhadamente como esses processos funcionam mais adiante neste artigo; esta é apenas uma visão geral de cada mecanismo de renderização:

O processo de layout

Depois que o mecanismo de renderização tem uma compreensão clara das tarefas em questão, ele inicia o processo de layout. Isso envolve o cálculo de onde cada elemento visual deve ser colocado na tela, qual deve ser o seu tamanho e como ele interage com outros elementos. Essa etapa é fundamental para garantir que o aplicativo tenha boa aparência em vários dispositivos, com diferentes tamanhos e resoluções de tela.

O processo de desenho

Depois de definir os elementos, o mecanismo passa a desenhá-los. Isso envolve a tradução do layout e das propriedades visuais em pixels reais na tela, um processo conhecido como rasterização. Ele determina a cor de cada pixel, com base nos elementos que precisam ser exibidos.

Toques finais

Por fim, o mecanismo de renderização aplica os retoques finais necessários para dar vida aos elementos visuais. Isso inclui a aplicação de texturas, a manipulação de efeitos de luz e a renderização de animações. Para as animações, o mecanismo calcula o movimento dos objetos quadro a quadro, garantindo transições e interações suaves. Durante todo esse processo, o mecanismo atualiza continuamente a tela, redesenhando os elementos visuais conforme necessário para refletir quaisquer alterações no estado do aplicativo ou nas interações do usuário.

O problema com o Skia

O Skia é o mecanismo de renderização usado anteriormente no Flutter. Em geral, ele não é um mecanismo ruim. Na verdade, é um mecanismo de uso geral muito bom.

Um rápido desvio do que é o Skia

O Skia é uma biblioteca de gráficos 2D de código aberto. Ela tem APIs comuns que funcionam em diferentes plataformas de hardware e software. O Skia é usado para gráficos em diferentes produtos, como Google Chrome e Chrome OS, Android, Mozilla Firefox e Firefox OS. O Skia foi projetado para ser poderoso e versátil. Ele pode renderizar gráficos de alta qualidade com rapidez e eficiência. O Skia é popular entre os desenvolvedores porque é confiável e tem um bom suporte. No entanto, para aplicativos Flutter, o design de uso geral do Skia tem limitações. Isso levou ao desenvolvimento do Impeller, um mecanismo de renderização voltado para o Flutter.

Os problemas de um mecanismo de renderização de uso geral no Flutter

O Skia, como uma biblioteca de gráficos 2D de uso geral, tem algumas dificuldades quando se trata de atender às necessidades específicas do Flutter. Embora a Skia seja uma biblioteca robusta e versátil que funciona bem para muitos aplicativos, ela não é totalmente otimizada para os requisitos de alto desempenho do Flutter. O problema é que o Skia vem com muitos recursos que excedem os requisitos do Flutter, o que pode causar sobrecarga desnecessária e resultar em tempos de renderização mais lentos. Isso pode ser um problema para os aplicativos Flutter, especialmente quando se trata de animações e transições que exigem uma renderização precisa e eficiente para uma experiência de usuário tranquila.

Além disso, a abordagem ampla do Skia para tarefas de renderização significa que ele não é ajustado com precisão para a arquitetura específica do Flutter. Essa falta de otimização se torna evidente em cenários exigentes, como animações e transições complexas, em que os aplicativos Flutter exigem uma renderização precisa e eficiente para evitar jank ou stuttering. A natureza de uso geral do Skia, portanto, pode introduzir gargalos de desempenho, prejudicando a capacidade de resposta e a fluidez que os usuários esperam dos aplicativos modernos.

Além disso, à medida que os desenvolvedores do Flutter ultrapassam os limites do que é possível com a estrutura, criando interfaces de usuário mais complexas e dinâmicas, as limitações do modelo de tamanho único do Skia se tornam cada vez mais aparentes. Esses desafios ressaltam a necessidade de um mecanismo de renderização que seja mais adaptado aos requisitos e à arquitetura exclusivos do Flutter, garantindo que os aplicativos possam aproveitar todo o potencial da estrutura sem serem prejudicados pelas restrições de um mecanismo de renderização de uso geral.

O que há de tão especial no Impeller?

O Impeller é um mecanismo de renderização que foi criado especificamente para aplicativos Flutter. Ao contrário do Skia, o Impeller não é uma biblioteca de gráficos para uso geral. Em vez disso, ele foi projetado para otimizar o processo de renderização para a arquitetura do Flutter. Essa especialização permite que o Impeller se concentre apenas em recursos e otimizações que melhoram o desempenho do aplicativo Flutter. O principal objetivo do Impeller é garantir que os aplicativos Flutter sejam executados sem problemas, eliminando qualquer jank ou gagueira que possa prejudicar a experiência do usuário.

Um dos recursos de destaque do Impeller é sua abordagem à renderização. O Impeller foi desenvolvido para aproveitar todos os recursos das GPUs (unidades de processamento gráfico) modernas com mais eficiência do que o Skia. Isso significa que animações, transições e elementos complexos da interface do usuário podem ser renderizados com maior velocidade e menos esforço do hardware do dispositivo. O mecanismo usa técnicas avançadas, como tesselação e compilação de sombreadores, para decompor e renderizar gráficos de uma forma que se alinha perfeitamente com a forma como os aplicativos Flutter são construídos. Ao fazer isso, o Impeller reduz significativamente a carga de trabalho no dispositivo, resultando em taxas de quadros mais rápidas e animações mais suaves.

O Impeller também apresenta uma arquitetura inovadora que agiliza o processo de renderização. Essa arquitetura é dividida em camadas, o que permite que cada componente do mecanismo execute sua tarefa específica com o máximo de eficiência. No centro desse sistema está o foco em minimizar as etapas necessárias para traduzir o widget do Flutter e renderizar árvores de objetos em pixels na tela. Ao otimizar o caminho do código para a saída visual, o Impeller garante que até mesmo os aplicativos Flutter com maior uso de gráficos possam manter o alto desempenho e a capacidade de resposta.

Outra vantagem importante do Impeller é a manipulação de shaders, pequenos programas que são executados na GPU para controlar a renderização de gráficos. Ao contrário do Skia, que compila shaders em tempo real e pode causar atrasos, o Impeller compila a maioria de seus shaders com antecedência. Essa pré-compilação reduz significativamente a chance de ocorrer jank durante as animações, pois a GPU não precisa pausar para compilar shaders durante a renderização de quadros. Para os desenvolvedores do Flutter, isso significa que seus aplicativos podem atingir taxas de quadros altas e consistentes sem a imprevisibilidade da compilação de shaders em tempo de execução.

Por fim, o Impeller tenta resolver desafios comuns de renderização, como anti-aliasing e clipping. Ele usa técnicas eficientes para garantir que as bordas sejam suaves e que as operações de recorte sejam rápidas, sem comprometer a qualidade ou o desempenho. A atenção do Impeller aos detalhes significa que os aplicativos Flutter podem ter uma aparência melhor e uma interação mais natural e fluida. Ele define um novo padrão de desempenho de aplicativos e qualidade visual, fornecendo um mecanismo de renderização que realmente entende e atende às necessidades da plataforma.

Mergulhe fundo na arquitetura do Impeller

Agora você já tem uma boa compreensão dos mecanismos de renderização e do que há de tão especial no Impeller. Agora, queremos dar uma olhada profunda em como o Impeller realmente funciona. Vamos dar uma olhada nas diferentes camadas, nos pipelines de renderização e nas compilações de shader, bem como em algumas outras otimizações para o Flutter.

A abordagem em camadas

O design do Impeller é estruturado em camadas, sendo que cada camada se baseia na camada abaixo dela para executar uma função especializada. Esse design torna o mecanismo mais eficiente e mais fácil de manter e atualizar, pois separa as diferentes preocupações. A hierarquia das camadas mantém o design organizado e permite uma compreensão clara de suas funções.

Aiks – A interface de alto nível

No topo da arquitetura do Impeller está o Aiks, uma camada que serve como interface de alto nível para operações de desenho.

Ela aceita comandos da estrutura do Flutter, como desenhar caminhos ou imagens, e os traduz em um conjunto mais refinado de operações chamado “Entities”. É aqui que entra a próxima camada.

Entities Framework

Por trás da Aiks está a Estrutura de entidades, um componente central da arquitetura do Impeller. Quando a Aiks processa um comando, ela gera entidades, unidades autônomas de instruções de renderização que incluem todas as informações necessárias para desenhar um elemento específico.

Cada entidade carrega consigo propriedades como matrizes de transformação (posição de codificação, rotação, escala), juntamente com um “objeto de conteúdo” que contém as instruções de GPU necessárias para a renderização.

Esses objetos de conteúdo são muito flexíveis e podem gerenciar muitas partes visuais, como cores sólidas, imagens, gradientes e texto. A Aiks pode usar diferentes versões desses objetos, cada uma feita para criar a mesma coisa de várias maneiras. Isso permite que a Aiks escolha a melhor maneira de mostrar os recursos visuais em seu aplicativo Flutter, garantindo que ele seja executado sem problemas.

Agora ainda não podemos nos comunicar com a própria GPU. Essa é uma tarefa bastante complexa porque o mecanismo precisa se comunicar com muitas APIs diferentes, como Metal no iOS ou Vulkan no Android. É por isso que há outra camada para isso no Impeller, a camada de abstração de hardware:

Hardware Abstraction Layer

A camada de abstração de hardware (HAL) forma a base da arquitetura do Impeller. Ela fornece uma interface uniforme para o hardware gráfico subjacente, abstraindo as especificidades de diferentes APIs gráficas, como Metal para iOS e Vulkan para Android. Essa camada garante que o Impeller possa operar em uma ampla gama de dispositivos e plataformas sem modificações, possibilitando a compatibilidade entre plataformas. Ela traduz os comandos de renderização de alto nível em instruções de baixo nível da GPU, atuando como ponte entre a lógica de renderização do Impeller e o hardware gráfico do dispositivo.

Pipelines de renderização e compilação de sombreadores

As tarefas mais caras do mecanismo de renderização são os pipelines de renderização e as compilações de shader.

Os pipelines de renderização são sequências de etapas que a GPU executa para renderizar gráficos. Essas são as etapas que foram produzidas pela camada de abstração de hardware.

Ao contrário dos mecanismos de renderização tradicionais que compilam shaders (os programas que são executados na GPU para processar gráficos) em tempo de execução, o Impeller segue um caminho diferente. Ele pré-compila a maioria de seus shaders com antecedência, uma estratégia que reduz significativamente a latência de renderização e elimina o jank associado à compilação de shaders em tempo real. Essa pré-compilação ocorre durante o processo de criação do aplicativo Flutter, garantindo que os shaders estejam prontos para uso assim que o aplicativo for iniciado.

(Inicialmente, eu queria falar sobre esse tópico mais detalhadamente mais adiante, mas isso tornaria este artigo muito grande. Se você quiser que eu faça um artigo sobre isso, basta me dizer deixando um comentário abaixo)!

Mais otimizações para o Flutter

O Impeller também cuida de algumas outras coisas que otimizam a renderização. Todas elas estão em algum lugar embutidas nas camadas do Impeller, mas ainda é importante saber que elas existem, pois têm um impacto significativo no desempenho.

 

Manutenção de um tamanho pequeno de aplicativo ao usar shaders pré-compilados

Já falamos sobre como o Impeller usa a compilação antecipada para resolver o problema do lixo eletrônico.

Agora, você pode se perguntar se ter esses shaders pré-compilados significa que seu aplicativo demorará mais para iniciar ou aumentará de tamanho. No entanto, a equipe do Flutter encontrou uma maneira de superar essa desvantagem.

O aplicativo Impeller usa uma técnica exclusiva para renderizar gráficos, contando com um conjunto mais simples de shaders do que o Skia. Essa abordagem ajuda a manter o tempo de inicialização do aplicativo baixo e o tamanho geral gerenciável.

Ao usar essa estratégia, o Impeller melhora o desempenho sem comprometer a qualidade, o que geralmente é uma preocupação com os shaders pré-compilados.

Anti-aliasing

O anti-aliasing é uma técnica usada em gráficos digitais para reduzir a aparência de bordas irregulares nas imagens, fazendo com que elas pareçam mais suaves e naturais ao olho humano. Isso é particularmente importante em aplicativos e jogos em que há linhas diagonais e curvas, pois elas podem exibir uma aparência de degraus ou “irregular” devido aos pixels quadrados que compõem a tela. Ao mesclar sutilmente as cores das bordas com o plano de fundo, o suavização de serrilhado suaviza essas linhas irregulares, aprimorando a qualidade visual geral dos gráficos.

No kit de ferramentas do Impeller, o anti-serrilhamento é tratado por meio do MSAA (Multisample Anti-Aliasing), uma técnica conhecida por sua eficiência e eficácia na suavização de bordas irregulares. A MSAA funciona por meio da amostragem de cada pixel várias vezes em diferentes posições dentro do pixel e, em seguida, calcula a média dessas amostras para determinar a cor final. Ao fazer isso, ele mescla as bordas de um objeto suavemente com seu plano de fundo, reduzindo sua aparência irregular. O MSAA é otimizado no Impeller, o que o torna uma opção ideal para dispositivos móveis em que o desempenho e a duração da bateria são fatores importantes. Apesar de sua baixa sobrecarga, o MSAA aprimora a qualidade visual dos aplicativos Flutter em uma ampla gama de dispositivos.

 

Recorte (Clipping)

O recorte é uma técnica para ocultar ou revelar seletivamente partes de objetos ou imagens com base em um limite ou forma definida, conhecida como máscara de recorte. No contexto dos aplicativos Flutter, o recorte é frequentemente empregado para criar elementos e animações complexos de UI, restringindo a visibilidade de widgets a áreas específicas. No entanto, para que o recorte seja eficaz, especialmente em aplicativos dinâmicos com atualizações frequentes, ele precisa ser executado de forma eficiente para evitar qualquer impacto negativo no desempenho.

Para superar esse desafio, o Impeller utiliza o buffer de estêncil, um componente das GPUs modernas, para gerenciar o processo de recorte.

À medida que o Impeller renderiza os recursos visuais, ele instrui a GPU a usar o buffer de estêncil, que atua como um filtro para determinar quais pixels devem ser exibidos com base na máscara de recorte. Ao otimizar o uso de recursos de hardware como o buffer de estêncil, o Impeller garante que as operações de recorte sejam executadas rapidamente.

Conclusão

Foi muito conhecimento.

Hoje, aprendemos o que é uma renderização e como ela funciona em geral, por que o Flutter muda do Skia para o Impeller e como o Impeller funciona nos bastidores. Agora você tem um ótimo entendimento do mecanismo de renderização subjacente no Flutter.

Eu me dediquei muito a este artigo. Se você gostar, ficaria muito grato se pudesse aplaudir este artigo! Para você, é apenas um clique, mas para mim significa muito mais do que você pensa!