3P by GN⁺ | ★ favorite | 댓글 1개
  • Mach engine의 실험적 그래픽 API sysgpu는 Direct3D 12용 셰이더 산출물이 필요했고, Microsoft DXC를 정적·크로스플랫폼으로 쓰기 쉬운 형태로 다시 빌드함
  • Direct3D 12의 DXIL은 Microsoft의 LLVM/Clang 3.7 포크가 내보내는 후처리 LLVM bitcode에 가까워, 명세보다 “실제 컴파일러 출력”에 의존하는 구조임
  • WebGPU 계열 런타임은 보통 WGSL을 HLSL로 변환한 뒤 FXC나 DXC로 DXBC/DXIL을 만들지만, DXC 배포 부담 때문에 오래되고 느린 FXC가 기본값이 되기 쉬움
  • 기존 DXC는 정적 링크가 어렵고 dxil.dll이라는 독점 서명/검증 바이너리가 Windows·x86 Linux 중심으로만 배포되어 macOS나 Arm Linux CI에서 오프라인 DirectX 셰이더 컴파일이 막힘
  • mach-dxcompiler는 CMake 빌드를 Zig build.zig로 다시 작성하고 DLL 의존을 제거해 정적 dxcompiler 라이브러리와 dxc CLI를 제공하지만, MSVC ABI·musl 테스트·SPIR-V 출력·SM6.7 지원에는 제한이 남아 있음

Mach가 DXC를 다시 빌드한 이유

  • Mach engine은 Zig로 sysgpu라는 실험적 그래픽 API를 만들고 있으며, Metal, Vulkan, Direct3D, OpenGL 백엔드 지원을 목표로 함
  • Direct3D 12 백엔드에서는 셰이더 프로그램을 Direct3D 12가 소비할 수 있는 형식으로 컴파일해야 함
  • 이 과정에서 Microsoft의 DirectX 셰이더 컴파일러 DXC가 게임 개발자에게 복잡하고 불편한 배포 경험을 만든다는 점이 드러남

FXC에서 DXC로 넘어간 DirectX 셰이더 컴파일

  • DirectX 그래픽 API는 셰이딩 언어로 HLSL을 사용함
  • Direct3D 11 이전 HLSL 컴파일러는 FXC, 즉 effects compiler로 불렸음
  • FXC는 게임 개발자 사이에서 느리고 코드 생성 품질이 좋지 않은 컴파일러로 알려져 있음
    • 셰이더 컴파일 속도와 실행 성능 모두에서 불리할 수 있음
  • Direct3D 12와 Shader Model 6.0에서 Microsoft는 Windows OS에 포함되던 FXC를 공식적으로 deprecated 처리하고, LLVM/Clang v3.7 기반 포크인 DXC를 도입함
  • DXC는 Microsoft/DirectXShaderCompiler로 공개되어 있으며, Microsoft는 사전 빌드 바이너리도 배포함
  • Microsoft의 LLVM 포크에는 HLSL 관련 변경이 // HLSL Change Start, // HLSL Change End 주석으로 표시되어 있음

Direct3D 드라이버가 소비하는 DXBC와 DXIL

  • GPU 제조사마다 하드웨어 구조와 요구사항이 달라, HLSL이 최종적으로 실행되는 네이티브 바이너리도 Intel, NVIDIA, AMD GPU마다 달라짐
  • Microsoft는 Direct3D와 HLSL 같은 프런트엔드 API를 제공하고, Intel·AMD·NVIDIA 같은 IHV가 드라이버를 작성해 이를 하드웨어 ISA에 가까운 형태로 연결함
  • DirectX 9~11에서는 드라이버가 DXBC를 소비함
    • 게임 개발자는 fxc.exe CLI나 d3dcompiler API로 HLSL을 DXBC로 컴파일함
    • 드라이버는 DXBC를 실제 GPU에서 실행될 바이너리로 변환함
    • DXBC는 Microsoft와 GPU 드라이버 제조사 사이에서 쓰이던 비공개 독점 형식임
  • DirectX 12와 Shader Model 6.0 이후에는 DXIL이 DirectX 12 드라이버 제조사가 소비하는 공식 형식이 됨
  • DXIL은 LLVM 3.7의 코드 생성과 최적화 패스 이후 bitcode 형식에 작은 커스텀 컨테이너/래퍼를 더한 형태에 가까움
  • DXIL 문서는 별도 명세라기보다, Microsoft의 LLVM 3.7 포크가 HLSL 변경과 최적화 이후 실제로 내보내는 bitcode에 의존함

