use primase_foundations::bytes::View;
use crate::{Result, error};
const MAX_RECORDS: usize = 1 << 20;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RuntimeInfo {
pub stage_words: [u32; 4],
pub min_wave_lanes: u32,
pub max_wave_lanes: u32,
pub stage: u8,
pub uses_view_id: bool,
pub stage_data: u16,
pub element_counts: [u8; 3],
pub input_vectors: u8,
pub output_vectors: [u8; 4],
pub num_threads: Option<[u32; 3]>,
pub entry_name: Option<u32>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Resource {
pub resource_type: u32,
pub space: u32,
pub lower: u32,
pub upper: u32,
pub kind: Option<u32>,
pub flags: Option<u32>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Element {
pub name: u32,
pub indices: u32,
pub rows: u8,
pub start_row: u8,
pub columns: u8,
pub start_column: u8,
pub allocated: bool,
pub semantic_kind: u8,
pub component_type: u8,
pub interpolation: u8,
pub dynamic_index_mask: u8,
pub stream: u8,
pub reserved: u8,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Psv {
pub runtime: RuntimeInfo,
pub resources: Vec<Resource>,
pub strings: Vec<u8>,
pub semantic_indices: Vec<u32>,
pub inputs: Vec<Element>,
pub outputs: Vec<Element>,
pub patch_constants: Vec<Element>,
pub dependencies: Vec<u32>,
}
impl Psv {
pub fn string(&self, offset: u32) -> Result<&str> {
let rest = usize::try_from(offset)
.ok()
.and_then(|at| self.strings.get(at..))
.ok_or_else(|| error(0, format!("PSV0: string {offset} past the string table")))?;
let end = rest
.iter()
.position(|b| *b == 0)
.ok_or_else(|| error(0, "PSV0: a string without its end"))?;
std::str::from_utf8(rest.get(..end).unwrap_or_default())
.map_err(|_| error(0, "PSV0: a string that is not UTF-8"))
}
pub fn element_indices(&self, element: &Element) -> Result<&[u32]> {
usize::try_from(element.indices)
.ok()
.and_then(|start| {
let end = start.checked_add(usize::from(element.rows))?;
self.semantic_indices.get(start..end)
})
.ok_or_else(|| error(0, "PSV0: an element's semantic indices lie past the index table"))
}
}
pub fn read(data: &[u8]) -> Result<Psv> {
let view = View::new(data);
let bit = |at: usize| at.saturating_mul(8);
let word = |at: usize| -> Result<u32> {
view.array::<4>(at)
.map(u32::from_le_bytes)
.map_err(|e| error(bit(at), format!("PSV0: {e}")))
};
let count = |at: usize, what: &str| -> Result<usize> {
usize::try_from(word(at)?).map_err(|_| error(bit(at), format!("PSV0: {what} past the address space")))
};
let advance = |at: usize, by: usize| {
at.checked_add(by)
.ok_or_else(|| error(bit(at), "PSV0: a table past the address space"))
};
let info_size = count(0, "the runtime info size")?;
let mut at = 4usize;
if !matches!(info_size, 36 | 48 | 52) {
return Err(error(
0,
format!("PSV0: a runtime info of {info_size} bytes, where only 36, 48 and 52 are read"),
));
}
let info = view
.slice(at, info_size)
.map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
let info_word = |i: usize| -> Result<u32> {
let start = i.saturating_mul(4);
info.get(start..start.saturating_add(4))
.and_then(|w| <[u8; 4]>::try_from(w).ok())
.map(u32::from_le_bytes)
.ok_or_else(|| error(bit(at), "PSV0: the runtime info is cut short"))
};
let byte = |i: usize| -> Result<u8> {
info.get(i)
.copied()
.ok_or_else(|| error(bit(at), "PSV0: the runtime info is cut short"))
};
let runtime = RuntimeInfo {
stage_words: [info_word(0)?, info_word(1)?, info_word(2)?, info_word(3)?],
min_wave_lanes: info_word(4)?,
max_wave_lanes: info_word(5)?,
stage: byte(24)?,
uses_view_id: byte(25)? != 0,
stage_data: u16::from_le_bytes([byte(26)?, byte(27)?]),
element_counts: [byte(28)?, byte(29)?, byte(30)?],
input_vectors: byte(31)?,
output_vectors: [byte(32)?, byte(33)?, byte(34)?, byte(35)?],
num_threads: if info_size >= 48 {
Some([info_word(9)?, info_word(10)?, info_word(11)?])
} else {
None
},
entry_name: if info_size >= 52 {
Some(info_word(12)?)
} else {
None
},
};
at = advance(at, info_size)?;
let resource_count = count(at, "the resource count")?;
at = advance(at, 4)?;
let mut resources = Vec::new();
if resource_count > 0 {
if resource_count > MAX_RECORDS {
return Err(error(bit(at), format!("PSV0: {resource_count} resource records")));
}
let size = count(at, "the resource record size")?;
at = advance(at, 4)?;
if !matches!(size, 16 | 24) {
return Err(error(
bit(at),
format!("PSV0: resource records of {size} bytes, where only 16 and 24 are read"),
));
}
for _ in 0..resource_count {
let long = size == 24;
resources.push(Resource {
resource_type: word(at)?,
space: word(advance(at, 4)?)?,
lower: word(advance(at, 8)?)?,
upper: word(advance(at, 12)?)?,
kind: if long { Some(word(advance(at, 16)?)?) } else { None },
flags: if long { Some(word(advance(at, 20)?)?) } else { None },
});
at = advance(at, size)?;
}
}
let strings_size = count(at, "the string table size")?;
at = advance(at, 4)?;
let strings = view
.slice(at, strings_size)
.map_err(|e| error(bit(at), format!("PSV0: {e}")))?
.to_vec();
at = advance(at, strings_size)?;
let index_count = count(at, "the semantic index count")?;
at = advance(at, 4)?;
let index_bytes = view
.slice(
at,
index_count
.checked_mul(4)
.ok_or_else(|| error(bit(at), "PSV0: the semantic index count"))?,
)
.map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
let semantic_indices = index_bytes
.as_chunks::<4>()
.0
.iter()
.map(|w| u32::from_le_bytes(*w))
.collect();
at = advance(at, index_bytes.len())?;
let mut elements = [Vec::new(), Vec::new(), Vec::new()];
if runtime.element_counts.iter().any(|n| *n > 0) {
let size = count(at, "the element size")?;
at = advance(at, 4)?;
if size != 16 {
return Err(error(
bit(at),
format!("PSV0: signature elements of {size} bytes, where only 16 are read"),
));
}
for (list, n) in elements.iter_mut().zip(runtime.element_counts) {
for _ in 0..n {
let raw = view
.array::<16>(at)
.map_err(|e| error(bit(at), format!("PSV0: {e}")))?;
let [
n0,
n1,
n2,
n3,
i0,
i1,
i2,
i3,
rows,
start_row,
packed,
kind,
component,
interpolation,
masks,
reserved,
] = raw;
list.push(Element {
name: u32::from_le_bytes([n0, n1, n2, n3]),
indices: u32::from_le_bytes([i0, i1, i2, i3]),
rows,
start_row,
columns: packed & 15,
start_column: (packed >> 4) & 3,
allocated: (packed >> 6) & 1 == 1,
semantic_kind: kind,
component_type: component,
interpolation,
dynamic_index_mask: masks & 15,
stream: (masks >> 4) & 3,
reserved,
});
at = advance(at, 16)?;
}
}
}
let [inputs, outputs, patch_constants] = elements;
let rest = data.get(at..).unwrap_or_default();
let (words, ragged) = rest.as_chunks::<4>();
if !ragged.is_empty() {
return Err(error(
bit(at),
format!(
"PSV0: {} bytes after the elements, not a whole number of words",
rest.len()
),
));
}
Ok(Psv {
runtime,
resources,
strings,
semantic_indices,
inputs,
outputs,
patch_constants,
dependencies: words.iter().map(|w| u32::from_le_bytes(*w)).collect(),
})
}
pub fn part(chunks: &[dxcontainer::Chunk<'_>]) -> Result<Option<Psv>> {
chunks
.iter()
.find(|c| &c.kind == b"PSV0")
.map(|c| read(c.data))
.transpose()
}