Apple Metal em MAPS.ME

imagem Olá pessoal!

No mundo, há um grande número de aplicativos no OpenGL, e parece que a Apple não concorda muito com isso. A partir do iOS 12 e do MacOS Mojave, o OpenGL foi descontinuado. Integramos o Apple Metal no MAPS.ME e estamos prontos para compartilhar nossa experiência e resultados. Contaremos como nosso mecanismo gráfico se refatorou, que dificuldades tivemos e, mais importante, quantos FPS temos agora.

Todo mundo que está interessado ou está pensando em adicionar o suporte Apple Metal ao mecanismo gráfico é bem-vindo ao gato.

Edição


Nosso mecanismo gráfico foi projetado como plataforma cruzada e, como o OpenGL é, de fato, a única API gráfica de plataforma cruzada para o conjunto de plataformas que nos interessa (iOS, Android, MacOS e Linux), escolhemos como base. Não realizamos um nível adicional de abstração que ocultasse os recursos específicos do OpenGL, mas, felizmente, deixamos o potencial para sua implementação.

Com o advento da nova geração de APIs gráficas Apple Metal e Vulkan, é claro que consideramos a possibilidade de sua aparência em nosso aplicativo, no entanto, o seguinte nos impediu:

  1. Vulkan só poderia funcionar no Android e Linux, e Apple Metal só poderia funcionar no iOS e MacOS. Não queríamos perder a plataforma cruzada no nível da API gráfica, isso complicaria os processos de desenvolvimento e depuração, aumentaria a quantidade de trabalho.
  2. Um aplicativo no Apple Metal não pode ser construído e executado em um simulador iOS (a propósito, até agora), o que também complicaria nosso desenvolvimento e não nos permitiria nos livrar completamente do OpenGL.
  3. O Qt Framework, que usamos para criar ferramentas internas, suportava apenas o OpenGL (o Vulkan agora é suportado ).
  4. O Apple Metal não tinha e não possui uma API C ++, o que nos forçaria a criar abstrações não apenas para o tempo de execução, mas também para a fase de construção do aplicativo, quando parte do mecanismo é compilada em Objective-C ++ e outra, substancialmente maior, em C ++.
  5. Não estávamos prontos para criar um mecanismo separado ou uma ramificação de código separada especificamente para iOS.
  6. A implementação foi avaliada por pelo menos seis meses no trabalho de um desenvolvedor gráfico.

Quando, na primavera de 2018, a Apple anunciou a transferência do OpenGL para o status obsoleto, ficou claro que não era mais possível adiar, e os problemas acima precisavam ser resolvidos de uma maneira ou de outra. Além disso, trabalhamos há muito tempo para otimizar a velocidade do aplicativo e o consumo de energia, e a Apple Metal parecia capaz de ajudar.

Seleção de decisão


Quase imediatamente notamos o MoltenVK . Essa estrutura emula a API do Vulkan usando o Apple Metal e seu código-fonte foi aberto recentemente. O uso do MoltenVK, ao que parece, permitiria substituir o OpenGL pelo Vulkan, e não teria que lidar com a integração separada do Apple Metal. Além disso, os desenvolvedores do Qt recusaram suporte separado para renderização no Apple Metal em favor do MoltenVK. No entanto, fomos parados:

  • a necessidade de oferecer suporte a dispositivos Android nos quais o Vulkan não está disponível;
  • a incapacidade de iniciar no simulador do iOS sem a presença de fallback no OpenGL;
  • a incapacidade de usar as ferramentas da Apple para depuração, criação de perfil e pré-compilação de shaders, pois o MoltenVK gera shaders em tempo real para o Apple Metal a partir dos códigos-fonte SPIR-V ou GLSL;
  • a necessidade de aguardar atualizações e correções de erros do MoltenVK quando novas versões do Metal forem lançadas;
  • a impossibilidade de otimização sutil específica do Metal, mas não específica ou inexistente para o Vulkan.

Aconteceu que precisamos salvar o OpenGL, o que significa que não podemos abstrair o mecanismo da API gráfica. O Apple Metal, o OpenGL ES e, no futuro, Vulkan, serão usados ​​para criar componentes internos independentes do mecanismo gráfico, que podem ser completamente intercambiáveis. O OpenGL desempenhará o papel da opção de fallback quando o Metal ou Vulkan estiver indisponível por um motivo ou outro.