사라진 DXIR 계획과 LLVM 업스트림 전환

  • Microsoft는 DirectX 12와 Shader Model 6.0 시점에 고수준·비최적화 IR인 DXIR을 만들고, DXC가 이를 최적화된 DXIL로 낮추는 구상을 했음
  • 2021년에는 DXIR을 만들 가능성을 암시하던 표현이 제거됨
  • 2019년 Microsoft 직원은 DXIR에서 DXIL로 낮추는 과정의 문서가 거의 없고, DXIR은 공식 형식이 아니라 CodeGen 이후 첫 LLVM IR에 가깝다고 답함
  • 2023년 Microsoft 직원은 DXC의 LLVM 포크가 LLVM의 코드 생성 계층과 인프라 상당 부분을 제거하거나 손상시켰다고 밝힘
    • DXC에 DXBC 생성을 추가하려면 깨진 LLVM 기능을 복구해야 하며, DXC에서는 이를 다루지 않겠다고 함
  • Microsoft는 2022년 3월부터 HLSL 컴파일 지원을 LLVM/Clang 본류에 업스트림하는 작업을 제안하고 진행 중임
  • 이 전환 계획에는 현대 LLVM/Clang에 레거시 LLVM v3.7 bitcode writing 지원을 다시 추가하는 작업도 포함됨

WebGPU와 게임 엔진의 DXC 배포 부담

  • Metal, Direct3D 12, Vulkan 같은 현대 그래픽 API를 통합하려는 그래픽 추상화 계층에는 통합 셰이딩 언어도 필요함
  • 현재 WebGPU 구현들은 장기적으로 DXIL을 직접 방출하는 경로를 목표로 삼는 경우가 있지만, 실제로는 대부분 그렇게 하지 않음
  • 일반적인 WebGPU 경로는 다음과 같음
    • WGSL 텍스트 언어를 런타임에 HLSL로 변환함
    • HLSL을 HLSL 컴파일러로 DXBC 또는 DXIL로 컴파일함
    • 최적화된 DXBC/DXIL을 그래픽 드라이버에 넘기고, 드라이버가 벤더별 중간 표현과 머신 코드로 변환함
  • Vulkan/SPIR-V도 드라이버가 SPIR-V를 네이티브 바이너리로 컴파일해야 하는 구조임
    • 일부 드라이버는 SPIR-V가 최적화되었다고 가정할 수 있지만, 모바일·데스크톱 GPU에 따라 다름
    • Valve의 Fossilize는 GPU와 드라이버 버전 조합별로 드라이버가 컴파일한 실제 바이너리 캐시를 유지함
  • DXIL은 항상 최적화 패스 이후 LLVM bitcode인 반면, SPIR-V는 최적화된 형태일 수도 있고 아닐 수도 있음
  • Apple Metal만 실제 대상 하드웨어의 네이티브 바이너리 형식으로 직접 컴파일하는 API를 지원함

dxcompiler.dlldxil.dll이 만드는 선택지

  • WebGPU 런타임은 WGSL→HLSL→DXIL 변환을 런타임에 수행하므로, 새 DXC와 오래된 FXC 중 하나를 골라야 함
  • Bevy 문서 기준으로 FXC는 오래되고 느리며 유지보수되지 않지만, 추가 DLL 배포가 필요 없음
  • 반대로 DXC는 새롭고 빠르며 유지보수되지만, 애플리케이션과 함께 dxcompiler.dlldxil.dll을 배포해야 함
  • 이 선택 문제는 Bevy뿐 아니라 wgpu Rust 사용자와 Dawn WebGPU 사용자에게도 영향을 줌
  • 결과적으로 많은 소프트웨어가 오래되고 느리며 유지보수되지 않는 FXC를 기본값으로 삼게 됨

