- A API gráfica experimental sysgpu, do Mach engine, precisava de artefatos de shader para Direct3D 12, então o Microsoft DXC foi reconstruído em um formato mais fácil de usar de forma estática e multiplataforma
- O DXIL do Direct3D 12 é mais próximo de um LLVM bitcode pós-processado emitido pelo fork do LLVM/Clang 3.7 da Microsoft do que de uma especificação separada, dependendo da “saída real do compilador”
- Runtimes da família WebGPU normalmente convertem WGSL para HLSL e depois usam FXC ou DXC para gerar DXBC/DXIL, mas o peso da distribuição do DXC faz com que o antigo e lento FXC tenda a virar o padrão
- O DXC existente é difícil de vincular estaticamente, e o
dxil.dll, um binário proprietário de assinatura/validação, é distribuído basicamente só para Windows e Linux x86, o que bloqueia a compilação offline de shaders DirectX em CI com macOS ou Linux Arm
- O
mach-dxcompiler reescreve o build em CMake para Zig build.zig, remove dependências de DLL e fornece a biblioteca estática dxcompiler e a CLI dxc, mas ainda tem limitações em ABI MSVC, testes com musl, saída SPIR-V e suporte a SM6.7
Por que a Mach reconstruiu o DXC
- O Mach engine está criando uma API gráfica experimental chamada sysgpu em Zig, com suporte planejado para backends Metal, Vulkan, Direct3D e OpenGL
- No backend Direct3D 12, é preciso compilar programas de shader para um formato que o Direct3D 12 consiga consumir
- Nesse processo, ficou claro que o compilador de shaders DirectX da Microsoft, o DXC, cria uma experiência de distribuição complexa e incômoda para desenvolvedores de jogos
A transição do compilador de shaders DirectX de FXC para DXC
- A API gráfica DirectX usa HLSL como linguagem de sombreamento
- O compilador de HLSL anterior ao Direct3D 11 era chamado de FXC, ou effects compiler
- O FXC é conhecido entre desenvolvedores de jogos como um compilador lento e com baixa qualidade de geração de código
- Isso pode prejudicar tanto a velocidade de compilação quanto o desempenho em execução dos shaders
- Com o Direct3D 12 e o Shader Model 6.0, a Microsoft oficialmente marcou o FXC, que vinha incluído no Windows, como deprecated e introduziu o DXC, um fork baseado em LLVM/Clang v3.7
- O DXC é disponibilizado em Microsoft/DirectXShaderCompiler, e a Microsoft também distribui binários pré-compilados
- No fork de LLVM da Microsoft, as alterações relacionadas a HLSL são marcadas com os comentários
// HLSL Change Start e // HLSL Change End
DXBC e DXIL consumidos pelos drivers Direct3D
- Como cada fabricante de GPU tem arquitetura e exigências diferentes, o binário nativo executado ao final a partir de HLSL também varia entre GPUs Intel, NVIDIA e AMD
- A Microsoft fornece APIs de frontend como Direct3D e HLSL, enquanto IHVs como Intel, AMD e NVIDIA escrevem os drivers que fazem a ligação com algo próximo da ISA do hardware
- No DirectX 9~11, os drivers consomem DXBC
- Desenvolvedores de jogos compilam HLSL para DXBC via CLI
fxc.exe ou API d3dcompiler
- O driver converte o DXBC em um binário executável na GPU real
- DXBC era um formato proprietário e não público usado entre a Microsoft e fabricantes de drivers de GPU
- A partir do DirectX 12 e do Shader Model 6.0, o DXIL passou a ser o formato oficial consumido por fabricantes de drivers para DirectX 12
- O DXIL é mais próximo de um bitcode gerado após codegen e passes de otimização do LLVM 3.7, com um pequeno contêiner/wrapper customizado por cima
- A documentação de DXIL depende menos de uma especificação independente e mais do bitcode que o fork LLVM 3.7 da Microsoft realmente emite após as mudanças de HLSL e otimizações
O plano abandonado do DXIR e a transição para o LLVM upstream
- Na época do DirectX 12 e do Shader Model 6.0, a Microsoft pretendia criar um IR de alto nível e não otimizado chamado DXIR, que o DXC então reduziria para o DXIL otimizado
- Em 2021, foram removidas expressões que insinuavam essa possibilidade de criar o DXIR
- Em 2019, um funcionário da Microsoft respondeu que quase não havia documentação sobre o processo de redução de DXIR para DXIL, e que o DXIR não era um formato oficial, sendo mais próximo do primeiro LLVM IR após o CodeGen
- Em 2023, um funcionário da Microsoft afirmou que o fork LLVM do DXC removeu ou danificou boa parte da camada de geração de código e da infraestrutura do LLVM
- Para adicionar geração de DXBC ao DXC, seria necessário restaurar funcionalidades quebradas do LLVM, e isso não seria tratado no DXC
- Desde março de 2022, a Microsoft vem propondo e conduzindo o upstream do suporte à compilação HLSL para o tronco principal do LLVM/Clang
- Esse plano de transição também inclui readicionar suporte à escrita legada de LLVM v3.7 bitcode no LLVM/Clang moderno
O peso de distribuir DXC para WebGPU e engines de jogo
- Camadas de abstração gráfica que tentam unificar APIs modernas como Metal, Direct3D 12 e Vulkan também precisam de uma linguagem de shader unificada
- Implementações atuais de WebGPU às vezes miram, no longo prazo, um caminho que emita DXIL diretamente, mas na prática a maioria ainda não faz isso
- O fluxo típico de WebGPU é o seguinte
- A linguagem textual WGSL é convertida em HLSL em tempo de execução
- O HLSL é compilado por um compilador HLSL para DXBC ou DXIL
- O DXBC/DXIL otimizado é entregue ao driver gráfico, que o converte para representação intermediária específica do fabricante e código de máquina
- No Vulkan/SPIR-V, os drivers também precisam compilar SPIR-V para binário nativo
- Alguns drivers podem assumir que o SPIR-V já está otimizado, mas isso varia conforme a GPU móvel ou de desktop
- O Fossilize, da Valve, mantém caches de binários reais compilados pelo driver para combinações de GPU e versão de driver
- Enquanto o DXIL é sempre LLVM bitcode após passes de otimização, o SPIR-V pode ou não estar otimizado
- Só o Apple Metal oferece uma API que compila diretamente para o formato binário nativo do hardware de destino real
As escolhas impostas por dxcompiler.dll e dxil.dll
- Runtimes WebGPU fazem a conversão WGSL→HLSL→DXIL em tempo de execução, então precisam escolher entre o DXC novo e o FXC antigo
- Segundo a documentação do Bevy, o FXC é antigo, lento e sem manutenção, mas não exige distribuição de DLLs extras
- Já o DXC é mais novo, mais rápido e mantido, mas exige distribuir
dxcompiler.dll e dxil.dll junto com a aplicação
- Esse problema de escolha afeta não só o Bevy, mas também usuários Rust de
wgpu e usuários de WebGPU Dawn
- Como resultado, muito software acaba adotando o FXC antigo, lento e sem manutenção como padrão
Por que o link estático do DXC é difícil
- O fork LLVM da Microsoft não oferece suporte a linkagem estática
- Se
SHARED for trocado por STATIC nos arquivos CMake, são geradas cerca de 15 bibliotecas estáticas, o que oferece uma experiência de link pior do que uma biblioteca única
- Ao tentar usar bibliotecas
OBJECT do CMake, as mudanças de HLSL da Microsoft revelam interdependências implícitas além das dependências lógicas
- Parte da implementação das interfaces COM do DXC foi projetada para carregar dinamicamente
dxcompiler.dll e dxil.dll e chamar a si mesma
- Não é fácil criar um DXC estático apenas mudando opções de build
O dxil.dll proprietário e a assinatura de shaders
- O
dxil.dll não é gerado ao compilar o DirectXShaderCompiler a partir do código-fonte, mas é distribuído nos releases do GitHub para Windows x86/Arm e Linux x86
- Segundo a especificação da API D3D12 Shader Cache, o D3D12 só aceita shaders assinados, e se houver otimização ou patch em tempo de execução, o shader precisa ser revalidado e assinado novamente
- No release preview do Shader Model 6.8,
dxil.dll/libdxil.so não é fornecido
- O DXIL direcionado a SM6.8 gerado por esse compilador não é final e não pode ser validado
- Não há suporte para distribuição ou execução em máquinas fora do modo de desenvolvedor
- Sem o
dxil.dll, os shaders não são assinados/validados
- Shaders não assinados/validados não podem rodar em máquinas Windows que não estejam em Developer Mode
Compilação offline e restrições de plataforma
- A Mach quer evitar, quando possível, a distribuição de dependências pesadas do DXC e realizar compilação offline de shaders
- A Microsoft distribui
dxil.dll apenas para Windows x86/Arm e Linux x86
- Não há binários para Linux aarch64 nem para macOS
- Por isso, não é possível criar builds multiplataforma de jogos para Windows a partir do macOS nem fazer compilação offline de shaders DirectX em pipelines de CI com Linux Arm
- Para executar o binário proprietário de assinatura, é necessário usar uma máquina Windows ou Linux x86_64
O que o mach-dxcompiler mudou
- Cerca de 10,5 mil linhas do sistema de build original em CMake foram reescritas em
build.zig do Zig
- Só as duas partes que os consumidores mais precisam — a biblioteca
dxcompiler.dll e o binário dxc.exe para compilação/testes offline — foram mantidas como alvos de build
- O resultado foi condensado em cerca de 1 mil linhas de lógica em
build.zig
- O código da Microsoft foi bifurcado para corrigir a expectativa do DXC de que
dxcompiler.dll e dxil.dll existam
- Simula o entrypoint da DLL
- Desativa a saída de informações de versão do compilador derivadas da DLL
- Emula o carregamento de ponteiros de função de bibliotecas dinâmicas
- O
mach-dxcompiler é configurado de forma a não depender de dxil.dll
- Em uma máquina macOS, ele consegue compilar shaders HLSL sem o
dxil.dll proprietário e gerar arquivos byte-for-byte idênticos ao bytecode DXIL que roda em máquinas Windows comuns
Resultados e modo de uso
- O release inclui binários pré-compilados da biblioteca estática
dxcompiler e da CLI dxc
- Não há dependência do
dxil.dll proprietário
- Os alvos compilados no pipeline de CI são os seguintes
- macOS: Apple Silicon aarch64 e Intel x86_64
- Linux: musl e glibc, aarch64 e x86_64
- Windows: x86_64 e aarch64, incluindo ABI MinGW/GNU
- A biblioteca expõe uma pequena API em C como alternativa à API COM existente
- Desenvolvedores de jogos em Zig podem usar a API Zig do repositório, com exemplos de uso nos testes de
src/main.zig
- Por padrão, usa-se o download dos binários pré-compilados
- O build a partir do código-fonte pode ser feito no repositório mach-dxcompiler apenas com
zig e git, exigindo a versão especificada do Zig
git clone https://github.com/hexops/mach-dxcompiler
cd mach-dxcompiler/
zig build -Dfrom_source -Dtarget=aarch64-macos
zig build -Dfrom_source -Dtarget=x86_64-windows-gnu
zig build -Dfrom_source -Dtarget=x86_64-linux-gnu
Limitações atuais e condições de manutenção
- Binários Windows com ABI MSVC não são gerados no momento por causa de um pequeno bug no binding em C
- Binários Linux musl são gerados, mas ainda não foram testados
- Como o Mach engine pretende usar o próprio Zig como linguagem de shader em vez de HLSL, o suporte à saída SPIR-V não é compilado nem há planos adicionais para isso
- Não há plano atual para atualizar o suporte recentemente lançado ao SM6.7
- Partes do sistema de build em CMake do LLVM ainda não foram totalmente migradas, e detalhes relacionados permanecem em
generated-include/
- Este projeto existe para resolver o problema da Mach, e hoje a estrutura é de uma única pessoa cuidando das issues
- Se surgir um caminho melhor, o projeto pode ser marcado como deprecated
1 comentários
Opiniões do Hacker News
É um texto que resume muito bem o quanto a base da compilação de shaders entre APIs 3D é bagunçada
Ele foca em D3D e na Microsoft, mas as outras APIs 3D não são muito melhores. Por exemplo, em um host Linux não é possível fazer cross-compile de shaders Metal; isso só é possível no macOS e em versões relativamente recentes do Windows
Se a equipe do Mach conseguir tornar a ideia de usar Zig como compilador de shaders para múltiplas APIs 3D tão fluida quanto “Zig como toolchain de cross-compilação”, isso pode ser a maior coisa em computação gráfica desde mais ou menos 1995
Isso também tem relação com o Godot
Dizem que “o motivo de ter sido tornado opcional é que o suporte a Direct3D 12 atualmente depende da distribuição, junto com o Godot, da biblioteca proprietária dxil.dll do DirectX Shader Compiler, e distribuir software proprietário vai contra a missão do projeto Godot”
https://godotengine.org/article/dev-snapshot-godot-4-3-dev-3...
Eu quero que minha GPU, Wi‑Fi e Bluetooth funcionem, então seria bom deixar isso marcado por padrão
Sobre a parte “não é necessário distribuir uma .dll adicional junto com a aplicação”, muitos videogames já distribuem middleware proprietário como Bink, SpeedTree e PhysX dessa forma
A maioria dos launchers, como Steam, GOG e Epic, também exige suas próprias .DLLs, e muitos jogos usam D3D11On12. Há muitos jogos lançados cuja lista de arquivos instalados inclui dxil.dll
Então, sinceramente, fico curioso: qual é o problema de distribuir mais uma DLL? O trabalho de engenharia reversa e reimplementação da assinatura de código aqui é excelente, e é especialmente impressionante que a saída seja idêntica bit a bit à da dxil.dll. Mas eu sou absurdamente preguiçoso e provavelmente teria escolhido o caminho mais fácil: distribuir a DLL
Por exemplo, a engine Mach poderia usá-la ao criar um compilador que compila código Zig para shaders em várias plataformas-alvo, e o usuário final poderia usar isso diretamente dentro dos recursos centrais da Mach, sem configuração ou dependências adicionais
Mesmo em jogos AAA, e também em outros jogos e softwares, isso aumenta as dependências adicionais que podem quebrar fora do seu controle. Em uma empresa onde trabalhei antes, recebíamos certo middleware apenas na forma de DLLs e bibliotecas e precisávamos linká-lo, então gastávamos mais esforço em atualizações do Visual Studio e também precisávamos obter novas versões. Como a empresa do middleware não tinha feito a atualização do lado dela, acabamos tendo que fazer até QA de compatibilidade com o novo VS
Claro que ter o código-fonte não torna as atualizações livres de atrito, mas reduz muito esse atrito e elimina a necessidade de esperar por terceiros. O Visual Studio recente parece ter tentado manter compatibilidade retroativa para bibliotecas C++ binárias, mas não vejo isso como algo em que se possa confiar no longo prazo
Além disso, tudo isso parte da premissa de que o código permanece na mesma plataforma e no mesmo alvo. Em algum momento, pode ser que você queira lidar com outra plataforma como host ou alvo, e, sem código-fonte, isso pode se tornar extremamente difícil ou impossível. Para algo específico de plataforma como DXIL, isso pode não parecer um grande problema, mas o texto também diz que, por causa da natureza de blob binário do DXIL, a pré-compilação de shaders era impossível fora das arquiteturas específicas de Windows e Linux para as quais a Microsoft fornecia a DLL
A “assinatura”[1] feita pelo DXIL.dll é, no fim das contas, apenas um MD5 modificado?
1: https://github.com/hexops/DirectXShaderCompiler/blob/4190bb0...
Quem leu o artigo com atenção até ali já podia perceber que tinha de ser um hash básico ou algo parecido; no pior caso, bastaria alguém fazer engenharia reversa do assembly
Depois de todo esse esforço, dá para criticar a Microsoft abertamente sem problema. Ainda mais quando se trata de código open source que qualquer pessoa interessada pode fuçar e descobrir; obrigado ao msk por ter encontrado
https://github.com/baldurk/renderdoc/blob/4a620bb5a16b4de4e2...
Gosto desta citação[0] da Microsoft. Ela diz que, como o fork do LLVM do DXC removeu ou quebrou boa parte da camada e da infraestrutura de geração de código do LLVM, dar suporte à geração de DXBC no DXC exigiria um grande trabalho para corrigir e restaurar os recursos quebrados do LLVM
Como a escala do problema é grande e os recursos da equipe são limitados, eles não vão resolver isso no novo compilador DXC; no futuro, o Clang pode vir a oferecer suporte à geração de DXBC, mas, por enquanto, o foco está em DXIL e SPIR-V, então deve ser difícil começar por alguns anos. É refrescante ver alguém deixar claro o que não vai fazer
[0] https://github.com/microsoft/DirectXShaderCompiler/issues/57...
Recomendo muito dar uma olhada no ecossistema Mach. Em especial, o mach-sysgpu é uma reimplementação completa do WebGPU, e foi escrito em grande parte por Ali Chraghi, de 17 anos
Do lado do SDL, estão criando uma linguagem de shaders no formato SDL_gpu, diferente das existentes, para incluir no SDL3. Venho acompanhando com atenção há algum tempo, porque pode se tornar uma forma multiplataforma de lidar com gráficos 3D para jogos
A diferença é que o SDL_gpu ainda está em estágio inicial, enquanto o WebGPU já tem duas boas implementações públicas
Uma abordagem menos problemática talvez seja algo como HLSL/GLSL → SPIR-V ↔ DXIL, ou escrever os shaders diretamente em SPIR-V
O vkd3d do Wine parece ter um conversor de DXIL → SPIR-V e, por ser uma linguagem intermediária bem mais simples que um conversor de linguagens de shading de alto nível, pode ser mais robusto
Ainda assim, fico me perguntando se existe um compilador HLSL → DXIL baseado em C99, puro e simples, que possa ser compilado sem esse monstro de LLVM, GCC ou Clang
Respondendo à pergunta: não existe. A conversão de HLSL para DXIL está, na prática, nas mãos da Microsoft, e houve pouquíssimos esforços para sair disso
Usar o próprio Zig como linguagem de shading é legal. Zig é de fato uma linguagem única para tudo. Também é um sistema de build e também é uma linguagem de shading!