mod body;
mod compute;
mod io;
mod operands;
mod resources;
mod setup;
mod tessellation;
use std::collections::{BTreeMap, BTreeSet};
use iridia::build::{Builder, Width};
use iridia::{Conventions, D3dModes, Program, Provenance, ShaderFormat, Stage};
use naga::{Handle, LocalVariable};
use crate::{Result, error};
use dxbc_reader::decode::{Decoded, decode};
pub const LIFTER_VERSION: u32 = 10;
pub fn lift(bytes: &[u8]) -> Result<Program> {
lift_with(bytes, &iridia::BoundSizes::new())
}
pub fn lift_with(bytes: &[u8], bound: &iridia::BoundSizes) -> Result<Program> {
let mut program = lift_program(bytes, bound)?;
program.provenance.source_hash = Some(iridia::source_hash(bytes));
Ok(program)
}
fn lift_program(bytes: &[u8], bound: &iridia::BoundSizes) -> Result<Program> {
let directory = crate::parts(bytes)?;
let code = directory
.iter()
.find(|c| matches!(&c.kind, b"SHDR" | b"SHEX"))
.ok_or_else(|| error(0, "missing shader code chunk"))?;
if code.data.len() % 4 != 0 {
return Err(error(0, "unaligned code length"));
}
let words: Vec<u32> = code
.data
.as_chunks::<4>()
.0
.iter()
.map(|b| u32::from_le_bytes(*b))
.collect();
if words.len() > 1_048_576 {
return Err(error(1, "program exceeds word budget"));
}
let decoded = decode(&words).map_err(crate::read)?;
let stage = crate::reflection::stage_of(decoded.kind);
let mut setup = setup::Setup::new(bytes, &decoded, stage)?;
setup.bind_sizes(bound)?;
let mut lift = Lift::new(&decoded, stage, setup)?;
if let Some(sigs) = lift.setup.sigs.clone() {
for sig in &sigs {
lift.run_function(sig)?;
}
return lift.finish_functions(decoded.target());
}
if stage == Stage::Compute {
lift.b.workgroup_size = lift
.setup
.workgroup
.ok_or_else(|| error(0, "a compute program that declares no thread group size"))?;
}
lift.prologue()?;
lift.body()?;
lift.finish(decoded.target())
}
struct Frame {
kind: FrameKind,
word: usize,
condition: Option<Handle<naga::Expression>>,
accept: Option<naga::Block>,
selector: Option<Handle<naga::Expression>>,
cases: Vec<naga::SwitchCase>,
current: Option<naga::SwitchValue>,
}
#[derive(PartialEq, Eq)]
enum FrameKind {
If,
Loop,
Switch,
}
struct Lift<'a> {
b: Builder,
decoded: &'a Decoded,
stage: Stage,
setup: setup::Setup,
temps: Vec<Handle<LocalVariable>>,
inputs: BTreeMap<u32, Handle<LocalVariable>>,
outputs: BTreeMap<u32, Handle<LocalVariable>>,
indexable: BTreeMap<u32, (Handle<LocalVariable>, u32)>,
ids: BTreeMap<iridia::SystemValue, Handle<LocalVariable>>,
sig: Option<iridia::FunctionSig>,
control_points: Option<(Handle<LocalVariable>, u32)>,
output_points: Option<(Handle<LocalVariable>, u32)>,
patch_in: Option<Handle<LocalVariable>>,
domain_point: Option<Handle<LocalVariable>>,
point_id: Option<Handle<LocalVariable>>,
fork_id: Option<Handle<LocalVariable>>,
join_id: Option<Handle<LocalVariable>>,
gs: Option<tessellation::GsState>,
gs_instance: Option<Handle<LocalVariable>>,
in_phase: bool,
pending_decl: Option<iridia::build::FunctionDecl>,
primitive_id: Option<Handle<LocalVariable>>,
input_coverage: Option<Handle<LocalVariable>>,
depth: Option<Handle<LocalVariable>>,
coverage: Option<Handle<LocalVariable>>,
resources: resources::Resources,
frames: Vec<Frame>,
notes: BTreeSet<String>,
word: usize,
returned: bool,
arguments: Vec<naga::FunctionArgument>,
result_type: Option<Handle<naga::Type>>,
}
impl<'a> Lift<'a> {
fn refuse<T>(&self, detail: impl Into<String>) -> Result<T> {
Err(error(self.word, detail))
}
fn u4(&mut self) -> Handle<naga::Type> {
self.b.ty_of(iridia::build::Kind::U, Width::Four)
}
fn check_closed(&self) -> Result<()> {
match self.frames.last() {
None => Ok(()),
Some(frame) => {
let (opener, closer) = match frame.kind {
FrameKind::If => ("if", "endif"),
FrameKind::Loop => ("loop", "endloop"),
FrameKind::Switch => ("switch", "endswitch"),
};
Err(error(
frame.word,
format!("{opener} is not closed: the program ends without its {closer}"),
))
}
}
}
fn finish(mut self, target: String) -> Result<Program> {
self.check_closed()?;
if !self.returned {
self.epilogue()?;
}
self.b.check().map_err(|detail| error(self.word, detail))?;
let stage = match self.stage {
Stage::Vertex => naga::ShaderStage::Vertex,
Stage::Compute => naga::ShaderStage::Compute,
_ => naga::ShaderStage::Fragment,
};
let word = self.word;
let module = self
.b
.finish(
"main",
stage,
self.arguments,
self.result_type,
self.setup.early_depth_test,
)
.map_err(|detail| error(word, detail))?;
let mut limit_notes = iridia::compute_limit_notes(&module);
let interface = self.setup.interface.clone();
limit_notes.extend(iridia::interface_notes(&interface));
Ok(Program {
module,
stage: self.stage,
entry: "main".into(),
interface: self.setup.interface,
bindings: self.resources.bound,
conventions: Conventions::D3D,
modes: D3dModes::ALL,
provenance: Provenance {
format: ShaderFormat::Dxbc,
target,
lifter: "iridia-dxbc".into(),
lifter_version: LIFTER_VERSION,
source_hash: None,
instruction_count: Some(iridia::InstructionCount {
count: self.decoded.instructions.len() as u64,
unit: iridia::InstructionUnit::DxbcInstructions,
}),
},
notes: self.notes.into_iter().chain(limit_notes).collect(),
})
}
}