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