O plano de implementação foi o seguinte:

  1. Refatoração do mecanismo de gráficos para abstrair a API de gráficos usada.
  2. Renderize para Apple Metal a versão iOS do aplicativo.
  3. Faça benchmarks apropriados para velocidade de renderização e consumo de energia para verificar se as APIs gráficas modernas de nível inferior podem beneficiar o produto.

Principais diferenças entre o OpenGL e o Metal


Para entender como abstrair a API gráfica, primeiro vamos determinar quais são as principais diferenças conceituais entre o OpenGL e o Metal.

  1. Acredita-se, e não sem razão, que o Metal é uma API de nível inferior. No entanto, isso não significa que você precise escrever no assembler ou implementar a rasterização por conta própria. O metal pode ser chamado de API de baixo nível, no sentido de que ele executa um número muito pequeno de ações implícitas, ou seja, quase todas as ações devem ser gravadas no próprio programador. O OpenGL faz muitas coisas implicitamente, começando do suporte a uma referência implícita a um contexto do OpenGL e vinculando esse contexto ao fluxo em que foi criado.
  2. No Metal, "não" validação em tempo real de equipes. No modo de depuração, a validação, é claro, existe e é feita muito melhor do que em muitas outras APIs, em grande parte devido à sua forte integração com o Xcode. Mas quando o programa é enviado ao usuário, não há mais validação, o programa simplesmente trava no primeiro erro. Escusado será dizer que o OpenGL falha apenas nos casos mais extremos. A prática mais comum: ignorar o erro e continuar trabalhando.
  3. O metal pode pré-compilar shaders e criar bibliotecas a partir deles. No OpenGL, os shaders são compilados a partir da fonte no processo do programa, pois isso é responsável pela implementação específica de baixo nível do OpenGL em um dispositivo específico. Às vezes, diferenças e / ou erros na implementação de compiladores de shader levam a erros fantásticos, especialmente em dispositivos Android de marcas chinesas.
  4. O OpenGL usa ativamente a máquina de estado, que adiciona efeitos colaterais a quase todas as funções. Portanto, as funções OpenGL não são funções puras, e o histórico de pedidos e chamadas é frequentemente importante. O metal não usa estados implicitamente e não os preserva por mais tempo que o necessário para a renderização. Os estados existem como objetos pré-criados e com falha.

Refatoração e incorporação gráfica de mecanismo de metal


O processo de refatoração do mecanismo gráfico consistiu basicamente em encontrar a melhor solução para se livrar dos recursos do OpenGL que nosso mecanismo tem usado ativamente. Embedding Metal, a partir de uma das etapas, foi paralelo.

  • Como já observado, a API do OpenGL possui uma entidade implícita chamada contexto. O contexto está associado a um encadeamento específico, e a função OpenGL chamada nesse encadeamento encontra e usa esse contexto. Metal, Vulkan (sim, e outras APIs, por exemplo, Direct3D) não funcionam dessa maneira, pois possuem objetos explícitos semelhantes chamados dispositivo ou instância. O próprio usuário cria esses objetos e é responsável por sua transferência para diferentes subsistemas. É através desses objetos que são feitas todas as chamadas para comandos gráficos.

    Chamamos nosso objeto abstrato de contexto gráfico e, no caso do OpenGL, ele simplesmente decora as chamadas dos comandos do OpenGL; no caso do Metal, ele contém a interface raiz do MTLDevice através da qual os comandos do Metal são chamados.

    Obviamente, eu tive que distribuir esse objeto (e como temos a renderização multiencadeada, e até vários desses objetos) por todos os subsistemas.

    Ocultamos a criação de filas de comandos, codificadores e seu gerenciamento no contexto gráfico, para não espalhar entidades que simplesmente não existem no OpenGL.
  • A perspectiva do desaparecimento da validação de comandos gráficos nos dispositivos do usuário não era abertamente agradável para nós. Uma ampla variedade de dispositivos e versões de SO não pôde ser totalmente coberta por nosso departamento de controle de qualidade. Portanto, tivemos que adicionar logs detalhados nos quais recebemos anteriormente um erro significativo da API gráfica. Obviamente, essa validação foi adicionada apenas a locais potencialmente perigosos e críticos do mecanismo gráfico, pois a cobertura de todo o mecanismo com um código de diagnóstico é praticamente impossível e geralmente prejudicial ao desempenho. A nova realidade é que o usuário que está testando e depurando com logs agora está no passado, pelo menos em termos de renderização.
  • Nosso sistema de shader anterior não era adequado para refatoração; tive que reescrevê-lo completamente. O ponto aqui não é apenas a pré-compilação de shaders e sua validação no estágio de montagem do projeto. O OpenGL usa as chamadas variáveis ​​uniformes para passar parâmetros para shaders. A transferência de dados estruturados está disponível apenas no OpenGL ES 3.0 e, como ainda oferecemos suporte ao OpenGL ES 2.0, simplesmente não usamos esse método. O metal nos fez usar estruturas de dados para passar parâmetros, e para o OpenGL tivemos que criar campos de estrutura de mapeamento para variáveis ​​uniformes. Além disso, tive que reescrever cada um dos shaders na Metal Shading Language.
  • Ao usar objetos de estado, tivemos que fazer um truque. No OpenGL, todos os estados, como regra, são definidos imediatamente antes da renderização, e no Metal esse deve ser um objeto criado e validado anteriormente. Nosso mecanismo, obviamente, usou a abordagem OpenGL, e a refatoração com a criação preliminar de objetos de estado foi comparável a uma reescrita completa do mecanismo. Para cortar esse nó, criamos um cache de estado dentro do contexto gráfico. Na primeira vez que uma combinação exclusiva de parâmetros de estado é gerada, um objeto de estado é criado no Metal e colocado no cache. Pela segunda vez e subsequentes, o objeto é simplesmente recuperado do cache. Isso funciona em nossos mapas, já que o número de combinações diferentes de parâmetros de estado não é muito grande (cerca de 20 a 30). Para um mecanismo complexo de gráficos de jogos, esse método não é adequado.

