# iridia-exec
A bounded CPU reference executor for [`iridia::Program`](../iridia): it runs a lifted shader on explicit inputs, whatever bytecode it was lifted from, and returns exactly what it wrote. It is for checking a translation (against values worked out by hand, or against Microsoft's WARP rasterizer), not for rendering: it is deterministic and slow.
Part of [iridia](https://github.com/Kalmarv/iridia), which turns compiled Direct3D shaders into naga modules. This crate depends on `iridia`; the readers (`iridia-dxbc`, `iridia-dxil`, `iridia-d3d9`, `iridia-mojo`) do not depend on it.
## What it runs
- **Entry-point programs** (pixel, vertex, compute, mesh). `Executable::new` checks a program and compiles it once into a flat form (typed operations on four-lane word registers, pointers folded to word offsets, dead operations dropped). A `Runner` executes one invocation, a 2 x 2 quad, or several invocations in lockstep (masked where they diverge, each with its own steps and budget) against a `Backend`, which answers what is outside the program: texture samples, loads, queries, unordered-access effects. `ProgramShader` puts a vertex or pixel program behind the `PixelShader` trait, in the register vocabulary of the D3D bytecode it came from: inputs and outputs by signature register, constant buffers and samplers by bind slot.
- **Stages WGSL has no entry point for.** Their programs lift as functions, which `Executable::functions` compiles: `geometry` (the input assembler's topologies and a geometry program's strips), `tessellation` and `tessellator` (hull and domain programs over a port of Microsoft's reference fixed-function tessellator), `mesh` (a thread group's meshlet), `Pipeline` with `Scene` (a ray tracing pipeline: `DispatchRays`, `TraceRay`, hit groups, miss and callable programs, over acceleration structures the caller builds; inline ray queries too).
- **`draw`**: a small software rasterizer for an indexed triangle list (vertex program, clipping, viewport, rasterization, pixel program, depth test, render targets), for re-running a recorded draw on its programs and setting its pixels against the game's.
- **`evaluate`**: a pixel shader over a grid of UVs, to bake a procedural material to textures. The caller states everything the program reads (the constant layout and values, a texture per sampled slot, a value per used input) and which outputs it wants; the evaluator has no policy of its own and refuses what it is not given. `evaluate_periodic` bakes to a texture that tiles.
- **`probe`**: runs one vertex of a vertex program with chosen constants to see what it does with them (what a matrix or a table is).
- **`value`**: one node of a program's value graph (`iridia::flow::Values`) evaluated from the words of the leaves it reads, with the executor's own operations: what a run computes for that lane where it is given the same leaves, bit for bit. The caller answers each leaf (an input lane, a constant word, a texel) or leaves it unknown; a lane that needs an unknown leaf is unknown and names it. A loop's value, a merge whose arms differ, a run-time index, a derivative and a subgroup operation are refused by name.
- **`texture`** and **`views`**: a 2D texture as the executor samples it (levels, colour transfer, the sampler state's addressing and bias, the filtering the caller chooses) and resource views bound by slot.
## Example
Lift a pixel shader and run it once. Its constant buffer holds `f = (0.25, 2, 4, 8)` and `u = (7, 2, 0, 0)`; the HLSL computes `min(f.x, f.y)` and `max(f.x, f.y)` into target 0 and `u.x / u.y` and `u.x % u.y` into target 1:
```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
use iridia_exec::{PixelShader, ProgramShader, Sample};
let program = iridia_dxbc::lift(&std::fs::read("../iridia-dxbc/tests/fixtures/modes.dxbc")?)?;
let shader = ProgramShader::new(program)?;
let bits = |v: [f32; 4]| v.map(f32::to_bits);
let constants = [bits([0.25, 2.0, 4.0, 8.0]), [7, 2, 0, 0], [0; 4]];
let inputs = vec![[0u32; 4]; shader.input_count()]; // SV_Position, which the program never reads
let run = shader.execute(&inputs, &[(0, &constants)], &mut |_: Sample| Err("no textures".into()))?;
assert_eq!(f32::from_bits(run.outputs[0][0]), 0.25); // min(f.x, f.y)
assert_eq!(f32::from_bits(run.outputs[0][1]), 2.0); // max(f.x, f.y)
assert_eq!(&run.outputs[1][..2], &[3, 1]); // 7 / 2, 7 % 2
Ok(())
}
```
## Guarantees and limits
- `Resources::constant_elements` supplies constant-buffer arrays by (resource ID, descriptor index), with separate rows for every descriptor. `bind_resource_arrays` borrows explicit element maps for buffer and constant-buffer arrays, including unbounded arrays; `bind_resources` uses the maps in `Resources`. Missing descriptors error when accessed. These bindings and compute writes store only supplied elements, so an unbounded declaration does not allocate by its sentinel count. The older `bind_elements` callback needs a bounded declaration; use an explicit map for an unbounded one.
- What a run does follows the program's `D3dModes`: a program that says its body spells out D3D's integer division, conversions and `min`/`max` runs as written, and one that expects denormals flushed to zero has every 32-bit float operation flush its operands and result (sign kept). Anything the program does that its module cannot make valid (an index past the end of a local array, an integer division by zero in the module) is an error of the run, never a silent value. Errors name their bytecode and location.
- Every run has a step budget (`STEP_LIMIT`).
- Sampling belongs to the caller: a scalar execution cannot infer derivatives, filtering or addressing, so a `Sampler` callback answers texture samples and the caller states the footprint, addressing, filtering and transfer. A quad run supplies the derivatives of its four invocations.
- A construct the executor does not run is refused by `Executable::new` with its name. A ray tracing program is not an entry point; it runs inside a `Pipeline`.
- The tessellator is a port of the reference tessellator Microsoft published with the Direct3D 11.3 functional specification (MIT licence; the notice is kept in `src/tessellator.rs`). Its output is held against that reference's recorded output in `tests/fixtures/tessellator_reference.txt`.
## Tests
`cargo test -p iridia-exec` needs no game files: it runs fixtures through both `iridia-dxbc` and `iridia-dxil` (development dependencies, a cycle cargo allows), holding the interface read from either to the values worked out from the HLSL (`tests/interface.rs`), the evaluator to outputs worked out by hand (`tests/evaluate.rs`), lockstep lanes against one pixel after another with diverging control flow (`tests/lanes.rs`), the value evaluator against a run of the same program on the same leaves, bit for bit (`tests/value.rs`), and the stage drivers to hand-worked meshes, strips, patches and scenes. The comparisons against WARP live in `iridia-dxbc` and `iridia-dxil` (`tests/exec_warp*.rs`).
## Check the examples
From the repository root, `cargo test -p iridia-exec --doc` checks the crate documentation, and
`cargo build -p iridia-exec --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, apart from the tessellator port noted above.
Texture sampling carries the stated border colour and mip range (`SamplerUse`, `ViewSampler`): outside a border axis reads that colour, and the mip range clamps the biased level. A border with no colour remains a refusal. Vector constant expressions compile as initial values, including constants introduced by material specialisation.