iridia-exec-ir 0.5.0

Run a program of iridia's shader IR on the CPU, one invocation or a pixel quad, by the IR's own definition of each operation
Documentation
  • Coverage
  • 100%
    28 out of 28 items documented0 out of 7 items with examples
  • Size
  • Source code size: 68.6 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 326.3 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 7s Average build duration of successful builds.
  • all releases: 7s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Kalmarv/iridia
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • Kalmarv

iridia-exec-ir

Runs a program of iridia's shader IR on the CPU, one invocation or a pixel program's 2 x 2 quad, by the IR's own definition of each operation: the reference the passes and lowerings are checked against.

Part of iridia, which turns compiled shaders into programs other tools can read and write. The design is docs/design/ir.md. This crate does no I/O.

What it runs

  • run(program, bound) takes the program and a Bound: each input slot's lanes, each constant buffer's rows, each pipeline state resource's value, and a sampler the caller answers each Sample with (the texture and sampler ids, the coordinate's lanes, the level of detail). It gives a Run: each output slot's lanes as last stored (a lane never stored is zero, a slot never stored None) and whether the invocation was killed.
  • run_quad(program, bounds) runs a pixel program's quad, a Bound per lane (lanes 0 and 1 the top row). A derivative is a difference between lanes: the right lane's less the left's along x, the lower's less the upper's along y, of the top row or left column when coarse (and for Fineness::Any, as iridia-exec takes it), of the lane's own when fine. An implicit-LOD sample is asked with the quad's coarse derivatives of its coordinate (Lod::Grad, Lod::GradBias). A demoted lane (a discard) goes on as a helper the others still read. The lanes run one after another with no threads, each until it meets a derivative or implicit-LOD sample whose operands are not all known, then again knowing them; lanes that do not meet it at the same operation and loop iteration are refused as divergent, and so is a run of more than MEETING_LIMIT meetings. needs_quad(program) says whether a program's results depend on its quad.
  • Both body forms: every construct of a Regions body, and the blocks of a Cfg one (block parameters copied in parallel). A run of more than STEP_LIMIT loop iterations or block executions is refused.
  • The arithmetic is the reference iridia-exec uses: IEEE 754 f32 for the basic operations, the transcendental functions (exp2, log2, sin, cos, sqrt, inverseSqrt, pow) in f64 rounded once, a dot product summed left to right with each step rounded. Denormals are kept. Each operation's enum decides the rest: a zero-wins multiply, a NaN rule of min and max, ties away from zero.

Example

use iridia_ir::{
    Conventions, FloatEnv, Program, Provenance,
    interface::{Interface, Label, ScalarType, ShaderFormat, Stage, StageFacts, SystemValue, Value, Varying},
};

let entry = iridia_ir::text::parse(
    "iridia-ir 1
fn main {
  %0: vec2<f32> = input 0.xy
  %1: vec2<f32> = vec2<f32>(0x7f800000, 3.0)
  %2: vec2<f32> = fmul.zerowins %0, %1
  output 0.xy = %2
  return
}
",
)
.unwrap();
let varying = |name: &str, system| Varying {
    label: Label { name: name.into(), index: 0 },
    system,
    value: Value { scalar: ScalarType::Float, width: 2 },
    interp: None,
    used: true,
    provenance: Default::default(),
};
let program = Program {
    stage: Stage::Pixel,
    entry,
    interface: Interface {
        stage: Stage::Pixel,
        inputs: vec![varying("TEXCOORD", None)],
        outputs: vec![varying("SV_Target", Some(SystemValue::Target(0)))],
        resources: Vec::new(),
        facts: StageFacts::default(),
    },
    conventions: Conventions::D3D,
    float: FloatEnv::default(),
    provenance: Provenance {
        format: ShaderFormat::D3d9,
        target: "example".into(),
        lifter: "text".into(),
        lifter_version: 1,
        source_hash: None,
        instruction_count: None,
        policies: Vec::new(),
    },
    notes: Vec::new(),
};
iridia_ir::validate(&program).unwrap();

let mut sampler = |_| Err::<[f32; 4], String>("no textures".into());
let inputs = [[0f32, 2.0, 0.0, 0.0].map(f32::to_bits)];
let mut bound = iridia_exec_ir::Bound {
    inputs: &inputs,
    buffers: &[],
    state: &[],
    sampler: &mut sampler,
    denormals: iridia_ir::Denormals::Any,
};
let run = iridia_exec_ir::run(&program, &mut bound).unwrap();
// zero wins: 0 * inf is +0, not NaN; the lanes past the output's width are zero
assert_eq!(run.outputs[0], Some([0, 6f32.to_bits(), 0, 0]));
assert!(!run.killed);

Guarantees and limits

  • This subset runs 32-bit scalars and vectors, constant buffer reads, pipeline state, samples, derivatives in a quad, demotion and kill; anything else (memory other than variables and constant buffers, other widths) is refused by name, not approximated.
  • A program whose float environment leaves denormals open runs by Bound::denormals; one that states a rule runs by it.
  • run runs one invocation, which has no derivatives: it refuses a derivative, and an implicit level of detail is Lod::Auto, for the caller's sampler to decide.
  • A quad's sampler may be asked a request again, once per pass that reaches it, and must answer it the same way each time.

Tests

cargo test -p iridia-exec-ir needs no game files: tests/fixtures.rs runs programs in IR text on inputs whose results are worked by hand from the operations' definitions (the NaN and rounding rules, zero-wins multiplication, saturating conversion, lane masks, a loop, a kill; a quad's coarse and fine derivatives, a derivative in a loop, a divergent quad refused, an implicit-LOD sample's footprint) and checks the text parser's refusals.

Licence

GPL-3.0-or-later.