2 pontos por GN⁺ 2024-02-15 | 1 comentários | Compartilhar no WhatsApp
  • O M1 ficou por anos no OpenGL 4.1, mas o driver mais recente para M1/M2 do Fedora Asahi Remix agora oferece suporte completo a OpenGL 4.6 e OpenGL ES 3.2
  • O driver Linux open source da Asahi, ao contrário do driver 4.1 não conforme do fornecedor, é um driver com certificação de conformidade listado pela Khronos, ampliando a compatibilidade com cargas de trabalho modernas de OpenGL como o Blender
  • O OpenGL 4.6 exige robustness, SPIR-V, clip control, cull distance, compute shaders e transform feedback aprimorado, mas o hardware do M1 não se encaixa diretamente nos padrões gráficos mais recentes
  • Os recursos ausentes no hardware foram compensados com técnicas de driver e compilador: geometry shaders, tessellation e transform feedback são tratados com compute shaders, enquanto cull distance e clip control são processados por transformações de shader
  • Para passar em mais de 100.000 testes de conformidade, a robustness de buffers e imagens foi implementada em software, com otimizações de clamp, preamble e contorno de mipmap para reduzir o custo adicional

OpenGL 4.6/ES 3.2 disponível no Fedora Asahi Remix

  • O M1 antes só suportava até OpenGL 4.1, mas agora passa a suportar OpenGL 4.6 e OpenGL ES 3.2
  • Os drivers mais recentes da série M1/M2 podem ser usados instalando o Fedora Asahi Remix
  • Quem já instalou pode atualizar com o seguinte comando
    • dnf upgrade --refresh
  • O driver Mesa open source da Asahi, ao contrário do driver 4.1 não conforme do fornecedor, possui conformidade com versões mais novas do OpenGL
  • Um driver 4.6/3.2 conforme precisa passar em mais de 100.000 testes para garantir a correção
  • Seis meses após surgir o primeiro driver conforme para a API gráfica padrão do M1 com OpenGL ES 3.1, agora o trabalho foi concluído também para OpenGL 4.6
  • O suporte a Vulkan também está em andamento

Implementação de recursos para superar a barreira do OpenGL 4.1

  • O OpenGL 4.6 adiciona vários recursos obrigatórios em comparação ao 4.1
    • Robustness
    • SPIR-V
    • Clip control
    • Cull distance
    • Compute shaders
    • Transform feedback aprimorado
  • O M1 não se adapta tão bem a padrões gráficos mais novos do que o OpenGL ES 3.1
    • O Vulkan torna alguns recursos opcionais, mas para colocar DirectX e OpenGL sobre ele, os recursos ausentes são necessários
    • A solução existente do M1 não conseguia ir além do conjunto de recursos do OpenGL 4.1
  • Os novos recursos sem suporte em hardware foram implementados com técnicas de driver
    • Geometry shaders, tessellation e transform feedback são tratados com compute shaders
    • Cull distance é tratado com valores interpolados transformados
    • Clip control é implementado no epílogo do vertex shader

Robustness de buffer e correção em software no M1

  • Tradicionalmente, GPUs priorizam desempenho acima de segurança, então código incorreto em que um shader lê fora dos limites de um buffer pode causar comportamento indefinido
  • Em aplicações que lidam com shaders não confiáveis, como navegadores web, esse tipo de compromisso não é desejável
    • A própria API gráfica não é um limite de segurança, então algum nível de sanitização é necessário
    • Reduzir comportamentos indefinidos da API ajuda na defesa em profundidade
  • Ao ativar robustness, a aplicação pode escolher um comportamento definido para acessos fora do intervalo em troca de abrir mão de parte do desempenho
  • O resultado de carregamentos fora do intervalo em buffers varia conforme a API
    • Direct3D e Vulkan robustBufferAccess2: retorna 0
    • OpenGL e Vulkan robustBufferAccess: retorna 0 ou alguns dados de dentro do buffer
    • OpenGL ES: pode retornar um valor arbitrário, mas não pode travar
  • Como o OpenGL exige retornar 0 ou dados de dentro do buffer em acessos fora do intervalo, é possível carregar com um índice seguro calculando o mínimo sem sinal entre o último índice válido e o índice acessado
    • carregamento de uniform buffer sem robustness: load.i32 result, buffer, index
    • após aplicar robustness: umin idx, index, last seguido de load.i32 result, buffer, idx
  • O preamble do M1 calcula uma vez e reutiliza valores em vez de recalculá-los repetidamente em todas as threads
    • Como o tamanho do uniform buffer é fixo, a aritmética extra de robustness também pode ser movida para o preamble
    • Mesmo em storage buffers robustos, ainda que o próprio load/store não possa ser movido, o cálculo de clamp pode ser levado ao preamble

