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
26mod controls;
27pub mod decode;
28mod format;
29mod opcodes;
30pub mod reflection;
31mod sampling;
32pub mod statistics;
33
34pub use controls::{GlobalFlags, InfoReturn, SyncFlags};
35pub use decode::{
36    Declaration, Declared, Decoded, Index, Instruction, Operand, ProgramType, Register, Selection, decode,
37    opcode_name,
38};
39pub use format::{OperandClass, OperandFormat, operand_format};
40pub use reflection::{
41    Rdef, RdefBinding, RdefBuffer, RdefMember, RdefType, RdefVariable, SignatureElement,
42    patch_signature_elements, program_type, rdef, rdef_target, signature, signature_elements,
43};
44pub use sampling::{TextureRead, texture_reads};
45pub use statistics::{Statistics, statistics};
46
47/// Something the reader can't read: the 32-bit word of the container's chunk or token stream where it is, and what.
48#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
49#[error("DXBC at word {word}: {detail}")]
50pub struct Error {
51    /// The word offset, counted from the start of the chunk or token stream being read.
52    pub word: usize,
53    /// What was expected there, or what was found instead.
54    pub detail: String,
55}
56
57/// The reader's result: an [`Error`] naming the word where reading failed.
58pub type Result<T> = std::result::Result<T, Error>;
59
60pub(crate) fn error(word: usize, detail: impl Into<String>) -> Error {
61    Error {
62        word,
63        detail: detail.into(),
64    }
65}
66
67/// The container's parts (its `DXBC` directory).
68pub fn parts(bytes: &[u8]) -> Result<Vec<dxcontainer::Chunk<'_>>> {
69    dxcontainer::chunks(bytes).map_err(|e| error(e.offset / 4, e.detail))
70}
71
72/// The container's one part of kind `tag`: an error where it has none, or more than one.
73pub fn chunk<'a>(directory: &[dxcontainer::Chunk<'a>], tag: &[u8; 4]) -> Result<&'a [u8]> {
74    let shown = String::from_utf8_lossy(tag);
75    let mut matching = directory.iter().filter(|c| &c.kind == tag);
76    let data = matching
77        .next()
78        .ok_or_else(|| error(0, format!("the container has no {shown} part")))?
79        .data;
80    if matching.next().is_some() {
81        return Err(error(
82            0,
83            format!("the container has the {shown} part more than once"),
84        ));
85    }
86    Ok(data)
87}
88
89/// The program's token stream (its `SHEX` or `SHDR` part) as 32-bit little-endian words, for [`decode()`].
90pub fn code(bytes: &[u8]) -> Result<Vec<u32>> {
91    let directory = parts(bytes)?;
92    let code = directory
93        .iter()
94        .find(|c| matches!(&c.kind, b"SHDR" | b"SHEX"))
95        .ok_or_else(|| error(0, "missing shader code chunk"))?;
96    if code.data.len() % 4 != 0 {
97        return Err(error(
98            code.data.len() / 4,
99            format!(
100                "the code part is {} bytes, expected a multiple of 4 (32-bit tokens)",
101                code.data.len()
102            ),
103        ));
104    }
105    Ok(code
106        .data
107        .as_chunks::<4>()
108        .0
109        .iter()
110        .map(|b| u32::from_le_bytes(*b))
111        .collect())
112}