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