Implementação de robustness de vertex buffer

  • Em APIs gráficas, a aplicação define o endereço base na GPU do vertex buffer e o layout dos atributos
    • Cada atributo tem offset e formato
    • O buffer tem stride, que representa o número de bytes por vértice
    • O vertex shader lê os atributos indexando implicitamente com base no vértice
  • Alguns hardwares implementam robust vertex fetch nativamente ou aceleram fetch em software com buffers checados por limites, mas o M1 não tem nenhum dos dois
  • Os carregamentos de memória da GPU do M1 recebem um endereço base de 64 bits e um offset em unidades de elemento, além de oferecer a instrução imad de multiplicação e soma inteira
    • Um carregamento de atributo de 32 bits pode ser implementado com duas instruções: imad idx, stride/4, vertex, offset/4 e load.i32 result, base, idx
    • Um atributo vetorial com 4 valores de 32 bits densamente empacotados pode ser carregado com uma única instrução: load.v4i32 result, base, vertex << 2
  • A robustness exige clamp, mas o tamanho do vertex buffer está em bytes e o carregamento otimizado usa índice em unidades de vértice
  • O problema foi resolvido reinterpretando múltiplos atributos e offsets dentro de um mesmo buffer como se fossem endereços base separados por atributo
    • Em vez de somar o offset no shader, passa-se uma base por atributo
    • Assim, o tamanho do buffer em bytes pode ser convertido para um tamanho em unidades de vértice para cada atributo
    • O que recebe clamp é o índice do vértice, não o offset
  • O driver calcula previamente o último índice de vértice válido para cada atributo usando o tamanho do formato do atributo e o envia ao shader
  • Se o buffer for pequeno demais para carregar qualquer coisa, o clamp não resolve; nesse caso, o buffer daquele atributo é trocado por um pequeno zero buffer
    • Como são usados endereços base por atributo, essa decisão também pode ser tomada por atributo
  • No fim, um vertex buffer robusto foi implementado com um pequeno cálculo no driver e o custo de apenas um umin

Robustness de imagem e contorno de mipmap

  • Além da robustness de buffers, também é necessária robustness de imagem, e carregamentos fora do intervalo em imagens devem retornar 0
  • Imagens com mipmap contêm vários níveis de detalhe
    • O nível base é a imagem original
    • Cada nível seguinte é uma versão reduzida do anterior
    • Na renderização, o hardware escolhe um nível próximo ao tamanho na tela para melhorar eficiência e qualidade visual
  • A especificação exige que, com robustness, image load retorne 0 nos seguintes casos
    • Quando a coordenada X ou Y está fora do intervalo
    • Quando o nível está fora do intervalo
  • O comportamento de image load na GPU do M1 é diferente do exigido
    • Se a coordenada X ou Y estiver fora do intervalo, retorna 0
    • Se o nível estiver fora do intervalo, retorna o valor do último nível
  • Como o fornecedor não publica documentação do hardware, não é possível saber se esse comportamento é intencional ou um bug de hardware, e foi necessário um contorno para passar na conformidade
  • O contorno mais simples seria usar branch: carregar só quando o nível for válido e, caso contrário, retornar 0, mas branches são ineficientes
  • Uma forma melhor é aproveitar que o carregamento não trava mesmo com nível fora do intervalo, fazer o load primeiro e depois escolher 0 com compare-select
    • Porém, o conjunto de instruções da GPU do M1 é escalar, e image load retorna um vetor com 4 componentes red/green/blue/alpha
    • Isso exigiria ulesel para cada componente, aumentando o assembly
  • O contorno final aproveita que o hardware retorna 0 quando X ou Y estão fora do intervalo
    • Como a largura máxima da imagem é 16384 px, trocar X por um valor como 20000 o coloca fora do intervalo
    • Se o nível for válido, usa-se o X original; se não for válido, X vira 20000 para fazer image load retornar 0
  • Esse método gera assembly compacto porque altera apenas um escalar, sem precisar selecionar o vetor inteiro
    • Se a constante for carregada previamente em um registrador uniforme, o custo do contorno é de uma única instrução
    • Com isso, foi possível passar na conformidade