Como resultado, após cerca de 5 meses de trabalho, conseguimos lançar o MAPS.ME pela primeira vez com renderização completa no Apple Metal. Estava na hora de descobrir o que aconteceu.

Teste de velocidade de renderização


Técnica experimental


Usamos diferentes gerações de dispositivos Apple no experimento. Todos eles foram atualizados para o iOS 12. O mesmo script de usuário foi executado na navegação de todos os mapas (movimentação e redimensionamento). O script foi criado para garantir uma identidade quase completa dos processos dentro do aplicativo cada vez que era iniciado em cada dispositivo. Como local de teste, escolhemos a área de Los Angeles - uma das áreas mais pesadas do MAPS.ME.

Primeiro, o script foi executado com renderização no OpenGL ES 3.0, depois no mesmo dispositivo com renderização no Apple Metal. Entre as partidas, o aplicativo foi completamente descarregado da memória.
Os seguintes indicadores foram medidos:

  • FPS (quadros por segundo) para o quadro inteiro;
  • FPS para a parte do quadro envolvida apenas na renderização, excluindo a preparação de dados e outras operações quadro a quadro;
  • A porcentagem de quadros lentos (maior que ~ 30 ms), ou seja, aqueles que o olho humano pode perceber como empurrões.

Ao medir o FPS, o desenho diretamente na tela do dispositivo foi excluído, pois a sincronização vertical com a taxa de atualização da tela não permite obter resultados confiáveis. Portanto, o quadro foi desenhado na textura na memória. Para sincronizar a CPU e a GPU, o OpenGL usou uma chamada adicional para o glFinish , enquanto a Apple Metal usou waitUntilCompleted para MTLFrameCommandBuffer .

iPhone 6siPhone 7+iPhone 8
OpenglDe metalOpenglDe metalOpenglDe metal
Fps106160159221196298
FPS (somente renderização)157596247597271833
Fração de quadros lentos (<30 qps)4,13%1,25%5,45%0,76%1,5%0,29%

iPhone XiPad Pro 12.9 '
OpenglDe metalOpenglDe metal
Fps145210.104137
FPS (somente renderização)248705147463
Fração de quadros lentos (<30 qps)0,15%0,15%17,52%4,46%

iPhone 6siPhone 7+iPhone 8iPhone XiPad Pro 12.9 '
Aceleração do quadro em Metal (N vezes)1,51,391,521,451,32
Aceleração da renderização em Metal (N vezes)3,782,413.072,843,15
Melhoria em quadros lentos (N vezes)3.3.7.175.1713,93

Análise de Resultados