DXC 정적 링크가 어려운 이유

  • Microsoft의 LLVM 포크는 정적 링크를 지원하지 않음
  • CMake 파일에서 SHAREDSTATIC으로 바꾸면 약 15개의 정적 라이브러리가 생기지만, 단일 라이브러리보다 링크 경험이 나쁨
  • CMake OBJECT 라이브러리를 쓰려 하면 Microsoft의 HLSL 변경 때문에 논리적 의존성과 별개로 암묵적 상호 의존성이 드러남
  • DXC의 COM 인터페이스 구현 일부는 dxcompiler.dlldxil.dll을 동적 라이브러리로 로드해 자기 자신을 호출하도록 설계되어 있음
  • 단순히 빌드 설정을 바꾸는 것만으로는 정적 DXC를 만들기 어려움

독점 dxil.dll과 셰이더 서명

  • dxil.dll은 DirectXShaderCompiler를 소스에서 빌드해도 생성되지 않지만, GitHub 릴리스에는 Windows x86/Arm과 Linux x86용으로 배포됨
  • D3D12 Shader Cache API specification에 따르면 D3D12는 서명된 셰이더만 받아들이며, 런타임 최적화나 패치가 수행되면 셰이더를 다시 검증하고 서명해야 함
  • Shader Model 6.8 preview 릴리스에서는 dxil.dll/libdxil.so가 제공되지 않음
    • 해당 컴파일러가 생성한 SM6.8 대상 DXIL은 최종본이 아니고 검증할 수 없음
    • 개발자 모드가 아닌 머신에서 배포나 실행이 지원되지 않음
  • dxil.dll이 없으면 셰이더가 서명/검증되지 않음
  • 서명/검증되지 않은 셰이더는 Windows 머신이 Developer Mode가 아닌 경우 실행될 수 없음

오프라인 컴파일과 플랫폼 제약

  • Mach는 필요할 때 무거운 DXC 의존 배포를 피하고 오프라인 셰이더 컴파일을 수행하고자 함
  • Microsoft는 dxil.dll을 Windows x86/Arm과 Linux x86용으로만 배포함
  • Linux aarch64 바이너리와 macOS 바이너리는 제공되지 않음
  • 따라서 macOS에서 Windows용 크로스플랫폼 게임 빌드를 만들거나 Arm Linux CI 파이프라인에서 오프라인 DirectX 셰이더 컴파일을 수행할 수 없음
  • 독점 서명 바이너리를 실행하려면 Windows 또는 x86_64 Linux 머신이 필요함

mach-dxcompiler가 바꾼 부분

  • 기존 CMake 빌드 시스템 약 10.5k 라인을 Zig의 build.zig로 다시 작성함
  • 소비자가 주로 필요로 하는 두 부분인 dxcompiler.dll 라이브러리와 dxc.exe 오프라인 컴파일/테스트 바이너리만 빌드 대상으로 삼음
  • 결과적으로 약 1k 라인의 build.zig 로직으로 정리됨
  • DXC가 dxcompiler.dlldxil.dll 존재를 기대하는 구조를 수정하기 위해 Microsoft 코드베이스를 포크함
    • DLL entrypoint를 시뮬레이션함
    • DLL에서 유래한 컴파일러 버전 정보 출력 기능을 비활성화함
    • 동적 라이브러리 함수 포인터 로드를 에뮬레이션함
  • mach-dxcompilerdxil.dll에 의존하지 않는 형태로 구성됨
  • macOS 머신에서 독점 dxil.dll 없이 HLSL 셰이더를 컴파일해, 일반 Windows 머신에서 실행되는 DXIL bytecode와 byte-for-byte 동일한 파일을 만들 수 있음

