Skip to main content

dxbc_reader/
reflection.rs

1//! The reflection (`RDEF`) and signature (`ISGN`, `OSGN`, `ISG1`, `OSG1`, `OSG5`, `PCSG`) parts of a Shader Model 4
2//! or 5 container, as the records they store: resource bindings, constant buffers with their variables and type
3//! trees, and signature elements, each with D3D's own numbers (`D3D_SHADER_INPUT_TYPE`, `D3D_NAME`, ...) and the word
4//! it starts at. What the numbers mean for a graphics API is the caller's to decide.
5//!
6//! Every table is bounded before it is read, every string is checked to end inside its part and be UTF-8, and a type
7//! tree is refused where it nests deeper than 32 or refers to itself.
8use crate::decode::ProgramType;
9use crate::{Result, chunk, error, parts};
10
11/// The most records one table may state.
12const MAX_RECORDS: usize = 65536;
13/// The longest name, in bytes.
14const MAX_NAME: usize = 4096;
15/// The largest constant buffer, in bytes.
16const MAX_BUFFER_SIZE: usize = 65536;
17
18struct View<'a>(&'a [u8]);
19impl View<'_> {
20    fn bytes(&self, at: usize, len: usize) -> Result<&[u8]> {
21        let end = at.checked_add(len).ok_or_else(|| {
22            error(
23                at / 4,
24                format!("{len} bytes at byte {at} overflow the address space"),
25            )
26        })?;
27        self.0.get(at..end).ok_or_else(|| {
28            error(
29                at / 4,
30                format!(
31                    "{len} bytes at byte {at} exceed the part's {} bytes",
32                    self.0.len()
33                ),
34            )
35        })
36    }
37    fn array<const N: usize>(&self, at: usize) -> Result<[u8; N]> {
38        self.bytes(at, N)?
39            .try_into()
40            .map_err(|_| error(at / 4, format!("{N} bytes at byte {at}")))
41    }
42    fn u32(&self, at: usize) -> Result<u32> {
43        Ok(u32::from_le_bytes(self.array(at)?))
44    }
45    fn u16(&self, at: usize) -> Result<u16> {
46        Ok(u16::from_le_bytes(self.array(at)?))
47    }
48    /// The byte `delta` bytes after `at`.
49    fn plus(&self, at: usize, delta: usize) -> Result<usize> {
50        at.checked_add(delta).ok_or_else(|| {
51            error(
52                at / 4,
53                format!("byte {at} plus {delta} overflows the address space"),
54            )
55        })
56    }
57    /// The 32-bit field `delta` bytes after `at`.
58    fn field(&self, at: usize, delta: usize) -> Result<u32> {
59        self.u32(self.plus(at, delta)?)
60    }
61    /// The 16-bit field `delta` bytes after `at`.
62    fn field16(&self, at: usize, delta: usize) -> Result<u16> {
63        self.u16(self.plus(at, delta)?)
64    }
65    /// The start of record `i` of a table at `at` with records of `stride` bytes.
66    fn record(&self, at: usize, i: usize, stride: usize) -> Result<usize> {
67        i.checked_mul(stride)
68            .and_then(|n| at.checked_add(n))
69            .ok_or_else(|| {
70                error(
71                    at / 4,
72                    format!("record {i} of {stride} bytes at byte {at} overflows"),
73                )
74            })
75    }
76    /// The string at byte `at`, which ends inside the part.
77    fn string(&self, at: usize) -> Result<String> {
78        let tail = self.0.get(at..).ok_or_else(|| {
79            error(
80                at / 4,
81                format!("a string at byte {at}, past the part's {} bytes", self.0.len()),
82            )
83        })?;
84        let end = tail.iter().position(|&b| b == 0).ok_or_else(|| {
85            error(
86                at / 4,
87                format!("the string at byte {at} has no terminating zero in the part"),
88            )
89        })?;
90        if end > MAX_NAME {
91            return Err(error(
92                at / 4,
93                format!("the string at byte {at} is {end} bytes, more than the {MAX_NAME} allowed"),
94            ));
95        }
96        let text = tail
97            .get(..end)
98            .ok_or_else(|| error(at / 4, format!("the string at byte {at}")))?;
99        std::str::from_utf8(text)
100            .map(str::to_owned)
101            .map_err(|_| error(at / 4, format!("the string at byte {at} is not UTF-8")))
102    }
103    /// The indices of the `count` records of `stride` bytes at `at`, once the table is known to lie in the part.
104    fn table(&self, at: usize, count: usize, stride: usize) -> Result<std::ops::Range<usize>> {
105        if count > MAX_RECORDS {
106            return Err(error(
107                at / 4,
108                format!("a table of {count} records at byte {at}, more than the {MAX_RECORDS} allowed"),
109            ));
110        }
111        self.bytes(
112            at,
113            count.checked_mul(stride).ok_or_else(|| {
114                error(
115                    at / 4,
116                    format!("a table of {count} records of {stride} bytes overflows"),
117                )
118            })?,
119        )?;
120        Ok(0..count)
121    }
122    /// The type record at byte `at` and its members' types, depth first.
123    fn ty(&self, at: usize, stack: &mut Vec<usize>, budget: &mut usize) -> Result<RdefType> {
124        if stack.len() >= 32 || stack.contains(&at) || *budget == 0 {
125            return Err(error(
126                at / 4,
127                "the type at this word is part of a cycle, nests deeper than 32 or is one of more than 65536 types",
128            ));
129        }
130        *budget = budget.saturating_sub(1);
131        self.bytes(at, 36)?;
132        stack.push(at);
133        let count = usize::from(self.field16(at, 10)?);
134        let offset = self.field(at, 12)? as usize;
135        let mut members = Vec::new();
136        for i in self.table(offset, count, 12)? {
137            let p = self.record(offset, i, 12)?;
138            members.push(RdefMember {
139                name: self.string(self.u32(p)? as usize)?,
140                offset: self.field(p, 8)?,
141                ty: self.ty(self.field(p, 4)? as usize, stack, budget)?,
142            });
143        }
144        stack.pop();
145        Ok(RdefType {
146            word: at / 4,
147            class: self.u16(at)?,
148            scalar: self.field16(at, 2)?,
149            rows: self.field16(at, 4)?,
150            columns: self.field16(at, 6)?,
151            elements: self.field16(at, 8)?,
152            members,
153        })
154    }
155}
156
157/// One element of a signature as stored.
158#[derive(Clone, Debug, PartialEq, Eq)]
159pub struct SignatureElement {
160    /// The semantic name (`POSITION`, `TEXCOORD`, ...).
161    pub name: String,
162    /// The semantic index (the `1` of `TEXCOORD1`).
163    pub index: u32,
164    /// `D3D_NAME`.
165    pub system_value: u32,
166    /// `D3D_REGISTER_COMPONENT_TYPE`.
167    pub component_type: u32,
168    /// The register the element is packed into.
169    pub register: u32,
170    /// The components the element occupies, one bit each (x is bit 0).
171    pub mask: u8,
172    /// The components a program reads (inputs) or never writes (outputs), as stored.
173    pub read_write_mask: u8,
174    /// Where the record starts, in words of the chunk.
175    pub at: usize,
176}
177
178/// A signature part's records. Layouts: `ISGN`/`OSGN`/`PCSG` (name, index, system value, component type, register,
179/// masks), `ISG1`/`OSG1` (a stream and a minimum precision besides) and `OSG5` (a stream besides). `kind` is the
180/// part's tag, which says which layout `bytes` has.
181pub fn signature(bytes: &[u8], kind: &[u8; 4]) -> Result<Vec<SignatureElement>> {
182    let v = View(bytes);
183    let count = v.u32(0)? as usize;
184    let directory = v.u32(4)?;
185    if directory != 8 {
186        return Err(error(
187            1,
188            format!("expected the record directory at byte 8, found byte {directory}"),
189        ));
190    }
191    // bytes before the name offset, and the record size
192    let (skip, record) = match kind {
193        b"ISG1" | b"OSG1" => (4, 32),
194        b"OSG5" => (4, 28),
195        _ => (0, 24),
196    };
197    let mut result = Vec::new();
198    for i in v.table(8, count, record)? {
199        let p = v.record(8, i, record)?;
200        let q = v.plus(p, skip)?;
201        let [mask, read_write_mask, _, _] = v.array::<4>(v.plus(q, 20)?)?;
202        result.push(SignatureElement {
203            name: v.string(v.u32(q)? as usize)?,
204            index: v.field(q, 4)?,
205            system_value: v.field(q, 8)?,
206            component_type: v.field(q, 12)?,
207            register: v.field(q, 16)?,
208            mask,
209            read_write_mask,
210            at: p / 4,
211        });
212    }
213    Ok(result)
214}
215
216/// A hull or domain program's patch-constant signature elements as stored (the `PCSG` part).
217pub fn patch_signature_elements(bytes: &[u8]) -> Result<Vec<SignatureElement>> {
218    let directory = parts(bytes)?;
219    signature(chunk(&directory, b"PCSG")?, b"PCSG")
220}
221
222/// The part among `tags` the container has (the first tag where it has none, so that is the one reported missing).
223fn signature_part<'a>(
224    directory: &[dxcontainer::Chunk<'a>],
225    tags: &[&[u8; 4]],
226) -> Result<(&'a [u8], [u8; 4])> {
227    let tag = tags
228        .iter()
229        .find(|t| directory.iter().any(|c| &&c.kind == *t))
230        .or(tags.first())
231        .ok_or_else(|| error(0, "no signature part kinds to look for"))?;
232    Ok((chunk(directory, tag)?, **tag))
233}
234
235/// A container's program type and its input and output signature elements as stored.
236pub fn signature_elements(
237    bytes: &[u8],
238) -> Result<(ProgramType, Vec<SignatureElement>, Vec<SignatureElement>)> {
239    let directory = parts(bytes)?;
240    let kind = program_type(&directory)?;
241    let read = |tags: &[&[u8; 4]]| -> Result<Vec<SignatureElement>> {
242        let (data, tag) = signature_part(&directory, tags)?;
243        signature(data, &tag)
244    };
245    Ok((
246        kind,
247        read(&[b"ISGN", b"ISG1"])?,
248        read(&[b"OSGN", b"OSG1", b"OSG5"])?,
249    ))
250}
251
252/// The type of a container's program: its code part's version token names it in the high 16 bits.
253pub fn program_type(directory: &[dxcontainer::Chunk<'_>]) -> Result<ProgramType> {
254    let code = directory
255        .iter()
256        .find(|c| matches!(&c.kind, b"SHDR" | b"SHEX"))
257        .ok_or_else(|| error(0, "the container has no SHDR or SHEX part"))?;
258    let token = View(code.data).u32(0)?;
259    match token >> 16 {
260        0 => Ok(ProgramType::Pixel),
261        1 => Ok(ProgramType::Vertex),
262        2 => Ok(ProgramType::Geometry),
263        3 => Ok(ProgramType::Hull),
264        4 => Ok(ProgramType::Domain),
265        5 => Ok(ProgramType::Compute),
266        kind => Err(error(
267            0,
268            format!("expected a program type from 0 (pixel) to 5 (compute), found {kind}"),
269        )),
270    }
271}
272
273/// A container's `RDEF` part as stored: what it says the program binds and the layouts of its buffers.
274#[derive(Clone, Debug, PartialEq, Eq)]
275pub struct Rdef {
276    /// The part's own version token (`0xffff0500`: an SM5.0 pixel program, `0xfffe0500`: vertex, ...).
277    pub target: u32,
278    /// The resource bindings, in the order the part lists them.
279    pub bindings: Vec<RdefBinding>,
280    /// The constant buffers, in the order the part lists them.
281    pub buffers: Vec<RdefBuffer>,
282}
283
284/// One resource binding record as stored.
285#[derive(Clone, Debug, PartialEq, Eq)]
286pub struct RdefBinding {
287    /// Where the record starts, in words of the part.
288    pub word: usize,
289    /// The resource's name.
290    pub name: String,
291    /// `D3D_SHADER_INPUT_TYPE`.
292    pub kind: u32,
293    /// `D3D_RESOURCE_RETURN_TYPE`.
294    pub returns: u32,
295    /// `D3D_SRV_DIMENSION`.
296    pub dimension: u32,
297    /// A multisampled texture's sample count; a structured buffer's stride.
298    pub samples: u32,
299    /// The first register the resource is bound to.
300    pub register: u32,
301    /// How many consecutive registers it is bound to.
302    pub count: u32,
303    /// `D3D_SHADER_INPUT_FLAGS`.
304    pub flags: u32,
305}
306
307/// One buffer (`D3D11_SHADER_BUFFER_DESC`) as stored, with its variables.
308#[derive(Clone, Debug, PartialEq, Eq)]
309pub struct RdefBuffer {
310    /// Where the record starts, in words of the part.
311    pub word: usize,
312    /// The buffer's name.
313    pub name: String,
314    /// `D3D_CBUFFER_TYPE`: 0 a cbuffer, 1 a tbuffer, 2 interface pointers, 3 a structured buffer's element.
315    pub kind: u32,
316    /// The buffer's size in bytes, as the part stores it.
317    pub size: u32,
318    /// The variables the buffer holds.
319    pub variables: Vec<RdefVariable>,
320}
321
322/// One variable of a buffer as stored: its byte range in the buffer, its type and its default value.
323#[derive(Clone, Debug, PartialEq, Eq)]
324pub struct RdefVariable {
325    /// Where the record starts, in words of the part.
326    pub word: usize,
327    /// The variable's name.
328    pub name: String,
329    /// Its first byte in the buffer.
330    pub offset: u32,
331    /// Its size in bytes.
332    pub size: u32,
333    /// Its type.
334    pub ty: RdefType,
335    /// The stored default value's `size` bytes, where the variable has one.
336    pub default: Option<Vec<u8>>,
337}
338
339/// A variable's type record (`D3D11_SHADER_TYPE_DESC`) as stored, and its members' types.
340#[derive(Clone, Debug, PartialEq, Eq)]
341pub struct RdefType {
342    /// Where the record starts, in words of the part.
343    pub word: usize,
344    /// `D3D_SHADER_VARIABLE_CLASS`: 0 scalar, 1 vector, 2 row-major matrix, 3 column-major matrix, 4 object, 5 struct.
345    pub class: u16,
346    /// `D3D_SHADER_VARIABLE_TYPE`.
347    pub scalar: u16,
348    /// The matrix's rows (1 for a scalar or vector).
349    pub rows: u16,
350    /// The vector's or matrix's columns.
351    pub columns: u16,
352    /// The array length; 0 where it isn't an array.
353    pub elements: u16,
354    /// The struct's members, in order.
355    pub members: Vec<RdefMember>,
356}
357
358/// A struct member: its name, its byte offset in the struct and its type.
359#[derive(Clone, Debug, PartialEq, Eq)]
360pub struct RdefMember {
361    /// The member's name.
362    pub name: String,
363    /// Its first byte in the struct.
364    pub offset: u32,
365    /// Its type.
366    pub ty: RdefType,
367}
368
369/// The version token of a container's first `RDEF` part (`None` where it has none), read without the rest of the part:
370/// a caller can tell which program model's layout it holds before it asks [`rdef`] for the records.
371pub fn rdef_target(bytes: &[u8]) -> Result<Option<u32>> {
372    let directory = parts(bytes)?;
373    directory
374        .iter()
375        .find(|c| &c.kind == b"RDEF")
376        .map(|rdef| View(rdef.data).u32(16))
377        .transpose()
378}
379
380/// A container's `RDEF` part (`None` where it has none), for the SM5 layout (the `RD11` record-size directory).
381pub fn rdef(bytes: &[u8]) -> Result<Option<Rdef>> {
382    let directory = parts(bytes)?;
383    if !directory.iter().any(|c| &c.kind == b"RDEF") {
384        return Ok(None);
385    }
386    let v = View(chunk(&directory, b"RDEF")?);
387    let target = v.u32(16)?;
388    let tag = v.bytes(28, 4)?;
389    if tag != b"RD11" {
390        return Err(error(
391            7,
392            format!(
393                "expected the `RD11` tag of the Shader Model 5 RDEF layout, found {:?}",
394                String::from_utf8_lossy(tag)
395            ),
396        ));
397    }
398    for (i, size) in [60, 24, 32, 40, 36, 12].into_iter().enumerate() {
399        let at = v.record(32, i, 4)?;
400        let found = v.u32(at)?;
401        if found != size {
402            return Err(error(
403                at / 4,
404                format!("expected record size {size} (entry {i} of the RD11 size directory), found {found}"),
405            ));
406        }
407    }
408    let mut bindings = Vec::new();
409    let offset = v.field(0, 12)? as usize;
410    for i in v.table(offset, v.field(0, 8)? as usize, 32)? {
411        let p = v.record(offset, i, 32)?;
412        bindings.push(RdefBinding {
413            word: p / 4,
414            name: v.string(v.u32(p)? as usize)?,
415            kind: v.field(p, 4)?,
416            returns: v.field(p, 8)?,
417            dimension: v.field(p, 12)?,
418            samples: v.field(p, 16)?,
419            register: v.field(p, 20)?,
420            count: v.field(p, 24)?,
421            flags: v.field(p, 28)?,
422        });
423    }
424    let mut buffers = Vec::new();
425    let offset = v.field(0, 4)? as usize;
426    let mut budget = MAX_RECORDS;
427    for i in v.table(offset, v.u32(0)? as usize, 24)? {
428        let p = v.record(offset, i, 24)?;
429        let name = v.string(v.u32(p)? as usize)?;
430        let kind = v.field(p, 20)?;
431        let size = v.field(p, 12)?;
432        if size as usize > MAX_BUFFER_SIZE {
433            return Err(error(
434                v.plus(p, 12)? / 4,
435                format!("constant buffer of {size} bytes, more than the {MAX_BUFFER_SIZE} allowed"),
436            ));
437        }
438        let mut variables = Vec::new();
439        let entries = v.field(p, 8)? as usize;
440        for j in v.table(entries, v.field(p, 4)? as usize, 40)? {
441            let q = v.record(entries, j, 40)?;
442            let start = v.field(q, 4)?;
443            let len = v.field(q, 8)?;
444            if start.checked_add(len).is_none_or(|end| end > size) {
445                return Err(error(
446                    v.plus(q, 4)? / 4,
447                    format!(
448                        "variable at byte {start} of {len} bytes lies outside its {size} byte constant buffer"
449                    ),
450                ));
451            }
452            let default = v.field(q, 20)? as usize;
453            let ty = v.ty(v.field(q, 16)? as usize, &mut Vec::new(), &mut budget)?;
454            variables.push(RdefVariable {
455                word: q / 4,
456                name: v.string(v.u32(q)? as usize)?,
457                offset: start,
458                size: len,
459                ty,
460                default: if default == 0 {
461                    None
462                } else {
463                    Some(v.bytes(default, len as usize)?.to_vec())
464                },
465            });
466        }
467        buffers.push(RdefBuffer {
468            word: p / 4,
469            name,
470            kind,
471            size,
472            variables,
473        });
474    }
475    Ok(Some(Rdef {
476        target,
477        bindings,
478        buffers,
479    }))
480}