Em média, o ganho de desempenho de quadros usando o Apple Metal foi de 43%. O valor mínimo é fixado no iPad Pro 12,9 '- 32%, o máximo - 52% no iPhone 8. Existe uma dependência: quanto menor a resolução da tela, mais o Apple Metal ultrapassa o OpenGL ES 3.0.

Se avaliarmos a parte do quadro que é diretamente responsável pela renderização, a velocidade média de renderização no Apple Metal aumentou três vezes. Isso indica uma organização significativamente melhor e, como resultado, a eficiência da Apple Metal API em comparação com o OpenGL ES 3.0.

O número de quadros lentos (mais de ~ 30 ms) no Apple Metal foi reduzido em cerca de 4 vezes. Isso significa que a percepção das animações e a movimentação no mapa se tornaram mais suaves. O pior resultado foi registrado no iPad Pro 12.9 'com uma resolução de 2732 x 2048 pixels: o OpenGL ES 3.0 fornece cerca de 17,5% de quadros lentos, enquanto o Apple Metal - apenas 4,5%.

Teste de potência


Técnica experimental


O consumo de energia foi testado no iPhone 8 no iOS 12. O mesmo cenário do usuário foi executado - navegação no mapa (movimentação e redimensionamento) por 1 hora. O script foi criado para garantir uma identidade quase completa dos processos dentro do aplicativo a cada início. A área de Los Angeles também foi escolhida como local de teste.

Usamos a seguinte abordagem para medir o consumo de energia. O dispositivo não está conectado ao carregamento. Nas configurações do desenvolvedor, o log de energia está ativado. Antes de iniciar o experimento, o dispositivo está totalmente carregado. O experimento termina no final do script. No final do experimento, o estado da carga da bateria foi registrado e os logs de consumo de energia foram importados para o utilitário para criação de perfil da bateria no Xcode. Registramos quanto da carga foi gasta na GPU. Além disso, aqui também ponderamos a renderização incluindo a exibição do esquema de metrô e o antialiasing em tela cheia.

O brilho da tela não mudou em todos os casos. Nenhum outro processo, exceto o sistema e o MAPS.ME, foi executado. O modo avião foi ativado, o Wi-Fi e o GPS foram desativados. Além disso, várias medidas de controle foram realizadas.

Como resultado, para cada um dos indicadores, uma comparação do Metal com o OpenGL foi formada e, em seguida, as proporções foram calculadas para obter uma estimativa agregada.

OpenglDe metalGanho
Dreno da bateria32%28%12,5%
Criação de perfil de uso da bateria no Xcode1,95%1,83%6,16%

Análise de Resultados


Em média, o consumo de energia da versão de renderização no Apple Metal melhorou um pouco. O consumo de energia do aplicativo GPU não é muito afetado, cerca de 2%, porque o MAPS.ME não pode ser chamado de muito carregado em termos de uso da GPU. Provavelmente, é conseguido um pequeno ganho reduzindo os custos computacionais ao preparar instruções para a GPU na CPU, que, infelizmente, não podem ser distinguidas com a ajuda de ferramentas de criação de perfil.

Sumário


Incorporar Metal nos custou 5 meses de desenvolvimento. Dois desenvolvedores fizeram isso, no entanto, quase sempre por sua vez. Obviamente, vencemos significativamente no desempenho da renderização, vencemos um pouco no consumo de energia. Além disso, tivemos a oportunidade de incorporar novas APIs gráficas, em particular a Vulkan, com muito menos esforço. Quase completamente “resolvemos” o mecanismo gráfico, como resultado, encontramos e corrigimos vários erros antigos e problemas de desempenho.

Para a questão de saber se o nosso projeto realmente precisa ser renderizado no Apple Metal, estamos prontos para responder afirmativamente. Não gostamos tanto da inovação ou que a Apple finalmente possa abandonar o OpenGL. É apenas 2018, e o OpenGL apareceu no distante 1997, é hora de dar o próximo passo.

PS Até lançarmos o recurso em todos os dispositivos iOS. Para habilitá-lo manualmente, digite ?metal na barra de pesquisa e reinicie o aplicativo. Para retornar a renderização ao OpenGL, digite o comando ?gl e reinicie o aplicativo.

O PPS MAPS.ME é um projeto de código aberto. Você pode ler o código fonte no github .

Source: https://habr.com/ru/post/pt430850/


All Articles