결과물과 사용 방법

  • 릴리스에는 정적 dxcompiler 라이브러리와 dxc CLI의 사전 빌드 바이너리가 포함됨
  • 독점 dxil.dll 의존은 없음
  • CI 파이프라인에서 빌드되는 대상은 다음과 같음
    • macOS: Apple Silicon aarch64와 Intel x86_64
    • Linux: musl과 glibc, aarch64와 x86_64
    • Windows: x86_64와 aarch64, MinGW/GNU ABI 포함
  • 라이브러리는 기존 COM API 대안으로 작은 C API를 노출함
  • Zig 게임 개발자는 저장소의 Zig API를 사용할 수 있으며, src/main.zig 테스트에서 사용 예시를 볼 수 있음
  • 기본적으로 사전 빌드 바이너리를 다운로드해 사용함
  • 소스 빌드는 mach-dxcompiler 저장소에서 ziggit만으로 수행할 수 있으며, 지정된 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

현재 한계와 유지보수 조건

  • Windows MSVC ABI 바이너리는 C 바인딩의 작은 버그 때문에 현재 빌드되지 않음
  • Linux musl 바이너리는 빌드되지만 아직 테스트되지 않음
  • Mach engine은 HLSL이 아니라 Zig 자체를 셰이딩 언어로 쓸 계획이어서 SPIR-V 출력 지원은 빌드하지 않으며 추가 계획도 없음
  • 최근 릴리스된 SM6.7 지원 업데이트 계획은 현재 없음
  • LLVM의 CMake 빌드 시스템 중 일부는 아직 완전히 옮겨지지 않았고, generated-include/에 관련 세부사항이 남아 있음
  • 이 프로젝트는 Mach의 문제를 해결하기 위해 존재하며, 현재 한 사람이 이슈를 처리하는 구조임
  • 더 나은 경로를 찾으면 프로젝트가 deprecated될 수 있음

댓글과 토론

