Skip to main content

dxbc_reader/
reflection.rs

1//! The reflection (`RDEF`) and signature (`ISGN`, `OSGN`, `ISG1`, `OSG1`, `OSG5`, `PCSG`, `PSG1`) parts of a Shader
2//! Model 4 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` (of `layout`'s size) and its members' types, depth first.
123    fn ty(&self, at: usize, layout: Layout, 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, layout.ty)?;
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, layout, 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/// The record sizes of an `RDEF` part's layout that differ. The resource binding (32 bytes), constant buffer (24) and
158/// struct member (12) records are the same in both; Shader Model 5's variable and type records end in fields Shader
159/// Model 4's do not have.
160#[derive(Clone, Copy)]
161struct Layout {
162    /// The size of a variable record.
163    variable: usize,
164    /// The size of a type record.
165    ty: usize,
166}
167
168/// Shader Model 4: the 28-byte header, then the tables.
169const SM4: Layout = Layout { variable: 24, ty: 16 };
170/// Shader Model 5: the header, the `RD11` tag and the six record sizes it states.
171const SM5: Layout = Layout { variable: 40, ty: 36 };
172
173/// One element of a signature as stored.
174#[derive(Clone, Debug, PartialEq, Eq)]
175pub struct SignatureElement {
176    /// The semantic name (`POSITION`, `TEXCOORD`, ...).
177    pub name: String,
178    /// The semantic index (the `1` of `TEXCOORD1`).
179    pub index: u32,
180    /// `D3D_NAME`.
181    pub system_value: u32,
182    /// `D3D_REGISTER_COMPONENT_TYPE`.
183    pub component_type: u32,
184    /// The register the element is packed into.
185    pub register: u32,
186    /// The components the element occupies, one bit each (x is bit 0).
187    pub mask: u8,
188    /// The components a program reads (inputs) or never writes (outputs), as stored.
189    pub read_write_mask: u8,
190    /// Where the record starts, in words of the chunk.
191    pub at: usize,
192}
193
194/// A signature part's records. Layouts: `ISGN`/`OSGN`/`PCSG` (name, index, system value, component type, register,
195/// masks), `ISG1`/`OSG1`/`PSG1` (a stream and a minimum precision besides) and `OSG5` (a stream besides). `kind` is the
196/// part's tag, which says which layout `bytes` has.
197pub fn signature(bytes: &[u8], kind: &[u8; 4]) -> Result<Vec<SignatureElement>> {
198    let v = View(bytes);
199    let count = v.u32(0)? as usize;
200    let directory = v.u32(4)?;
201    if directory != 8 {
202        return Err(error(
203            1,
204            format!("expected the record directory at byte 8, found byte {directory}"),
205        ));
206    }
207    // bytes before the name offset, and the record size
208    let (skip, record) = match kind {
209        b"ISG1" | b"OSG1" | b"PSG1" => (4, 32),
210        b"OSG5" => (4, 28),
211        _ => (0, 24),
212    };
213    let mut result = Vec::new();
214    for i in v.table(8, count, record)? {
215        let p = v.record(8, i, record)?;
216        let q = v.plus(p, skip)?;
217        let [mask, read_write_mask, _, _] = v.array::<4>(v.plus(q, 20)?)?;
218        result.push(SignatureElement {
219            name: v.string(v.u32(q)? as usize)?,
220            index: v.field(q, 4)?,
221            system_value: v.field(q, 8)?,
222            component_type: v.field(q, 12)?,
223            register: v.field(q, 16)?,
224            mask,
225            read_write_mask,
226            at: p / 4,
227        });
228    }
229    Ok(result)
230}
231
232/// A hull or domain program's patch-constant signature elements as stored (the `PCSG` part, or the `PSG1` part where
233/// the program uses minimum precision).
234pub fn patch_signature_elements(bytes: &[u8]) -> Result<Vec<SignatureElement>> {
235    let directory = parts(bytes)?;
236    let (data, tag) = signature_part(&directory, &[b"PCSG", b"PSG1"])?;
237    signature(data, &tag)
238}
239
240/// The part among `tags` the container has (the first tag where it has none, so that is the one reported missing).
241fn signature_part<'a>(
242    directory: &[dxcontainer::Chunk<'a>],
243    tags: &[&[u8; 4]],
244) -> Result<(&'a [u8], [u8; 4])> {
245    let tag = tags
246        .iter()
247        .find(|t| directory.iter().any(|c| &&c.kind == *t))
248        .or(tags.first())
249        .ok_or_else(|| error(0, "no signature part kinds to look for"))?;
250    Ok((chunk(directory, tag)?, **tag))
251}
252
253/// A container's program type and its input and output signature elements as stored.
254pub fn signature_elements(
255    bytes: &[u8],
256) -> Result<(ProgramType, Vec<SignatureElement>, Vec<SignatureElement>)> {
257    let directory = parts(bytes)?;
258    let kind = program_type(&directory)?;
259    let read = |tags: &[&[u8; 4]]| -> Result<Vec<SignatureElement>> {
260        let (data, tag) = signature_part(&directory, tags)?;
261        signature(data, &tag)
262    };
263    Ok((
264        kind,
265        read(&[b"ISGN", b"ISG1"])?,
266        read(&[b"OSGN", b"OSG1", b"OSG5"])?,
267    ))
268}
269
270/// The type of a container's program: its code part's version token names it in the high 16 bits.
271pub fn program_type(directory: &[dxcontainer::Chunk<'_>]) -> Result<ProgramType> {
272    let code = directory
273        .iter()
274        .find(|c| matches!(&c.kind, b"SHDR" | b"SHEX"))
275        .ok_or_else(|| error(0, "the container has no SHDR or SHEX part"))?;
276    let token = View(code.data).u32(0)?;
277    match token >> 16 {
278        0 => Ok(ProgramType::Pixel),
279        1 => Ok(ProgramType::Vertex),
280        2 => Ok(ProgramType::Geometry),
281        3 => Ok(ProgramType::Hull),
282        4 => Ok(ProgramType::Domain),
283        5 => Ok(ProgramType::Compute),
284        kind => Err(error(
285            0,
286            format!("expected a program type from 0 (pixel) to 5 (compute), found {kind}"),
287        )),
288    }
289}
290
291/// A container's `RDEF` part as stored: what it says the program binds and the layouts of its buffers.
292#[derive(Clone, Debug, PartialEq, Eq)]
293pub struct Rdef {
294    /// The part's own version token (`0xffff0500`: an SM5.0 pixel program, `0xfffe0500`: vertex, ...).
295    pub target: u32,
296    /// The resource bindings, in the order the part lists them.
297    pub bindings: Vec<RdefBinding>,
298    /// The constant buffers, in the order the part lists them.
299    pub buffers: Vec<RdefBuffer>,
300}
301
302/// One resource binding record as stored.
303#[derive(Clone, Debug, PartialEq, Eq)]
304pub struct RdefBinding {
305    /// Where the record starts, in words of the part.
306    pub word: usize,
307    /// The resource's name.
308    pub name: String,
309    /// `D3D_SHADER_INPUT_TYPE`.
310    pub kind: u32,
311    /// `D3D_RESOURCE_RETURN_TYPE`.
312    pub returns: u32,
313    /// `D3D_SRV_DIMENSION`.
314    pub dimension: u32,
315    /// A multisampled texture's sample count; a structured buffer's stride.
316    pub samples: u32,
317    /// The first register the resource is bound to.
318    pub register: u32,
319    /// How many consecutive registers it is bound to.
320    pub count: u32,
321    /// `D3D_SHADER_INPUT_FLAGS`.
322    pub flags: u32,
323}
324
325/// One buffer (`D3D11_SHADER_BUFFER_DESC`) as stored, with its variables.
326#[derive(Clone, Debug, PartialEq, Eq)]
327pub struct RdefBuffer {
328    /// Where the record starts, in words of the part.
329    pub word: usize,
330    /// The buffer's name.
331    pub name: String,
332    /// `D3D_CBUFFER_TYPE`: 0 a cbuffer, 1 a tbuffer, 2 interface pointers, 3 a structured buffer's element.
333    pub kind: u32,
334    /// The buffer's size in bytes, as the part stores it.
335    pub size: u32,
336    /// The variables the buffer holds.
337    pub variables: Vec<RdefVariable>,
338}
339
340/// One variable of a buffer as stored: its byte range in the buffer, its type and its default value.
341#[derive(Clone, Debug, PartialEq, Eq)]
342pub struct RdefVariable {
343    /// Where the record starts, in words of the part.
344    pub word: usize,
345    /// The variable's name.
346    pub name: String,
347    /// Its first byte in the buffer.
348    pub offset: u32,
349    /// Its size in bytes.
350    pub size: u32,
351    /// Its type.
352    pub ty: RdefType,
353    /// The stored default value's `size` bytes, where the variable has one.
354    pub default: Option<Vec<u8>>,
355}
356
357/// A variable's type record (`D3D11_SHADER_TYPE_DESC`) as stored, and its members' types.
358#[derive(Clone, Debug, PartialEq, Eq)]
359pub struct RdefType {
360    /// Where the record starts, in words of the part.
361    pub word: usize,
362    /// `D3D_SHADER_VARIABLE_CLASS`: 0 scalar, 1 vector, 2 row-major matrix, 3 column-major matrix, 4 object, 5 struct.
363    pub class: u16,
364    /// `D3D_SHADER_VARIABLE_TYPE`.
365    pub scalar: u16,
366    /// The matrix's rows (1 for a scalar or vector).
367    pub rows: u16,
368    /// The vector's or matrix's columns.
369    pub columns: u16,
370    /// The array length; 0 where it isn't an array.
371    pub elements: u16,
372    /// The struct's members, in order.
373    pub members: Vec<RdefMember>,
374}
375
376/// A struct member: its name, its byte offset in the struct and its type.
377#[derive(Clone, Debug, PartialEq, Eq)]
378pub struct RdefMember {
379    /// The member's name.
380    pub name: String,
381    /// Its first byte in the struct.
382    pub offset: u32,
383    /// Its type.
384    pub ty: RdefType,
385}
386
387/// The version token of a container's first `RDEF` part (`None` where it has none), read without the rest of the part:
388/// a caller can tell which program model's layout it holds before it asks [`rdef`] for the records.
389pub fn rdef_target(bytes: &[u8]) -> Result<Option<u32>> {
390    let directory = parts(bytes)?;
391    directory
392        .iter()
393        .find(|c| &c.kind == b"RDEF")
394        .map(|rdef| View(rdef.data).u32(16))
395        .transpose()
396}
397
398/// The `RD11` tag and record-size directory a Shader Model 5 `RDEF` part has after its 28-byte header.
399fn read_directory(v: &View<'_>) -> Result<()> {
400    let tag = v.bytes(28, 4)?;
401    if tag != b"RD11" {
402        return Err(error(
403            7,
404            format!(
405                "expected the `RD11` tag of the Shader Model 5 RDEF layout, found {:?}",
406                String::from_utf8_lossy(tag)
407            ),
408        ));
409    }
410    for (i, size) in [60, 24, 32, 40, 36, 12].into_iter().enumerate() {
411        let at = v.record(32, i, 4)?;
412        let found = v.u32(at)?;
413        if found != size {
414            return Err(error(
415                at / 4,
416                format!("expected record size {size} (entry {i} of the RD11 size directory), found {found}"),
417            ));
418        }
419    }
420    Ok(())
421}
422
423/// A container's `RDEF` part (`None` where it has none): the Shader Model 4 layout where its target is a 4.x one (a
424/// 28-byte header, then the tables), the Shader Model 5 layout (the `RD11` record-size directory) where it is a 5.x one.
425pub fn rdef(bytes: &[u8]) -> Result<Option<Rdef>> {
426    let directory = parts(bytes)?;
427    if !directory.iter().any(|c| &c.kind == b"RDEF") {
428        return Ok(None);
429    }
430    let v = View(chunk(&directory, b"RDEF")?);
431    let target = v.u32(16)?;
432    let layout = match (target >> 8) & 0xff {
433        4 => SM4,
434        _ => {
435            read_directory(&v)?;
436            SM5
437        }
438    };
439    let mut bindings = Vec::new();
440    let offset = v.field(0, 12)? as usize;
441    for i in v.table(offset, v.field(0, 8)? as usize, 32)? {
442        let p = v.record(offset, i, 32)?;
443        bindings.push(RdefBinding {
444            word: p / 4,
445            name: v.string(v.u32(p)? as usize)?,
446            kind: v.field(p, 4)?,
447            returns: v.field(p, 8)?,
448            dimension: v.field(p, 12)?,
449            samples: v.field(p, 16)?,
450            register: v.field(p, 20)?,
451            count: v.field(p, 24)?,
452            flags: v.field(p, 28)?,
453        });
454    }
455    let mut buffers = Vec::new();
456    let offset = v.field(0, 4)? as usize;
457    let mut budget = MAX_RECORDS;
458    for i in v.table(offset, v.u32(0)? as usize, 24)? {
459        let p = v.record(offset, i, 24)?;
460        let name = v.string(v.u32(p)? as usize)?;
461        let kind = v.field(p, 20)?;
462        let size = v.field(p, 12)?;
463        if size as usize > MAX_BUFFER_SIZE {
464            return Err(error(
465                v.plus(p, 12)? / 4,
466                format!("constant buffer of {size} bytes, more than the {MAX_BUFFER_SIZE} allowed"),
467            ));
468        }
469        let mut variables = Vec::new();
470        let entries = v.field(p, 8)? as usize;
471        for j in v.table(entries, v.field(p, 4)? as usize, layout.variable)? {
472            let q = v.record(entries, j, layout.variable)?;
473            let start = v.field(q, 4)?;
474            let len = v.field(q, 8)?;
475            if start.checked_add(len).is_none_or(|end| end > size) {
476                return Err(error(
477                    v.plus(q, 4)? / 4,
478                    format!(
479                        "variable at byte {start} of {len} bytes lies outside its {size} byte constant buffer"
480                    ),
481                ));
482            }
483            let default = v.field(q, 20)? as usize;
484            let ty = v.ty(v.field(q, 16)? as usize, layout, &mut Vec::new(), &mut budget)?;
485            variables.push(RdefVariable {
486                word: q / 4,
487                name: v.string(v.u32(q)? as usize)?,
488                offset: start,
489                size: len,
490                ty,
491                default: if default == 0 {
492                    None
493                } else {
494                    Some(v.bytes(default, len as usize)?.to_vec())
495                },
496            });
497        }
498        buffers.push(RdefBuffer {
499            word: p / 4,
500            name,
501            kind,
502            size,
503            variables,
504        });
505    }
506    Ok(Some(Rdef {
507        target,
508        bindings,
509        buffers,
510    }))
511}