1 comentários

 
GN⁺ 2024-02-15
Comentários do Hacker News
  • Alyssa Rosenzweig parece um grande presente que continua contribuindo para a comunidade
    A cada post do blog, sempre acabo aprendendo algo que não sabia sobre a estrutura interna do hardware gráfico moderno

  • É um trabalho que mostra, toda vez, que competência vence palavras
    Só de ler o blog, há tanta coisa para destrinchar que a cabeça esquenta; e, embora a conclusão esteja na segunda frase, não na última, você acaba descendo pela toca do coelho acompanhando cada manipulação de bits
    Se existisse um benchmark de número de insights por parágrafo, acho que Alyssa ficaria em primeiro em todos

  • Talvez, se um dia a Apple abandonar o OpenGL 3.3 core, todo mundo acabe abandonando também
    Em geral, ouvi dizer que OpenGL é mais fácil de usar que Vulkan, mas APIs complexas demais podem dificultar o aproveitamento da GPU por desenvolvedores com menos experiência e virar uma barreira de entrada, afastando desenvolvedores indie de jogos
    Hoje em dia todo mundo usa Unity e Unreal, então criar algo do zero ou usar outro motor parece estranho; ver o desenvolvimento de jogos acordar depois que a Unity tentou fechar mais o cerco é interessante, mas também irritante
    Open source em desenvolvimento de jogos sempre foi apertado e, embora exista o Godot, parece difícil competir seriamente com Unity e Unreal
    Mesmo que o Godot seja plenamente viável, é bem provável que desenvolvedores indie continuem onde estão por serem mais familiarizados com Unity e Unreal
    O estado do open source no desenvolvimento de jogos às vezes parece desesperador, e a chegada das APIs gráficas de próxima geração não torna as coisas mais fáceis

    • A afirmação de que OpenGL é mais fácil que Vulkan faz bastante sentido
      O exemplo de renderização de triângulo em OpenGL tem cerca de 200 linhas, enquanto o exemplo de renderização de triângulo em Vulkan tem cerca de 1000 linhas
    • Pessoalmente, acho que Metal é mais fácil de usar que Vulkan
      Vulkan foi projetado para ser muito flexível, mas não oferece muitos recursos de conveniência
      De qualquer forma, OpenGL era de alto nível demais para ser exposto como API direta do driver; faz mais sentido ter uma API de baixo nível como Vulkan como camada base e colocar algo como OpenGL por cima dela, porque isso combina melhor com o modo como o hardware de GPU funciona
      Além disso, nem todo mundo usa Unity e Unreal
      Os 6 indicados a Jogo do Ano no The Game Awards 2023 foram todos feitos com motores próprios, e mesmo no indie ainda há desenvolvedores que criam seus próprios motores, como em Hades
      Dito isso, é verdade que a maioria usa motores prontos
    • OpenGL não foi abandonado; ele é simplesmente mais simples e continua sendo usado onde Vulkan é exagero
      Se ele atende a todos os recursos necessários e você aceita um pipeline de renderização baseado em estado, é uma boa escolha também para projetos novos
    • No macOS e no iOS, OpenGL já está marcado para descontinuação há alguns anos
      Ainda funciona e, hoje em dia, roda como uma camada sobre Metal, mas, ao compilar código GL para macOS ou iOS, os avisos de descontinuação aparecem continuamente
      Dá para desativar com um define
    • WGPU está mais próximo de tentar resolver esse problema como uma API multiplataforma mais fácil de usar que Vulkan
      O problema do OpenGL é que ele fica distante demais do modo como a GPU funciona, o que dificulta obter bom desempenho
  • Fico curioso para saber quanto deste trabalho está preso ao código da GPU do M1 e quanto dessa implementação de recurso sobre recurso poderia ser reutilizado em outros lugares
    Parece muito parecido com a forma como o Zink executa recursos complexos de OpenGL sobre um Vulkan mais primitivo, mas o M1 ainda não tem um backend Vulkan como alvo

    • De forma mais geral, é possível executar OpenGL ou Vulkan complexos sobre qualquer combinação de renderização por software na CPU e suporte a aceleração nativa específico de hardware
      No fim, é uma questão de volume de trabalho, e isso pode ser reutilizado em vários hardwares
      Também pode ajudar em hardwares antigos e bem compreendidos, mas difíceis de usar sozinhos em cargas de trabalho modernas
  • Tenho muita curiosidade sobre qual é o impacto no desempenho disso, especialmente em comparação com usar Metal diretamente no macOS
    A resposta certamente será “depende da situação”, mas ainda assim fico curioso
    Talvez a resposta esteja no texto, mas não entendi a maior parte

    • Não há necessariamente uma grande diferença entre implementar um recurso via código compute no driver e implementá-lo com suporte de hardware da GPU
      “Suporte de hardware” normalmente também é implementado por microcódigo da GPU e muitas vezes passa pelo mesmo silício
      Qualquer recurso pode virar gargalo de desempenho, e é difícil saber onde ele vai travar antes de testar de fato
    • A formulação da Alyssa me parece um pouco estranha
      É verdade que a GPU da Apple não oferece suporte nativo a geometry shaders, mas geometry shaders têm um design ruim e não se encaixam bem no hardware de GPU
      Mesmo em hardwares que de fato oferecem suporte, eles são conhecidos por serem lentos, e há um motivo para a Nvidia ter projetado mesh shading
      Transform feedback também é citado com frequência, mas a GPU da Apple consegue escrever em posições arbitrárias de memória a partir de qualquer estágio de shader, então transform feedback é praticamente desnecessário
      O ponto central é que a Apple implementou uma arquitetura de computação enxuta e cortou muitos resquícios antigos e recursos conhecidos por não funcionarem bem
      A expressão “o M1 ficou preso ao OpenGL 4.1” não me parece adequada
      Parei de acompanhar OpenGL há muito tempo, então não sei a quais recursos após a 4.1 isso se refere, mas eu ficaria muito surpreso se houvesse algo possível em OpenGL e impossível em Metal
      Por outro lado, há muitas coisas possíveis em Metal que são totalmente impossíveis em OpenGL, e o fato de a linguagem de shading do Metal ter ponteiros completos já é um exemplo
  • Isto é para o Fedora no M1
    Seria surpreendente se isso também se tornasse possível no macOS, mas fico curioso sobre o que seria necessário para criar algo assim

    • No fim, a estrutura é criar buffers de comando e enviá-los para a GPU, então seria preciso um jeito de fazer isso no macOS
      O driver Mesa inicial para a GPU do M1 também foi bootstrapado enviando buffers de comando para o driver AGX do macOS via IOKit
      https://rosenzweig.io/blog/asahi-gpu-part-2.html
      https://github.com/AsahiLinux/gpu/blob/main/demo/iokit.c
      Então seria necessário um pouco mais de código de integração no lado do Mesa para passar a surface da GPU como algo que possa ser composto na tela do macOS
    • Segundo os desenvolvedores, na prática é difícil porque a Apple não tem uma API pública de kernel estável: https://social.treehouse.systems/@AsahiLinux/111930744188229065
    • Talvez já seja possível pela rota MoltenVK → Vulkan → Zink
    • A Apple parece proibir drivers de kernel de terceiros
      Pelo que entendo, para criar uma implementação adequada de Vulkan ou OpenGL é necessário um par no lado do kernel responsável pelo processamento da GPU
      Talvez seja por isso que ninguém pareça estar tentando implementar Vulkan nativo para macOS
      Mas não sei se seria possível sobre os drivers da Apple
    • Dá para implementar um driver OpenGL sobre Metal
      Mas não sei se há motivo para gastar tantos recursos em uma API legada que não é ideal
  • É bem engraçado chamar de robustez a mudança de acesso fora dos limites de trap para retorno de dados arbitrários
    Programação gráfica é definitivamente estranha

    • Do ponto de vista de quem escreve drivers gráficos, faz sentido, e também combina com a lei de Postel, ou seja, o princípio da robustez
      O ponto central de um driver de GPU é fazer aplicações quebradas rodarem, ou rodarem mais rápido
      Tornar o padrão mais estrito não vai corrigir os problemas estruturais da indústria de videogames que lança código quebrado; só vai afastar os usuários
      Em hardware em que desvios geralmente são muito caros, parece útil uma flag que diga ao sistema para lidar silenciosamente com casos de borda da forma mais eficiente
      Também deve haver muitos usos válidos em que o programador pode ter uma confiança razoável de que esses casos de borda quase não afetam o quadro final renderizado
    • A ausência de verificações de robustez não significa que um acesso fora dos limites necessariamente gere um trap; robustez aqui significa fornecer um resultado conhecido mesmo nesses casos estranhos
      Juntando isso ao fato de que GPUs, em geral, não gostam muito de traps, faz sentido
      Carmack também já disse que, ao projetar megatexture, foi doloroso fazer os fabricantes aceitarem a ideia de memória virtual
    • Por isso, nas áreas de gráficos, computação de alto desempenho, física de altas energias e trading de altíssima frequência, o futuro de C e C++ continua promissor
      Em áreas dominadas pela cultura de “desempenho antes de segurança”, falar de outras linguagens de programação é quase como falar com uma parede
  • Sem dúvida é algo muito interessante, mas fico me perguntando por que não mirar primeiro em Vulkan
    Hoje em dia parece um alvo mais importante, e já existe uma implementação de OpenGL sobre ele

    • Uma camada de compatibilidade OpenGL sobre Vulkan não é mágica
      Para dar suporte a um recurso específico do OpenGL, o driver Vulkan precisa suportar o recurso correspondente, e normalmente é necessária uma extensão
      Ou seja, implementar apenas um driver Vulkan básico não dá suporte a OGL 4.6 de graça; para fazer o Mesa traduzir OGL 4.6 para Vulkan, é preciso implementar no driver Vulkan todos os recursos do OGL 4.6
      Além disso, Alyssa já trabalhou com engenharia reversa e em projetos de driver OpenGL
      Não sei os detalhes, mas é bem possível que criar um driver para uma API familiar seja muito mais fácil e rápido do que para uma API desconhecida
    • Primeiro, miraram no OpenGL antigo para fazer o conjunto básico de recursos funcionar
      A partir daí, subir até um OpenGL mais recente provavelmente exigiu menos trabalho do que uma implementação completa de Vulkan, e eles provavelmente aprenderam muito do que é necessário para Vulkan também
    • Pensei algo parecido, mas, para oferecer suporte a OpenGL sobre Vulkan, de todo modo seria necessária uma versão mais alta do Vulkan, e a carga de trabalho também é grande
      Então parece que escolheram começar por uma versão mais baixa do OpenGL para obter algo funcionando mais rapidamente
  • É bem surpreendente pensar que, se John Carmack não tivesse insistido em usar OpenGL em Quake II nos anos 90, talvez o OpenGL não tivesse ganhado presença nos jogos 3D

    • Quake provavelmente foi apenas uma pequena parte da história
      O que acabou transformando o OpenGL no que ele se tornou foi a SGI e o enorme esforço para criar implementações compatíveis em vários sistemas e arquiteturas
    • Como curiosidade, o site mais antigo preservado do OpenGL tinha um enorme banner FAST GAMES GRAPHICS, com gráficos animados de Quake 1 e outro menu :-P
      https://web.archive.org/web/19970707113513/http://www.opengl.org/
    • Não sei se foi o único motivo, mas o apoio de Carmack ao OpenGL certamente ajudou
      Muitas coisas relacionadas a jogos 3D existem graças a Doom e Quake
    • Alguns anos depois, em 2011, John Carmack disse que o Direct3D era, na época, uma API melhor
      Ele via que a Microsoft tinha coragem de continuar fazendo grandes mudanças incompatíveis para melhorar a API, enquanto o OpenGL ficava travado por preocupações de compatibilidade
      Disse que o Direct3D lidava melhor com multithreading e que as versões mais recentes também tinham melhor gerenciamento de estado
      Ainda assim, afirmou que a id Software continuava no OpenGL por inércia e que, apesar das vantagens, não havia planos de migrar para o Direct3D
      Fonte: https://www.bit-tech.net/news/gaming/pc/carmack-directx-better-opengl/1/
    • Material de contexto: https://www.chrishecker.com/OpenGL/Press_Release