Hacker News 의견들
  • 크로스 3D API 셰이더 컴파일 밑바닥이 얼마나 엉망인지 잘 정리한 글임
    D3D와 Microsoft에 초점을 맞추지만 다른 3D API도 크게 낫지 않음. 예를 들어 Linux 호스트에서는 Metal 셰이더를 크로스 컴파일할 수 없고, macOS와 비교적 최근의 Windows에서만 가능함
    Mach 팀이 Zig를 크로스 3D API 셰이더 컴파일러로 쓰는 구상을 “크로스 컴파일 툴체인으로서의 Zig”만큼 매끄럽게 만들 수 있다면, 1995년쯤 이후 컴퓨터 그래픽스에서 가장 큰 일이 될 수 있음

    • 그렇게 된다면 Zig에도 큰 호재임. 언어로서도, 툴체인으로서도 힘을 받게 됨
    • Linux 호스트에서 Metal 셰이더를 크로스 컴파일할 수 없다는 건 왜 그런지 궁금함. 그냥 아직 아무도 구현하지 않았다는 뜻 아닌가?
    • Windows에서 가능하다면 Wine으로 Linux에서도 가능하다는 뜻 아닌가?
  • 이것은 Godot와도 관련 있음
    “선택 사항으로 만든 이유는 Direct3D 12 지원이 현재 DirectX Shader Compiler의 독점 dxil.dll 라이브러리를 Godot와 함께 배포하는 데 의존하고 있고, 독점 소프트웨어 배포는 Godot 프로젝트의 사명에 어긋나기 때문”이라고 함
    https://godotengine.org/article/dev-snapshot-godot-4-3-dev-3...

    • dxil/dxc의 정확히 어느 부분이 독점인지 궁금함. https://github.com/microsoft/DirectXShaderCompiler의 라이선스 난장을 이해해 보려는 중
    • 최종 사용자는 이런 걸 전혀 신경 쓰지 않을 것임. 일부 Linux 배포판 설치 때 “서드파티 드라이버를 다운로드하고 설치하시겠습니까”라고 묻는 것과 같음
      내 GPU, Wi-Fi, Bluetooth가 동작하길 원하니 기본적으로 체크해 두면 좋겠음
    • dxil.dll은 관리 코드, 즉 “dotnet” 코드 아닌가?
  • “애플리케이션과 함께 추가 .dll을 배포할 필요는 없다”는 대목에 대해, 이미 많은 비디오 게임은 Bink, SpeedTree, PhysX 같은 독점 미들웨어를 그렇게 배포함
    Steam, GOG, Epic 같은 대부분의 런처도 각자의 .DLL을 요구하고, 많은 게임이 D3D11On12를 사용함. 설치된 파일 목록에 dxil.dll이 들어 있는 출시 게임도 많음
    그래서 솔직히 궁금한데, DLL 하나 더 배포하는 게 무엇이 문제인가? 여기서 코드 서명을 리버스 엔지니어링하고 재구현한 작업은 훌륭하고, 특히 dxil.dll 출력과 비트 단위로 같다는 점은 대단함. 하지만 나는 터무니없이 게을러서 더 쉬운 길인 DLL 배포를 택했을 듯함

    • 오픈소스 구현은 프로그래머가 dxil.dll이 무엇인지 알거나 이해하지 않아도 기존 게임 엔진에 포함될 수 있음
      예를 들어 Mach 엔진은 Zig 코드를 대상 플랫폼 전반의 셰이더로 컴파일하는 컴파일러를 만들 때 이를 쓸 수 있고, 최종 사용자는 추가 설정이나 의존성 없이 Mach 핵심 기능 안에서 바로 사용할 수 있음
    • 게임에는 AAA 게임만 있는 게 아니고, 그래픽 애플리케이션도 게임만 있는 게 아님. 평균적인 100GB AAA 게임에는 괜찮은 일이 itch.io의 30MB 게임, 그중 24MB가 DXC인 경우나 개인 웹사이트의 3D 모델 뷰어·변환기·애니메이터에는 맞지 않을 수 있음
      AAA 게임에서도, 또 다른 게임과 소프트웨어에서도 통제 밖에서 깨질 수 있는 추가 의존성이 늘어남. 예전에 다니던 회사에서는 어떤 미들웨어를 DLL과 라이브러리 형태로만 받아 링크해야 했기 때문에 Visual Studio 업그레이드에 더 많은 노력을 썼고, 새 버전도 받아야 했음. 그 미들웨어 회사가 자기 쪽 업그레이드를 안 해 둔 탓에 우리가 새 VS 호환성 QA까지 하게 됐음
      물론 소스 코드가 있다고 업데이트가 마찰 없이 되는 건 아니지만, 마찰은 훨씬 줄고 남을 기다릴 필요도 없어짐. 최근 Visual Studio는 바이너리 C++ 라이브러리 하위 호환성을 시도한 것으로 보이지만 장기적으로 의존할 수 있는 성질은 아니라고 봄
      또 이 모든 건 코드가 같은 플랫폼과 대상에 머문다는 가정 아래임. 어느 순간 다른 플랫폼을 호스트나 대상으로 다루고 싶어질 수 있고, 소스 코드가 없으면 엄청나게 어렵거나 불가능해질 수 있음. DXIL처럼 플랫폼 특화된 것에는 큰 문제처럼 안 보일 수 있지만, 글에서도 DXIL의 바이너리 블롭성 때문에 Microsoft가 DLL을 제공한 특정 Windows와 Linux 아키텍처 밖에서는 셰이더 사전 컴파일이 불가능했다고 말함
    • 지적한 대로 그 자체는 큰 문제가 아님. 다만 글 끝부분의 진짜 흥미로운 지점은 임의의 OS/아키텍처에서 크로스 빌드할 수 있다는 것임
  • DXIL.dll이 하는 “서명”[1]은 결국 변형된 MD5일 뿐인가?
    1: https://github.com/hexops/DirectXShaderCompiler/blob/4190bb0...

    • Mach 쪽 글이 훌륭한데도 그 “서명”이 무엇인지 그냥 말하지 않는 점은 답답함
      글을 거기까지 주의 깊게 읽은 사람이라면 기본 해시나 비슷한 것이어야 한다는 걸 알 수 있었고, 최악이어도 누군가 어셈블리를 리버스 엔지니어링하면 되는 수준이었음
      그렇게 애쓴 뒤라면 Microsoft를 그냥 공개적으로 까도 됨. 특히 관심 있는 사람은 누구나 파고들어 찾을 수 있는 오픈소스 코드 얘기라면 더더욱 그렇고, 찾아준 msk에게 고마움
    • HN에서 이런 부분이 정말 좋음. 임의의 C 코드가 메모리를 다루는 걸 보고 “이건 변형된 X처럼 보이네”라고 말할 수 있는 사람들이 있음. 주로 고수준 언어를 쓰는 나 같은 사람에게는 꽤 놀라움
    • RenderDoc은 2021년부터 이를 처리하는 코드를 갖고 있었고, 설명이 잘 된 주석도 있음
      https://github.com/baldurk/renderdoc/blob/4a620bb5a16b4de4e2...
    • 그 “서명”은 늘 헛소리 같은 보안 연극이었음. 다른 모든 그래픽 API는 그런 것 없이도 잘 돌아간다는 점을 보면 됨. 누군가 “서명” 알고리즘을 공개적으로 올려줘서 반가움
  • Microsoft 쪽의 이 인용[0]은 마음에 듦. DXC의 LLVM 포크가 LLVM의 코드 생성 계층과 인프라를 많이 제거하거나 망가뜨렸기 때문에, DXC에서 DXBC 생성을 지원하려면 깨진 LLVM 기능을 고치고 복원하는 대규모 작업이 필요하다고 함
    문제 규모가 크고 팀 리소스가 제한적이라 새 DXC 컴파일러에서 이 문제를 해결하지 않을 것이며, 향후 Clang에서 DXBC 생성을 지원할 수는 있지만 당분간은 DXIL과 SPIR-V 생성 지원에 집중하느라 몇 년은 시작하기 어렵다고 함. 하지 않을 일을 명확히 알려주는 건 신선함
    [0] https://github.com/microsoft/DirectXShaderCompiler/issues/57...

  • Mach 생태계를 꼭 살펴보길 권함. 특히 mach-sysgpu는 WebGPU의 완전한 재구현이고, 대부분을 17세인 Ali Chraghi가 작성함

  • SDL 쪽에서는 SDL3에 넣기 위해 기존 것과 다른 SDL_gpu 형태의 셰이더 언어를 만들고 있음. 게임용 3D 그래픽을 다룰 때 크로스 플랫폼 방식이 될 수 있어서 한동안 유심히 보고 있음

    • SDL_gpu가 WebGPU와 의미 있게 다른지 궁금함. 목표를 보면 DX12/Vulkan/Metal 위에 접근하기 쉬운 최소공통분모 추상화를 만든다는 점에서 거의 같아 보임
      차이라면 SDL_gpu는 아직 초기 작업 중이고, WebGPU는 이미 좋은 공개 구현이 둘 있다는 정도임
  • 덜 골치 아픈 방식은 HLSL/GLSL → SPIR-V ↔ DXIL 같은 형태이거나, 셰이더를 직접 SPIR-V로 작성하는 것일 듯함
    Wine의 vkd3d에는 DXIL → SPIR-V 변환기가 있는 것으로 보이고, 고수준 셰이딩 언어 변환기보다 훨씬 단순한 중간 언어라 더 견고할 수 있음
    다만 이 LLVM 괴물 말고, GCC나 Clang 없이 컴파일 가능한 순수하고 단순한 C99 기반 HLSL → DXIL 컴파일러가 있는지 궁금함

    • SPIR-V → DXIL은 확실히 매력적이고 탐색해 볼 만해 보임. 아주 최근에 Mesa에 spirv2dxil이라는 도구가 있다는 걸 알았지만, HLSL → DXC → DXIL에 비해 얼마나 견고하고 기능이 충분한지는 모르겠음
    • “셰이더를 직접 SPIR-V로 작성”은 제발 아니었으면 함
      질문에 답하자면, 없음. HLSL에서 DXIL로의 변환은 사실상 Microsoft가 쥐고 있고, 거기서 벗어나려는 노력은 거의 없었음
  • Zig 자체를 셰이딩 언어로 쓰는 건 멋짐. Zig는 진정한 단일 언어임. 빌드 시스템이기도 하고, 셰이딩 언어이기도 함!