dxil_reader/psv.rs
1//! The pipeline state validation (`PSV0`) part of a Shader Model 6 program container: the runtime information, resource
2//! records and signature elements a pipeline is validated against, which the validator writes from the program's own
3//! metadata. Little-endian.
4//!
5//! ```text
6//! u32 runtime-info size (36, 48 or 52), then the runtime info
7//! u32 resource count; when it is not zero, u32 record size (16 or 24), then the records
8//! u32 string-table size, then the table
9//! u32 semantic-index count, then the indices (u32 each)
10//! u32 element size (16) when there are any input, output or patch-constant elements, then the input, the output and
11//! the patch-constant elements
12//! the rest: the input-to-output dependency tables, as words
13//! ```
14//!
15//! The runtime info is 16 bytes whose meaning depends on the stage, the lowest and highest wave lane counts (u32 each),
16//! then from 36 bytes the stage (u8), the uses-view-id flag (u8), a u16 (a geometry program's largest output vertex
17//! count; in the low byte a hull or domain program's patch-constant vector count), the input, output and
18//! patch-constant element counts (u8 each), the input vector count (u8) and four output vector counts (u8 each), from
19//! 48 bytes the thread group size (three u32), and at 52 bytes the offset of the entry point's name in the string table.
20//! The stage-dependent 16 bytes are: vertex, whether an `SV_Position` output exists (word 3); pixel, whether it writes
21//! depth (byte 0) and whether it shades per sample (byte 1); hull, the input and output control point counts, the
22//! domain and the output primitive; domain, the input control point count; geometry, the input primitive, the output
23//! topology, the output stream mask and whether it writes a position. The record sizes follow the validator that wrote
24//! the part: validator 1.5 writes a 36-byte runtime info and 16-byte resource records, 1.6 writes 48 and 24, and 1.10
25//! writes 52 and 24 (the 16-byte records have no kind or flags). The reader reads these three sizes only.
26//!
27//! Every field above was checked against the metadata of the same program in the stock containers of skate. (19,240
28//! with a `PSV0` part, validator 1.10) and Elden Ring (46,002; 10 with validator 1.5, 45,992 with 1.6): the layout
29//! consumes each part to its last byte, and the stage, the thread group, the resource ranges, the elements' name,
30//! semantic indices, size, kind, interpolation and placement, the vector counts and the stage unions equal what
31//! [`crate::reflection`] reads from the metadata. The dependency tables are kept as stored words; the part's remainder
32//! after the elements is as long as the vectors state (the input vectors' components times the words of the output
33//! vectors' components).
34use primase_foundations::bytes::View;
35
36use crate::{Result, error};
37
38/// Most resource records a part may state.
39const MAX_RECORDS: usize = 1 << 20;
40
41/// The runtime information of a `PSV0` part.
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub struct RuntimeInfo {
44 /// The stage-dependent 16 bytes, as four words (see the module documentation).
45 pub stage_words: [u32; 4],
46 /// The smallest wave lane count the program expects (0 where it states none).
47 pub min_wave_lanes: u32,
48 /// The largest wave lane count the program expects (`u32::MAX` where it states none).
49 pub max_wave_lanes: u32,
50 /// The shader kind (0 pixel, 1 vertex, 2 geometry, 3 hull, 4 domain, 5 compute, 13 mesh, ...), as in the program
51 /// header.
52 pub stage: u8,
53 /// The program uses `SV_ViewID`.
54 pub uses_view_id: bool,
55 /// A geometry program's largest output vertex count; in the low byte a hull or domain program's patch-constant
56 /// vector count.
57 pub stage_data: u16,
58 /// The input, output and patch-constant element counts as stored (the lengths of [`Psv::inputs`] and the others).
59 pub element_counts: [u8; 3],
60 /// How many input vectors (registers) the program reads.
61 pub input_vectors: u8,
62 /// How many output vectors it writes, per stream.
63 pub output_vectors: [u8; 4],
64 /// The thread group size, from 48 bytes of runtime info.
65 pub num_threads: Option<[u32; 3]>,
66 /// The offset of the entry point's name in the string table, from 52 bytes of runtime info.
67 pub entry_name: Option<u32>,
68}
69
70/// A resource record.
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub struct Resource {
73 /// 1 sampler, 2 constant buffer, 3 typed SRV, 4 raw SRV, 5 structured SRV, 6 typed UAV, 7 raw UAV, 8 structured UAV,
74 /// 9 structured UAV with a counter.
75 pub resource_type: u32,
76 /// The register space.
77 pub space: u32,
78 /// The first register.
79 pub lower: u32,
80 /// The last register (`u32::MAX` for an unbounded range).
81 pub upper: u32,
82 /// The resource kind (the metadata's `DxilResourceKind` for an SRV or UAV, 13 for a constant buffer, 14 for a
83 /// sampler), in the 24-byte record only.
84 pub kind: Option<u32>,
85 /// The record's flags, in the 24-byte record only.
86 pub flags: Option<u32>,
87}
88
89/// A signature element record (16 bytes).
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub struct Element {
92 /// The offset of the semantic name in the string table (an empty string for a system value).
93 pub name: u32,
94 /// The offset of the element's first semantic index in the semantic-index table.
95 pub indices: u32,
96 /// How many registers the element spans.
97 pub rows: u8,
98 /// The first register.
99 pub start_row: u8,
100 /// How many components of each register it uses.
101 pub columns: u8,
102 /// The first component (0 x to 3 w).
103 pub start_column: u8,
104 /// The packer placed the element in registers.
105 pub allocated: bool,
106 /// DXIL semantic kind (0 arbitrary, 3 `SV_Position`, ...).
107 pub semantic_kind: u8,
108 /// The component type (1 u32, 2 i32, 3 f32: not the metadata's numbers, which are 5, 4 and 9).
109 pub component_type: u8,
110 /// DXIL interpolation mode.
111 pub interpolation: u8,
112 /// The components indexed dynamically, a 4-bit mask.
113 pub dynamic_index_mask: u8,
114 /// The output stream (0 to 3).
115 pub stream: u8,
116 /// The record's last byte, as stored.
117 pub reserved: u8,
118}
119
120/// A `PSV0` part.
121#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct Psv {
123 /// The runtime information.
124 pub runtime: RuntimeInfo,
125 /// The resource records, in the part's order (constant buffers, samplers, SRVs, UAVs).
126 pub resources: Vec<Resource>,
127 /// The string table: names, null-terminated.
128 pub strings: Vec<u8>,
129 /// The semantic-index table.
130 pub semantic_indices: Vec<u32>,
131 /// The input elements.
132 pub inputs: Vec<Element>,
133 /// The output elements.
134 pub outputs: Vec<Element>,
135 /// The patch-constant elements.
136 pub patch_constants: Vec<Element>,
137 /// The part after the elements: the dependency tables, as words.
138 pub dependencies: Vec<u32>,
139}
140
141impl Psv {
142 /// The string at `offset` of the string table (an error where it is outside the table, has no end, or is not UTF-8).
143 pub fn string(&self, offset: u32) -> Result<&str> {
144 let rest = usize::try_from(offset)
145 .ok()
146 .and_then(|at| self.strings.get(at..))
147 .ok_or_else(|| error(0, format!("PSV0: string {offset} past the string table")))?;
148 let end = rest
149 .iter()
150 .position(|b| *b == 0)
151 .ok_or_else(|| error(0, "PSV0: a string without its end"))?;
152 std::str::from_utf8(rest.get(..end).unwrap_or_default())
153 .map_err(|_| error(0, "PSV0: a string that is not UTF-8"))
154 }
155
156 /// The semantic indices of `element`, one for each of its rows.
157 pub fn element_indices(&self, element: &Element) -> Result<&[u32]> {
158 usize::try_from(element.indices)
159 .ok()
160 .and_then(|start| {
161 let end = start.checked_add(usize::from(element.rows))?;
162 self.semantic_indices.get(start..end)
163 })
164 .ok_or_else(|| error(0, "PSV0: an element's semantic indices lie past the index table"))
165 }
166}
167
168/// Reads the `PSV0` part `data`. Anything outside the layout above (a runtime info or record of another size, a table
169/// past the part's end, a remainder that is not whole words) is an error naming the bit.
170pub fn read(data: &[u8]) -> Result<Psv> {
171 let view = View::new(data);
172 let bit = |at: usize| at.saturating_mul(8);
173 let word = |at: usize| -> Result<u32> {
174 view.array::<4>(at)
175 .map(u32::from_le_bytes)
176 .map_err(|e| error(bit(at), format!("PSV0: {e}")))
177 };
178 let count = |at: usize, what: &str| -> Result<usize> {
179 usize::try_from(word(at)?).map_err(|_| error(bit(at), format!("PSV0: {what} past the address space")))
180 };
181 let advance = |at: usize, by: usize| {
182 at.checked_add(by)
183 .ok_or_else(|| error(bit(at), "PSV0: a table past the address space"))
184 };
185
186 let info_size = count(0, "the runtime info size")?;
187 let mut at = 4usize;
188 if !matches!(info_size, 36 | 48 | 52) {
189 return Err(error(
190 0,
191 format!("PSV0: a runtime info of {info_size} bytes, where only 36, 48 and 52 are read"),
192 ));
193 }
194 let info = view
195 .slice(at, info_size)
196 .map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
197 let info_word = |i: usize| -> Result<u32> {
198 let start = i.saturating_mul(4);
199 info.get(start..start.saturating_add(4))
200 .and_then(|w| <[u8; 4]>::try_from(w).ok())
201 .map(u32::from_le_bytes)
202 .ok_or_else(|| error(bit(at), "PSV0: the runtime info is cut short"))
203 };
204 let byte = |i: usize| -> Result<u8> {
205 info.get(i)
206 .copied()
207 .ok_or_else(|| error(bit(at), "PSV0: the runtime info is cut short"))
208 };
209 let runtime = RuntimeInfo {
210 stage_words: [info_word(0)?, info_word(1)?, info_word(2)?, info_word(3)?],
211 min_wave_lanes: info_word(4)?,
212 max_wave_lanes: info_word(5)?,
213 stage: byte(24)?,
214 uses_view_id: byte(25)? != 0,
215 stage_data: u16::from_le_bytes([byte(26)?, byte(27)?]),
216 element_counts: [byte(28)?, byte(29)?, byte(30)?],
217 input_vectors: byte(31)?,
218 output_vectors: [byte(32)?, byte(33)?, byte(34)?, byte(35)?],
219 num_threads: if info_size >= 48 {
220 Some([info_word(9)?, info_word(10)?, info_word(11)?])
221 } else {
222 None
223 },
224 entry_name: if info_size >= 52 {
225 Some(info_word(12)?)
226 } else {
227 None
228 },
229 };
230 at = advance(at, info_size)?;
231
232 let resource_count = count(at, "the resource count")?;
233 at = advance(at, 4)?;
234 let mut resources = Vec::new();
235 if resource_count > 0 {
236 if resource_count > MAX_RECORDS {
237 return Err(error(bit(at), format!("PSV0: {resource_count} resource records")));
238 }
239 let size = count(at, "the resource record size")?;
240 at = advance(at, 4)?;
241 if !matches!(size, 16 | 24) {
242 return Err(error(
243 bit(at),
244 format!("PSV0: resource records of {size} bytes, where only 16 and 24 are read"),
245 ));
246 }
247 for _ in 0..resource_count {
248 let long = size == 24;
249 resources.push(Resource {
250 resource_type: word(at)?,
251 space: word(advance(at, 4)?)?,
252 lower: word(advance(at, 8)?)?,
253 upper: word(advance(at, 12)?)?,
254 kind: if long { Some(word(advance(at, 16)?)?) } else { None },
255 flags: if long { Some(word(advance(at, 20)?)?) } else { None },
256 });
257 at = advance(at, size)?;
258 }
259 }
260
261 let strings_size = count(at, "the string table size")?;
262 at = advance(at, 4)?;
263 let strings = view
264 .slice(at, strings_size)
265 .map_err(|e| error(bit(at), format!("PSV0: {e}")))?
266 .to_vec();
267 at = advance(at, strings_size)?;
268
269 let index_count = count(at, "the semantic index count")?;
270 at = advance(at, 4)?;
271 let index_bytes = view
272 .slice(
273 at,
274 index_count
275 .checked_mul(4)
276 .ok_or_else(|| error(bit(at), "PSV0: the semantic index count"))?,
277 )
278 .map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
279 let semantic_indices = index_bytes
280 .as_chunks::<4>()
281 .0
282 .iter()
283 .map(|w| u32::from_le_bytes(*w))
284 .collect();
285 at = advance(at, index_bytes.len())?;
286
287 let mut elements = [Vec::new(), Vec::new(), Vec::new()];
288 if runtime.element_counts.iter().any(|n| *n > 0) {
289 let size = count(at, "the element size")?;
290 at = advance(at, 4)?;
291 if size != 16 {
292 return Err(error(
293 bit(at),
294 format!("PSV0: signature elements of {size} bytes, where only 16 are read"),
295 ));
296 }
297 for (list, n) in elements.iter_mut().zip(runtime.element_counts) {
298 for _ in 0..n {
299 let raw = view
300 .array::<16>(at)
301 .map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
302 let [
303 n0,
304 n1,
305 n2,
306 n3,
307 i0,
308 i1,
309 i2,
310 i3,
311 rows,
312 start_row,
313 packed,
314 kind,
315 component,
316 interpolation,
317 masks,
318 reserved,
319 ] = raw;
320 list.push(Element {
321 name: u32::from_le_bytes([n0, n1, n2, n3]),
322 indices: u32::from_le_bytes([i0, i1, i2, i3]),
323 rows,
324 start_row,
325 columns: packed & 15,
326 start_column: (packed >> 4) & 3,
327 allocated: (packed >> 6) & 1 == 1,
328 semantic_kind: kind,
329 component_type: component,
330 interpolation,
331 dynamic_index_mask: masks & 15,
332 stream: (masks >> 4) & 3,
333 reserved,
334 });
335 at = advance(at, 16)?;
336 }
337 }
338 }
339 let [inputs, outputs, patch_constants] = elements;
340
341 let rest = data.get(at..).unwrap_or_default();
342 let (words, ragged) = rest.as_chunks::<4>();
343 if !ragged.is_empty() {
344 return Err(error(
345 bit(at),
346 format!(
347 "PSV0: {} bytes after the elements, not a whole number of words",
348 rest.len()
349 ),
350 ));
351 }
352 Ok(Psv {
353 runtime,
354 resources,
355 strings,
356 semantic_indices,
357 inputs,
358 outputs,
359 patch_constants,
360 dependencies: words.iter().map(|w| u32::from_le_bytes(*w)).collect(),
361 })
362}
363
364/// The `PSV0` part of a container's directory, if it has one (a library has none; it has runtime data, [`crate::rdat`]).
365pub fn part(chunks: &[dxcontainer::Chunk<'_>]) -> Result<Option<Psv>> {
366 chunks
367 .iter()
368 .find(|c| &c.kind == b"PSV0")
369 .map(|c| read(c.data))
370 .transpose()
371}