- API đồ họa thử nghiệm sysgpu của Mach engine cần sản phẩm đầu ra shader cho Direct3D 12, nên đã xây dựng lại Microsoft DXC thành dạng dễ dùng theo kiểu tĩnh và đa nền tảng
- DXIL của Direct3D 12 gần với LLVM bitcode đã hậu xử lý do fork LLVM/Clang 3.7 của Microsoft xuất ra, nên cấu trúc phụ thuộc vào “đầu ra thực tế của trình biên dịch” hơn là đặc tả
- Các runtime thuộc hệ WebGPU thường chuyển WGSL sang HLSL rồi dùng FXC hoặc DXC để tạo DXBC/DXIL, nhưng do gánh nặng phân phối DXC, FXC cũ và chậm dễ trở thành lựa chọn mặc định
- DXC hiện có khó liên kết tĩnh, còn
dxil.dll, một binary ký/xác minh độc quyền, chủ yếu chỉ được phân phối cho Windows và Linux x86, khiến việc biên dịch shader DirectX offline trên macOS hoặc CI Arm Linux bị chặn
mach-dxcompiler viết lại build CMake bằng Zig build.zig và loại bỏ phụ thuộc DLL để cung cấp thư viện dxcompiler tĩnh cùng CLI dxc, nhưng vẫn còn hạn chế về MSVC ABI, kiểm thử musl, đầu ra SPIR-V và hỗ trợ SM6.7
Vì sao Mach xây dựng lại DXC
- Mach engine đang tạo một API đồ họa thử nghiệm tên sysgpu bằng Zig, với mục tiêu hỗ trợ các backend Metal, Vulkan, Direct3D và OpenGL
- Với backend Direct3D 12, chương trình shader phải được biên dịch sang định dạng mà Direct3D 12 có thể tiêu thụ
- Trong quá trình này, họ nhận ra trình biên dịch shader DirectX DXC của Microsoft tạo ra trải nghiệm phân phối phức tạp và bất tiện cho nhà phát triển game
Biên dịch shader DirectX chuyển từ FXC sang DXC
- API đồ họa DirectX dùng HLSL làm ngôn ngữ shading
- Trình biên dịch HLSL trước Direct3D 11 được gọi là FXC, tức effects compiler
- FXC được giới phát triển game biết đến là một trình biên dịch chậm và chất lượng sinh mã không tốt
- Có thể bất lợi cả về tốc độ biên dịch shader lẫn hiệu năng thực thi
- Với Direct3D 12 và Shader Model 6.0, Microsoft chính thức deprecated FXC vốn được tích hợp trong Windows OS, và đưa vào DXC, một fork dựa trên LLVM/Clang v3.7
- DXC được công khai tại Microsoft/DirectXShaderCompiler, và Microsoft cũng phân phối binary dựng sẵn
- Trong fork LLVM của Microsoft, các thay đổi liên quan đến HLSL được đánh dấu bằng chú thích
// HLSL Change Start, // HLSL Change End
DXBC và DXIL mà driver Direct3D tiêu thụ
- Do mỗi nhà sản xuất GPU có kiến trúc phần cứng và yêu cầu khác nhau, binary native mà HLSL cuối cùng chạy cũng khác nhau giữa GPU Intel, NVIDIA và AMD
- Microsoft cung cấp các API frontend như Direct3D và HLSL, còn các IHV như Intel, AMD, NVIDIA viết driver để nối chúng tới dạng gần với ISA phần cứng
- Trong DirectX 9~11, driver tiêu thụ DXBC
- Nhà phát triển game biên dịch HLSL sang DXBC bằng CLI
fxc.exe hoặc API d3dcompiler
- Driver chuyển DXBC thành binary sẽ chạy trên GPU thực tế
- DXBC là định dạng độc quyền không công khai được dùng giữa Microsoft và các nhà sản xuất driver GPU
- Từ DirectX 12 và Shader Model 6.0 trở đi, DXIL trở thành định dạng chính thức mà các nhà sản xuất driver DirectX 12 tiêu thụ
- DXIL gần với dạng bitcode sau các pass sinh mã và tối ưu hóa của LLVM 3.7, cộng thêm một container/wrapper tùy biến nhỏ
- Tài liệu DXIL không hẳn là một đặc tả riêng, mà phụ thuộc vào bitcode thực tế mà fork LLVM 3.7 của Microsoft xuất ra sau các thay đổi và tối ưu hóa HLSL
Kế hoạch DXIR biến mất và chuyển sang LLVM upstream
- Vào thời điểm DirectX 12 và Shader Model 6.0, Microsoft từng có ý tưởng tạo DXIR, một IR cấp cao, chưa tối ưu hóa, rồi để DXC hạ nó xuống DXIL đã tối ưu hóa
- Năm 2021, các cách diễn đạt ám chỉ khả năng tạo DXIR đã bị gỡ bỏ
- Năm 2019, một nhân viên Microsoft trả lời rằng tài liệu về quá trình hạ từ DXIR xuống DXIL gần như không có, và DXIR không phải định dạng chính thức mà gần với LLVM IR đầu tiên sau CodeGen
- Năm 2023, một nhân viên Microsoft cho biết fork LLVM của DXC đã loại bỏ hoặc làm hỏng phần lớn tầng sinh mã và hạ tầng của LLVM
- Nếu muốn thêm khả năng sinh DXBC vào DXC thì phải khôi phục các chức năng LLVM đã hỏng, và DXC sẽ không xử lý việc đó
- Từ tháng 3/2022, Microsoft đã đề xuất và đang tiến hành đưa hỗ trợ biên dịch HLSL lên upstream LLVM/Clang chính
- Kế hoạch chuyển đổi này cũng bao gồm việc thêm lại hỗ trợ ghi legacy LLVM v3.7 bitcode vào LLVM/Clang hiện đại
Gánh nặng phân phối DXC của WebGPU và game engine
- Các lớp trừu tượng hóa đồ họa muốn hợp nhất những API đồ họa hiện đại như Metal, Direct3D 12 và Vulkan cũng cần một ngôn ngữ shading thống nhất
- Hiện một số triển khai WebGPU đặt mục tiêu dài hạn là có đường dẫn phát trực tiếp DXIL, nhưng trên thực tế hầu hết chưa làm vậy
- Đường dẫn WebGPU phổ biến như sau
- Chuyển ngôn ngữ văn bản WGSL sang HLSL tại runtime
- Biên dịch HLSL bằng trình biên dịch HLSL sang DXBC hoặc DXIL
- Chuyển DXBC/DXIL đã tối ưu hóa cho driver đồ họa, rồi driver chuyển thành biểu diễn trung gian và mã máy theo từng vendor
- Vulkan/SPIR-V cũng có cấu trúc trong đó driver phải biên dịch SPIR-V sang binary native
- Một số driver có thể giả định SPIR-V đã được tối ưu hóa, nhưng điều này khác nhau tùy GPU mobile và desktop
- Fossilize của Valve duy trì cache các binary thực tế do driver biên dịch cho từng tổ hợp GPU và phiên bản driver
- DXIL luôn là LLVM bitcode sau các pass tối ưu hóa, trong khi SPIR-V có thể là dạng đã tối ưu hóa hoặc chưa
- Chỉ Apple Metal hỗ trợ API biên dịch trực tiếp sang định dạng binary native của phần cứng đích thực tế
Các lựa chọn do dxcompiler.dll và dxil.dll tạo ra
- Vì runtime WebGPU thực hiện chuyển đổi WGSL→HLSL→DXIL tại runtime, nó phải chọn giữa DXC mới và FXC cũ
- Theo tài liệu Bevy, FXC cũ, chậm và không được bảo trì, nhưng không cần phân phối DLL bổ sung
- Ngược lại, DXC mới, nhanh và được bảo trì, nhưng phải phân phối
dxcompiler.dll và dxil.dll cùng ứng dụng
- Vấn đề lựa chọn này ảnh hưởng không chỉ Bevy mà cả người dùng Rust
wgpu và người dùng Dawn WebGPU
- Kết quả là nhiều phần mềm chọn FXC cũ, chậm và không được bảo trì làm mặc định
Vì sao khó liên kết tĩnh DXC
- Fork LLVM của Microsoft không hỗ trợ liên kết tĩnh
- Nếu đổi
SHARED thành STATIC trong file CMake, sẽ tạo ra khoảng 15 thư viện tĩnh, nhưng trải nghiệm liên kết tệ hơn so với một thư viện đơn
- Khi cố dùng thư viện
OBJECT của CMake, các thay đổi HLSL của Microsoft làm lộ ra những phụ thuộc lẫn nhau ngầm, độc lập với phụ thuộc logic
- Một phần triển khai giao diện COM của DXC được thiết kế để nạp
dxcompiler.dll và dxil.dll dưới dạng thư viện động rồi tự gọi lại chính nó
- Chỉ thay đổi thiết lập build là không đủ để tạo DXC tĩnh
dxil.dll độc quyền và chữ ký shader
dxil.dll không được tạo ra ngay cả khi build DirectXShaderCompiler từ source, nhưng được phân phối trong các GitHub release cho Windows x86/Arm và Linux x86
- Theo D3D12 Shader Cache API specification, D3D12 chỉ chấp nhận shader đã ký; nếu có tối ưu hóa hoặc patch tại runtime, shader phải được xác minh và ký lại
- Trong bản release preview Shader Model 6.8,
dxil.dll/libdxil.so không được cung cấp
- DXIL mục tiêu SM6.8 do trình biên dịch đó tạo ra chưa phải bản cuối và không thể xác minh
- Không hỗ trợ phân phối hoặc chạy trên máy không bật Developer Mode
- Nếu không có
dxil.dll, shader sẽ không được ký/xác minh
- Shader không được ký/xác minh sẽ không thể chạy nếu máy Windows không ở Developer Mode
Biên dịch offline và ràng buộc nền tảng
- Mach muốn tránh phân phối phụ thuộc DXC nặng khi cần, và thực hiện biên dịch shader offline
- Microsoft chỉ phân phối
dxil.dll cho Windows x86/Arm và Linux x86
- Không có binary Linux aarch64 và macOS
- Do đó, không thể tạo build game đa nền tảng dành cho Windows trên macOS, hoặc thực hiện biên dịch shader DirectX offline trong pipeline CI Arm Linux
- Để chạy binary ký độc quyền, cần máy Windows hoặc x86_64 Linux
Những phần mach-dxcompiler đã thay đổi
- Viết lại khoảng 10,5k dòng hệ thống build CMake hiện có thành
build.zig của Zig
- Chỉ lấy hai phần mà người dùng chủ yếu cần làm mục tiêu build: thư viện
dxcompiler.dll và binary biên dịch/kiểm thử offline dxc.exe
- Kết quả được rút gọn thành khoảng 1k dòng logic
build.zig
- Fork codebase của Microsoft để sửa cấu trúc DXC vốn kỳ vọng có
dxcompiler.dll và dxil.dll
- Mô phỏng DLL entrypoint
- Vô hiệu hóa chức năng xuất thông tin phiên bản trình biên dịch bắt nguồn từ DLL
- Mô phỏng việc nạp con trỏ hàm thư viện động
mach-dxcompiler được cấu hình theo dạng không phụ thuộc vào dxil.dll
- Trên máy macOS, có thể biên dịch shader HLSL không cần
dxil.dll độc quyền, và tạo ra file giống byte-for-byte với DXIL bytecode chạy trên máy Windows thông thường
Kết quả và cách sử dụng
- Release bao gồm thư viện
dxcompiler tĩnh và binary dựng sẵn của CLI dxc
- Không phụ thuộc vào
dxil.dll độc quyền
- Các target được build trong pipeline CI gồm
- macOS: Apple Silicon aarch64 và Intel x86_64
- Linux: musl và glibc, aarch64 và x86_64
- Windows: x86_64 và aarch64, bao gồm MinGW/GNU ABI
- Thư viện expose C API nhỏ như một phương án thay thế COM API hiện có
- Nhà phát triển game Zig có thể dùng Zig API trong repository, và xem ví dụ sử dụng trong test
src/main.zig
- Theo mặc định, tải và dùng binary dựng sẵn
- Có thể build từ source tại repository mach-dxcompiler chỉ với
zig và git, yêu cầu đúng phiên bản Zig được chỉ định
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
Giới hạn hiện tại và điều kiện bảo trì
- Binary Windows MSVC ABI hiện chưa build được do một lỗi nhỏ trong C binding
- Binary Linux musl build được nhưng chưa được kiểm thử
- Mach engine dự định dùng chính Zig làm ngôn ngữ shading thay vì HLSL, nên không build hỗ trợ đầu ra SPIR-V và cũng không có kế hoạch bổ sung
- Hiện chưa có kế hoạch cập nhật hỗ trợ SM6.7 vừa được release gần đây
- Một phần hệ thống build CMake của LLVM vẫn chưa được chuyển hoàn toàn, và các chi tiết liên quan còn nằm trong
generated-include/
- Dự án này tồn tại để giải quyết vấn đề của Mach, và hiện do một người xử lý issue
- Nếu tìm được hướng đi tốt hơn, dự án có thể bị deprecated
1 bình luận
Ý kiến trên Hacker News
Đây là một bài viết tóm tắt rất tốt về việc tầng đáy của biên dịch shader đa API 3D lộn xộn đến mức nào
Bài tập trung vào D3D và Microsoft, nhưng các API 3D khác cũng chẳng khá hơn nhiều. Ví dụ, trên host Linux không thể cross-compile shader Metal; chỉ làm được trên macOS và các bản Windows tương đối gần đây
Nếu nhóm Mach có thể biến ý tưởng dùng Zig làm trình biên dịch shader đa API 3D trở nên trơn tru như “Zig với vai trò toolchain cross-compile”, thì đây có thể là sự kiện lớn nhất trong đồ họa máy tính kể từ khoảng năm 1995
Điều này cũng liên quan đến Godot
Họ nói rằng “lý do biến nó thành tùy chọn là vì hỗ trợ Direct3D 12 hiện phụ thuộc vào việc phân phối thư viện dxil.dll độc quyền của DirectX Shader Compiler cùng với Godot, và việc phân phối phần mềm độc quyền đi ngược lại sứ mệnh của dự án Godot”
https://godotengine.org/article/dev-snapshot-godot-4-3-dev-3...
Tôi muốn GPU, Wi‑Fi và Bluetooth của mình hoạt động, nên tốt nhất là cứ bật chọn sẵn theo mặc định
Về đoạn “không cần phân phối thêm .dll cùng với ứng dụng”, thực ra rất nhiều trò chơi điện tử đã phân phối middleware độc quyền như Bink, SpeedTree, PhysX theo cách đó
Hầu hết launcher như Steam, GOG, Epic cũng yêu cầu các .DLL riêng của họ, và nhiều game dùng D3D11On12. Cũng có nhiều game phát hành có dxil.dll trong danh sách tệp được cài đặt
Vì vậy tôi thật lòng thắc mắc: vấn đề của việc phân phối thêm một DLL là gì? Công việc reverse-engineer và tái triển khai phần ký mã ở đây rất xuất sắc, đặc biệt là việc đầu ra giống dxil.dll đến từng bit. Nhưng tôi thì lười khủng khiếp, nên chắc đã chọn con đường dễ hơn là phân phối DLL
Ví dụ, engine Mach có thể dùng nó khi xây dựng trình biên dịch để biên dịch mã Zig thành shader trên nhiều nền tảng đích, và người dùng cuối có thể dùng ngay trong chức năng cốt lõi của Mach mà không cần cấu hình hay phụ thuộc bổ sung
Ngay cả với game AAA, cũng như các game và phần mềm khác, số lượng phụ thuộc bổ sung có thể hỏng ngoài tầm kiểm soát lại tăng lên. Ở công ty cũ của tôi, vì một middleware chỉ được cung cấp dưới dạng DLL và thư viện để liên kết, chúng tôi phải tốn nhiều công sức hơn cho việc nâng cấp Visual Studio và còn phải nhận phiên bản mới. Do công ty middleware đó chưa nâng cấp phía họ, cuối cùng chúng tôi còn phải QA cả khả năng tương thích với VS mới
Tất nhiên có mã nguồn không có nghĩa là cập nhật sẽ hoàn toàn không ma sát, nhưng ma sát giảm đi rất nhiều và cũng không cần chờ người khác. Visual Studio gần đây có vẻ đã cố gắng duy trì tương thích ngược cho thư viện C++ dạng nhị phân, nhưng tôi không nghĩ đó là thứ có thể dựa vào dài hạn
Ngoài ra, tất cả những điều này đều giả định mã vẫn ở cùng nền tảng và cùng đích. Đến một lúc nào đó bạn có thể muốn xử lý một nền tảng khác làm host hoặc target, và nếu không có mã nguồn thì việc đó có thể cực kỳ khó hoặc bất khả thi. Với thứ đặc thù nền tảng như DXIL, chuyện này có thể không giống một vấn đề lớn, nhưng bài viết cũng nói rằng do tính chất binary blob của DXIL, việc biên dịch trước shader là không thể ngoài các kiến trúc Windows và Linux cụ thể mà Microsoft đã cung cấp DLL
“Chữ ký”[1] mà DXIL.dll thực hiện rốt cuộc chỉ là MD5 đã bị biến đổi thôi sao?
1: https://github.com/hexops/DirectXShaderCompiler/blob/4190bb0...
Ai đọc kỹ đến đoạn đó cũng có thể biết nó phải là một hàm băm cơ bản hoặc thứ gì tương tự; tệ nhất thì cũng chỉ ở mức ai đó đảo ngược assembly là ra
Sau khi đã bỏ công như vậy thì cứ công khai chê Microsoft cũng được. Nhất là khi nói về mã nguồn mở mà bất kỳ ai quan tâm cũng có thể đào sâu để tìm ra; cảm ơn msk đã tìm giúp
https://github.com/baldurk/renderdoc/blob/4a620bb5a16b4de4e2...
Tôi thích trích dẫn này[0] từ phía Microsoft. Họ nói rằng vì nhánh fork LLVM của DXC đã loại bỏ hoặc làm hỏng nhiều lớp sinh mã và hạ tầng của LLVM, nên để hỗ trợ tạo DXBC trong DXC sẽ cần một khối lượng công việc lớn nhằm sửa và khôi phục các chức năng LLVM bị hỏng
Do quy mô vấn đề lớn và tài nguyên nhóm hạn chế, họ sẽ không giải quyết vấn đề này trong trình biên dịch DXC mới; về sau Clang có thể hỗ trợ tạo DXBC, nhưng trong thời gian tới họ tập trung hỗ trợ tạo DXIL và SPIR-V nên khó có thể bắt đầu trong vài năm nữa. Việc nói rõ những gì sẽ không làm thật mới mẻ
[0] https://github.com/microsoft/DirectXShaderCompiler/issues/57...
Rất khuyên nên xem qua hệ sinh thái Mach. Đặc biệt mach-sysgpu là một bản tái triển khai hoàn chỉnh của WebGPU, và phần lớn do Ali Chraghi, 17 tuổi, viết
Phía SDL đang tạo một ngôn ngữ shader dưới dạng SDL_gpu, khác với những thứ hiện có, để đưa vào SDL3. Vì nó có thể trở thành cách tiếp cận đa nền tảng khi xử lý đồ họa 3D cho game, tôi đã theo dõi sát một thời gian
Nếu có khác thì chỉ là SDL_gpu vẫn đang ở giai đoạn đầu, còn WebGPU đã có hai bản triển khai công khai tốt
Cách đỡ đau đầu hơn có lẽ là dạng HLSL/GLSL → SPIR-V ↔ DXIL, hoặc viết shader trực tiếp bằng SPIR-V
vkd3d của Wine dường như có bộ chuyển đổi DXIL → SPIR-V, và vì đó là ngôn ngữ trung gian đơn giản hơn nhiều so với các bộ chuyển đổi ngôn ngữ shading bậc cao, nó có thể vững chắc hơn
Tuy vậy tôi vẫn tò mò liệu có trình biên dịch HLSL → DXIL dựa trên C99 thuần túy, đơn giản, có thể biên dịch mà không cần con quái vật LLVM này, cũng không cần GCC hay Clang, hay không
Trả lời câu hỏi thì: không có. Việc chuyển từ HLSL sang DXIL về cơ bản nằm trong tay Microsoft, và gần như không có nỗ lực nào để thoát khỏi điều đó
Dùng chính Zig làm ngôn ngữ shading thật ngầu. Zig đúng là một ngôn ngữ đơn nhất thực thụ. Nó vừa là hệ thống build, vừa là ngôn ngữ shading!