Skip to main content

dxbc_reader/
lib.rs

1//! A reader of Shader Model 4 and 5 programs as Direct3D stores them in a `DXBC` container: the token stream of any
2//! stage ([`mod@decode`]), the reflection and signature chunks as the records the file holds ([`reflection`]), and the
3//! compiler's own counters ([`statistics`]).
4//!
5//! It reads and checks bytes and never interprets them for an API: a register binding is the RDEF record's own
6//! numbers, a signature element its stored name, index and masks. Mapping those into a graphics API's vocabulary is
7//! the caller's choice. Everything it can't read (a truncated instruction, an out-of-range table, a cyclic type) is an
8//! [`Error`] naming the 32-bit word where it happened; it never panics on untrusted input.
9//!
10//! ```no_run
11//! # fn main() -> Result<(), dxbc_reader::Error> {
12//! let container = std::fs::read("shader.dxbc").expect("a compiled shader");
13//! let code = dxbc_reader::code(&container)?;
14//! let program = dxbc_reader::decode(&code)?;
15//! println!("{} with {} instructions", program.target(), program.instructions.len());
16//! # Ok(()) }
17//! ```
18#![deny(
19    clippy::arithmetic_side_effects,
20    clippy::indexing_slicing,
21    clippy::cast_possible_truncation
22)]
23#![deny(missing_docs)]
24#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
25
26pub mod decode;
27mod opcodes;
28pub mod reflection;
29pub mod statistics;
30
31pub use decode::{
32    Declaration, Declared, Decoded, Index, Instruction, Operand, ProgramType, Register, Selection, decode,
33    opcode_name,
34};
35pub use reflection::{
36    Rdef, RdefBinding, RdefBuffer, RdefMember, RdefType, RdefVariable, SignatureElement,
37    patch_signature_elements, program_type, rdef, rdef_target, signature, signature_elements,
38};
39pub use statistics::{Statistics, statistics};
40
41/// Something the reader can't read: the 32-bit word of the container's chunk or token stream where it is, and what.
42#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
43#[error("DXBC at word {word}: {detail}")]
44pub struct Error {
45    /// The word offset, counted from the start of the chunk or token stream being read.
46    pub word: usize,
47    /// What was expected there, or what was found instead.
48    pub detail: String,
49}
50
51/// The reader's result: an [`Error`] naming the word where reading failed.
52pub type Result<T> = std::result::Result<T, Error>;
53
54pub(crate) fn error(word: usize, detail: impl Into<String>) -> Error {
55    Error {
56        word,
57        detail: detail.into(),
58    }
59}
60
61/// The container's parts (its `DXBC` directory).
62pub fn parts(bytes: &[u8]) -> Result<Vec<dxcontainer::Chunk<'_>>> {
63    dxcontainer::chunks(bytes).map_err(|e| error(e.offset / 4, e.detail))
64}
65
66/// The container's one part of kind `tag`: an error where it has none, or more than one.
67pub fn chunk<'a>(directory: &[dxcontainer::Chunk<'a>], tag: &[u8; 4]) -> Result<&'a [u8]> {
68    let shown = String::from_utf8_lossy(tag);
69    let mut matching = directory.iter().filter(|c| &c.kind == tag);
70    let data = matching
71        .next()
72        .ok_or_else(|| error(0, format!("the container has no {shown} part")))?
73        .data;
74    if matching.next().is_some() {
75        return Err(error(
76            0,
77            format!("the container has the {shown} part more than once"),
78        ));
79    }
80    Ok(data)
81}
82
83/// The program's token stream (its `SHEX` or `SHDR` part) as 32-bit little-endian words, for [`decode()`].
84pub fn code(bytes: &[u8]) -> Result<Vec<u32>> {
85    let directory = parts(bytes)?;
86    let code = directory
87        .iter()
88        .find(|c| matches!(&c.kind, b"SHDR" | b"SHEX"))
89        .ok_or_else(|| error(0, "missing shader code chunk"))?;
90    if code.data.len() % 4 != 0 {
91        return Err(error(
92            code.data.len() / 4,
93            format!(
94                "the code part is {} bytes, expected a multiple of 4 (32-bit tokens)",
95                code.data.len()
96            ),
97        ));
98    }
99    Ok(code
100        .data
101        .as_chunks::<4>()
102        .0
103        .iter()
104        .map(|b| u32::from_le_bytes(*b))
105        .collect())
106}