iridia-dxil
Lifts compiled Direct3D Shader Model 6 programs (DXIL, what dxc writes) into iridia::Program, a naga module with its interface, and reads a container's reflection, signatures and a ray tracing library's subobjects.
Part of iridia, which turns compiled Direct3D shaders into naga modules. The container, the LLVM bitcode of its DXIL part and the reflection and runtime-data records are read by dxil-reader; this crate maps them.
What it reads
A DXIL container of Shader Model 6 (its fixtures are dxc output for 6.0 and 6.6) of any stage but amplification: vertex, pixel, compute, hull, domain, geometry, mesh and the ray tracing stages (ray generation, intersection, any-hit, closest-hit, miss, callable), including inline ray queries, wave and quad operations, resource arrays and descriptor heaps, unordered-access counters, half (computed as a 32-bit float and rounded to a half after each operation, which a note says) and 64-bit integers (carried as a pair of 32-bit words).
A DXIL function is LLVM's: values in single static assignment form over basic blocks. The lifter turns the blocks into structured control flow (an irreducible graph is refused by name; a phi is a local the edges assign) and reads the dx.op intrinsics (inputs and outputs, math, bit operations, constant buffers, samples, loads, derivatives, discard, wave, mesh, ray query and ray tracing operations), alloca, getelementptr, load and store. The conventions are the same as iridia-dxbc's, so a program lifted from either has the same shape: varyings unpacked from the signature registers (register and lanes in provenance), constant buffers cb<n> as uniform array<vec4<u32>, rows> in group 0, textures and buffer views in group 1, samplers in group 2, typed texture views in group 3 and the hidden counters of unordered-access views in group 4, binding = register, and Direct3D's numeric results written out (D3dModes::ALL).
Reflection without a lift: interface(container) reads the stage, the signatures (ISG1/OSG1/PSG1, with each pixel input's interpolation from the entry point's metadata), the resources (dx.resources) and the constant buffer layouts (dx.typeAnnotations, named from the STAT part when the container has one; the program's own metadata carries no names), and subobjects(rdat) maps the subobjects of a ray tracing library from its runtime data (RDAT).
Constant-buffer descriptor arrays, including unbounded ones, use binding arrays of structures containing the rows. The descriptor and row indices remain separate. iridia-exec::Resources::constant_elements supplies each descriptor's data; focused cross-build tests in iridia-dxbc/tests/sm51_exec.rs compare their execution with fxc SM5.1 programs from the same source.
Example
Guarantees
- The interface lists every element of the container's signatures,
usedwhere the program reads or writes a cell of it (the program's own reading, not the compiler's masks). - Each
dx.opis read by position after one check of its argument count (src/lift/dxop.rs; the counts are DXC's, fromhctdb.py, and every one equals how the real-data modules call it).tests/malformed.rsmakes the damaged cases from committed fixtures; a function body naming something the function does not have is refused. - Anything not carried is refused by name with the bit it is at, never dropped: a double, 64-bit integer division and conversions between 64-bit integers and floats, an irreducible loop, a wave or quad operation of a kind not lifted, amplification programs (
DispatchMesh), per-primitive mesh outputs, a mesh payload read, clip or cull distances of a mesh program, an output written at a computed row, and so on. A geometry program's stream other than 0 is refused. - A ray tracing program has no WGSL form (WebGPU has no ray tracing pipeline):
iridia::emit::wgslrefuses it, andiridia-exec'sPipelineruns the lifted functions.
Tests
cargo test -p iridia-dxil needs no game files:
tests/fixtures.rslifts our own HLSL (tests/fixtures/*.hlsl, each with thedxccommand that built its.dxil): the lifted interface (signature packing of every element,used, resource names and slots, buffer field names and offsets), execution oniridia_exec::ProgramShaderagainst outputs worked out from the HLSL, every truncation of a fixture refused, and a seeded mutation run that must never panic (the debug keydxil.fuzz_runs, default 3000).tests/semantics.rs,tests/mesh.rs,tests/pipeline.rs,tests/geometry.rs,tests/rdat.rsand the others do the same for their areas;tests/versions.rsis the version gate againsttests/lifted.hashes.tests/exec_warp*.rsrender the fixtures on Microsoft's WARP rasterizer (Direct3D 12) through a separate host program and compare against the executor lane by lane, bit for bit (the float lanes of the fixture that usesexp2,log2andrsqrtwithin 1e-5 relative). They needtools.warp(not part of this repository) andtools.dxc.tests/lift_corpus.rslifts a folder of real containers (oracles.corpus.skate/shaders,elden/shaders), validates and writes each and prints the refusals by reason.
The inputs the tests read from GAME_DATA are listed in tests/inputs/README.md.
Check the examples
From the repository root, cargo test -p iridia-dxil --doc checks the crate documentation, and
cargo build -p iridia-dxil --examples builds its example programs. Examples that read paths need the caller's files; the repository ships no game assets.
Licence
GPL-3.0-or-later.