- La API gráfica experimental sysgpu del motor Mach necesitaba artefactos de shaders para Direct3D 12, y reconstruyó Microsoft DXC en una forma estática y fácil de usar en varias plataformas
- DXIL de Direct3D 12 se parece más a LLVM bitcode posprocesado emitido por el fork de LLVM/Clang 3.7 de Microsoft, una estructura que depende de la “salida real del compilador” más que de una especificación
- Los runtimes de la familia WebGPU suelen convertir WGSL a HLSL y luego generar DXBC/DXIL con FXC o DXC, pero por la carga de distribuir DXC, es fácil que el viejo y lento FXC quede como valor predeterminado
- El DXC existente es difícil de enlazar de forma estática, y
dxil.dll, un binario propietario de firma/verificación, se distribuye principalmente para Windows y Linux x86, lo que bloquea la compilación offline de shaders DirectX en CI sobre macOS o Arm Linux
mach-dxcompiler reescribe la compilación con CMake como build.zig de Zig y elimina la dependencia de DLLs para ofrecer una biblioteca estática dxcompiler y la CLI dxc, aunque siguen existiendo limitaciones en MSVC ABI, pruebas con musl, salida SPIR-V y soporte para SM6.7
Por qué Mach reconstruyó DXC
- Mach engine está creando en Zig una API gráfica experimental llamada sysgpu, con el objetivo de soportar backends Metal, Vulkan, Direct3D y OpenGL
- En el backend Direct3D 12, los programas de shaders deben compilarse a un formato que Direct3D 12 pueda consumir
- En este proceso quedó claro que el compilador de shaders DirectX de Microsoft, DXC, genera para los desarrolladores de juegos una experiencia de distribución compleja e incómoda
La compilación de shaders DirectX pasó de FXC a DXC
- La API gráfica DirectX usa HLSL como lenguaje de sombreado
- El compilador de HLSL anterior a Direct3D 11 se llamaba FXC, es decir, effects compiler
- Entre desarrolladores de juegos, FXC es conocido como un compilador lento y con mala calidad de generación de código
- Puede ser desfavorable tanto en velocidad de compilación de shaders como en rendimiento de ejecución
- Con Direct3D 12 y Shader Model 6.0, Microsoft declaró oficialmente deprecated al FXC incluido en Windows OS e introdujo DXC, un fork basado en LLVM/Clang v3.7
- DXC está publicado como Microsoft/DirectXShaderCompiler, y Microsoft también distribuye binarios precompilados
- En el fork de LLVM de Microsoft, los cambios relacionados con HLSL están marcados con los comentarios
// HLSL Change Start y // HLSL Change End
DXBC y DXIL que consumen los drivers Direct3D
- Como cada fabricante de GPU tiene una arquitectura de hardware y requisitos distintos, el binario nativo en el que finalmente se ejecuta HLSL también difiere entre GPUs Intel, NVIDIA y AMD
- Microsoft ofrece APIs de frontend como Direct3D y HLSL, y los IHV como Intel, AMD y NVIDIA escriben drivers que las conectan con una forma cercana al ISA del hardware
- En DirectX 9~11, los drivers consumen DXBC
- Los desarrolladores de juegos compilan HLSL a DXBC con la CLI
fxc.exe o la API d3dcompiler
- El driver convierte DXBC al binario que se ejecutará en la GPU real
- DXBC es un formato propietario no público usado entre Microsoft y los fabricantes de drivers de GPU
- Desde DirectX 12 y Shader Model 6.0, DXIL es el formato oficial que consumen los fabricantes de drivers DirectX 12
- DXIL se parece a un formato de bitcode posterior a la generación de código y los pases de optimización de LLVM 3.7, con un pequeño contenedor/wrapper personalizado adicional
- La documentación de DXIL, más que una especificación separada, depende del bitcode que el fork de LLVM 3.7 de Microsoft emite realmente después de los cambios y optimizaciones de HLSL
El plan DXIR desaparecido y la transición al LLVM upstream
- En la época de DirectX 12 y Shader Model 6.0, Microsoft planeó crear DXIR, un IR de alto nivel y no optimizado, y hacer que DXC lo bajara a DXIL optimizado
- En 2021 se eliminó una frase que insinuaba la posibilidad de crear DXIR
- En 2019, un empleado de Microsoft respondió que casi no había documentación del proceso para bajar de DXIR a DXIL, y que DXIR no era un formato oficial sino algo más cercano al primer LLVM IR después de CodeGen
- En 2023, un empleado de Microsoft afirmó que el fork de LLVM de DXC había eliminado o dañado gran parte de la capa de generación de código y la infraestructura de LLVM
- Para agregar generación de DXBC a DXC habría que restaurar las funciones rotas de LLVM, y DXC no se ocuparía de eso
- Desde marzo de 2022, Microsoft propuso y está avanzando con el trabajo de upstream del soporte de compilación HLSL a la rama principal de LLVM/Clang
- Este plan de transición también incluye volver a agregar en LLVM/Clang moderno soporte para escritura de bitcode legacy de LLVM v3.7
La carga de distribuir DXC en WebGPU y motores de juegos
- Las capas de abstracción gráfica que buscan unificar APIs gráficas modernas como Metal, Direct3D 12 y Vulkan también necesitan un lenguaje de sombreado unificado
- Las implementaciones actuales de WebGPU en algunos casos apuntan a largo plazo a una ruta que emita DXIL directamente, pero en la práctica la mayoría no lo hace
- La ruta típica de WebGPU es la siguiente
- Convierte en runtime el lenguaje de texto WGSL a HLSL
- Compila HLSL a DXBC o DXIL con un compilador HLSL
- Entrega el DXBC/DXIL optimizado al driver gráfico, y el driver lo convierte a una representación intermedia y código máquina específicos del proveedor
- Vulkan/SPIR-V también tiene una estructura en la que el driver debe compilar SPIR-V a un binario nativo
- Algunos drivers pueden asumir que SPIR-V está optimizado, pero depende de la GPU móvil o de escritorio
- Fossilize de Valve mantiene una caché de los binarios reales compilados por el driver para cada combinación de GPU y versión de driver
- DXIL siempre es LLVM bitcode después de pases de optimización, mientras que SPIR-V puede estar optimizado o no
- Solo Apple Metal soporta una API que compila directamente al formato binario nativo del hardware de destino real
Las opciones que crean dxcompiler.dll y dxil.dll
- Como los runtimes WebGPU realizan la conversión WGSL→HLSL→DXIL en runtime, deben elegir entre el nuevo DXC y el viejo FXC
- Según la documentación de Bevy, FXC es viejo, lento y no mantenido, pero no requiere distribuir DLLs adicionales
- En cambio, DXC es nuevo, rápido y mantenido, pero hay que distribuir
dxcompiler.dll y dxil.dll junto con la aplicación
- Este problema de elección afecta no solo a Bevy, sino también a usuarios de Rust de
wgpu y usuarios de Dawn WebGPU
- Como resultado, mucho software termina usando como valor predeterminado el viejo, lento y no mantenido FXC
Por qué es difícil enlazar DXC de forma estática
- El fork de LLVM de Microsoft no soporta enlace estático
- Si en los archivos CMake se cambia
SHARED por STATIC, se generan unas 15 bibliotecas estáticas, pero la experiencia de enlace es peor que con una sola biblioteca
- Al intentar usar bibliotecas
OBJECT de CMake, los cambios HLSL de Microsoft revelan interdependencias implícitas al margen de las dependencias lógicas
- Algunas implementaciones de la interfaz COM de DXC están diseñadas para cargar
dxcompiler.dll y dxil.dll como bibliotecas dinámicas y llamarse a sí mismas
- No basta con cambiar simplemente la configuración de compilación para crear un DXC estático
El dxil.dll propietario y la firma de shaders
dxil.dll no se genera aunque se compile DirectXShaderCompiler desde el código fuente, pero se distribuye en los releases de GitHub para Windows x86/Arm y Linux x86
- Según la D3D12 Shader Cache API specification, D3D12 solo acepta shaders firmados, y si se realiza optimización o parcheo en runtime, el shader debe volver a verificarse y firmarse
- En el release preview de Shader Model 6.8 no se proporciona
dxil.dll/libdxil.so
- El DXIL objetivo SM6.8 generado por ese compilador no es final y no puede verificarse
- No se soporta su distribución ni ejecución en máquinas que no estén en modo desarrollador
- Sin
dxil.dll, los shaders no se firman ni verifican
- Los shaders no firmados/verificados no pueden ejecutarse si la máquina Windows no está en Developer Mode
Compilación offline y restricciones de plataforma
- Mach quiere evitar la distribución de la pesada dependencia de DXC cuando sea necesario y realizar compilación offline de shaders
- Microsoft distribuye
dxil.dll solo para Windows x86/Arm y Linux x86
- No se ofrecen binarios para Linux aarch64 ni para macOS
- Por lo tanto, no es posible crear builds de juegos multiplataforma para Windows desde macOS ni realizar compilación offline de shaders DirectX en pipelines de CI sobre Arm Linux
- Para ejecutar el binario propietario de firma se necesita una máquina Windows o Linux x86_64
Qué cambió mach-dxcompiler
- Reescribió unas 10.5k líneas del sistema de compilación CMake existente como
build.zig de Zig
- Tomó como objetivos de compilación solo las dos partes que los consumidores necesitan principalmente: la biblioteca
dxcompiler.dll y el binario dxc.exe para compilación offline/pruebas
- Como resultado, quedó organizado en unas 1k líneas de lógica
build.zig
- Hizo un fork del codebase de Microsoft para modificar la estructura en la que DXC espera que existan
dxcompiler.dll y dxil.dll
- Simula el entrypoint de la DLL
- Deshabilita la función que muestra información de versión del compilador proveniente de la DLL
- Emula la carga de punteros a funciones de bibliotecas dinámicas
mach-dxcompiler está configurado sin depender de dxil.dll
- En una máquina macOS, sin el
dxil.dll propietario, puede compilar shaders HLSL y producir archivos byte-for-byte idénticos al bytecode DXIL que se ejecuta en máquinas Windows normales
Resultados y uso
- El release incluye binarios precompilados de la biblioteca estática
dxcompiler y la CLI dxc
- No depende del
dxil.dll propietario
- Los targets compilados en el pipeline de CI son los siguientes
- macOS: Apple Silicon aarch64 e Intel x86_64
- Linux: musl y glibc, aarch64 y x86_64
- Windows: x86_64 y aarch64, incluido el ABI MinGW/GNU
- La biblioteca expone una pequeña API C como alternativa a la API COM existente
- Los desarrolladores de juegos en Zig pueden usar la API Zig del repositorio, y pueden ver un ejemplo de uso en la prueba
src/main.zig
- De forma predeterminada, se descargan y usan binarios precompilados
- La compilación desde código fuente puede realizarse en el repositorio mach-dxcompiler solo con
zig y git, y requiere la versión especificada de 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
Limitaciones actuales y condiciones de mantenimiento
- Los binarios para Windows MSVC ABI actualmente no se compilan debido a un pequeño bug en el binding C
- Los binarios Linux musl se compilan, pero todavía no han sido probados
- Como Mach engine planea usar el propio Zig como lenguaje de sombreado, no HLSL, no compila soporte de salida SPIR-V ni tiene planes de agregarlo
- Actualmente no hay planes de actualización para soportar el recién lanzado SM6.7
- Parte del sistema de compilación CMake de LLVM todavía no se ha migrado por completo, y quedan detalles relacionados en
generated-include/
- Este proyecto existe para resolver el problema de Mach, y actualmente una sola persona se encarga de los issues
- Si se encuentra una mejor ruta, el proyecto podría quedar deprecated
1 comentarios
Opiniones de Hacker News
Es un artículo que resume bien lo desordenado que está el trasfondo de la compilación de shaders entre APIs 3D
Se enfoca en D3D y Microsoft, pero las demás APIs 3D no están mucho mejor. Por ejemplo, en un host Linux no se pueden compilar shaders de Metal de forma cruzada; solo es posible en macOS y en versiones relativamente recientes de Windows
Si el equipo de Mach logra que su idea de usar Zig como compilador de shaders para APIs 3D cruzadas sea tan fluida como “Zig como toolchain de compilación cruzada”, podría ser lo más importante que le haya pasado a la computación gráfica desde más o menos 1995
Esto también está relacionado con Godot
Dicen que “la razón por la que se hizo opcional es que el soporte de Direct3D 12 actualmente depende de distribuir junto con Godot la biblioteca propietaria dxil.dll de DirectX Shader Compiler, y la distribución de software propietario va en contra de la misión del proyecto Godot”
https://godotengine.org/article/dev-snapshot-godot-4-3-dev-3...
Quiero que mi GPU, Wi‑Fi y Bluetooth funcionen, así que preferiría que estuviera marcado por defecto
Sobre la parte de que “no hace falta distribuir un .dll adicional junto con la aplicación”, muchos videojuegos ya distribuyen middleware propietario como Bink, SpeedTree o PhysX de esa forma
La mayoría de los launchers como Steam, GOG y Epic también requieren sus propios .DLL, y muchos juegos usan D3D11On12. También hay muchos juegos publicados que incluyen dxil.dll en la lista de archivos instalados
Así que, honestamente, me da curiosidad: ¿cuál es el problema de distribuir un DLL más? El trabajo de ingeniería inversa y reimplementación de la firma de código que hicieron aquí es excelente, y es especialmente impresionante que la salida sea idéntica bit a bit a la de dxil.dll. Pero yo soy absurdamente flojo, así que probablemente habría elegido el camino más fácil: distribuir el DLL
Por ejemplo, el motor Mach puede usarla al crear un compilador que compile código Zig a shaders para varias plataformas objetivo, y el usuario final puede usarla directamente dentro de la funcionalidad central de Mach, sin configuración ni dependencias adicionales
Incluso en juegos AAA, y también en otros juegos y software, se acumulan dependencias adicionales que pueden romperse fuera de tu control. En una empresa donde trabajé antes, teníamos que enlazar cierto middleware que solo recibíamos en forma de DLL y bibliotecas, así que invertíamos más esfuerzo al actualizar Visual Studio y también teníamos que conseguir versiones nuevas. Como la empresa de ese middleware no había hecho sus propias actualizaciones, terminamos haciendo incluso QA de compatibilidad con la nueva versión de VS
Claro que tener el código fuente no hace que las actualizaciones sean sin fricción, pero reduce mucho la fricción y evita tener que esperar a otros. Parece que las versiones recientes de Visual Studio intentan mantener compatibilidad hacia atrás con bibliotecas binarias de C++, pero no creo que sea algo de lo que se pueda depender a largo plazo
Además, todo esto asume que el código se queda en la misma plataforma y objetivo. En algún momento puede que quieras manejar otra plataforma como host o como objetivo, y sin código fuente eso puede volverse extremadamente difícil o imposible. Para algo específico de plataforma como DXIL puede no parecer un gran problema, pero el artículo también dice que, por la naturaleza de blob binario de DXIL, la precompilación de shaders era imposible fuera de las arquitecturas específicas de Windows y Linux para las que Microsoft proporcionaba el DLL
¿La “firma”[1] que hace DXIL.dll al final no es más que un MD5 modificado?
1: https://github.com/hexops/DirectXShaderCompiler/blob/4190bb0...
Quien hubiera leído el artículo con atención hasta ese punto ya podía darse cuenta de que tenía que ser un hash básico o algo parecido, y en el peor de los casos bastaba con que alguien hiciera ingeniería inversa del ensamblado.
Después de todo ese esfuerzo, bien podrían criticar públicamente a Microsoft. Sobre todo si se trata de código open source que cualquiera interesado puede investigar y encontrar; gracias a msk por encontrarlo.
https://github.com/baldurk/renderdoc/blob/4a620bb5a16b4de4e2...
Me gusta esta cita[0] de Microsoft. Dice que, como el fork de LLVM de DXC eliminó o rompió gran parte de la capa de generación de código y la infraestructura de LLVM, dar soporte a la generación de DXBC en DXC requeriría un trabajo enorme para arreglar y restaurar las funciones rotas de LLVM.
Como la escala del problema es grande y los recursos del equipo son limitados, no van a resolverlo en el nuevo compilador DXC; y aunque en el futuro Clang podría soportar la generación de DXBC, por ahora están enfocados en soportar la generación de DXIL y SPIR-V, así que sería difícil siquiera empezar durante varios años. Es refrescante que digan claramente lo que no van a hacer.
[0] https://github.com/microsoft/DirectXShaderCompiler/issues/57...
Recomiendo mucho echarle un vistazo al ecosistema de Mach. En particular, mach-sysgpu es una reimplementación completa de WebGPU, y la mayor parte fue escrita por Ali Chraghi, de 17 años.
En SDL están creando un lenguaje de shaders distinto, en forma de SDL_gpu, para incluirlo en SDL3. Lo he estado siguiendo con atención desde hace un tiempo porque podría convertirse en una forma multiplataforma de manejar gráficos 3D para juegos.
La diferencia sería que SDL_gpu todavía está en una etapa temprana, mientras que WebGPU ya tiene dos buenas implementaciones públicas.
La forma menos problemática probablemente sea algo como HLSL/GLSL → SPIR-V ↔ DXIL, o escribir los shaders directamente en SPIR-V.
Parece que vkd3d de Wine tiene un conversor de DXIL → SPIR-V, y al ser un lenguaje intermedio mucho más simple que un conversor de lenguajes de shading de alto nivel, podría ser más robusto.
Aun así, me pregunto si existe un compilador HLSL → DXIL basado en C99, puro y simple, que pueda compilarse sin GCC ni Clang, en lugar de este monstruo de LLVM.
Para responder la pregunta: no existe. La conversión de HLSL a DXIL está básicamente en manos de Microsoft, y casi no ha habido esfuerzos por salirse de ahí.
Usar Zig como lenguaje de shading en sí es genial. Zig es verdaderamente un lenguaje único para todo. ¡También es un sistema de build y un lenguaje de shading!