use crate::opcodes::NAMES;
use crate::{Result, error};
fn low8(v: u32) -> u8 {
let [byte, ..] = v.to_le_bytes();
byte
}
fn low16(v: u32) -> u16 {
let [a, b, ..] = v.to_le_bytes();
u16::from_le_bytes([a, b])
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Register {
Temp,
Input,
Output,
IndexableTemp,
Constant,
Immediate,
Texture,
Sampler,
Null,
Uav,
ImmediateBuffer,
PrimitiveId,
InputCoverage,
OutputDepth,
OutputDepthGreaterEqual,
OutputDepthLessEqual,
OutputCoverage,
OutputStencilRef,
GroupSharedMemory,
ThreadId,
ThreadGroupId,
ThreadIdInGroup,
ThreadIdInGroupFlattened,
OutputControlPointId,
ForkInstanceId,
JoinInstanceId,
InputControlPoint,
OutputControlPoint,
InputPatchConstant,
InputDomainPoint,
Stream,
GsInstanceId,
}
impl Register {
fn dimensions(self) -> u32 {
match self {
Register::Immediate
| Register::Null
| Register::PrimitiveId
| Register::InputCoverage
| Register::OutputDepth
| Register::OutputDepthGreaterEqual
| Register::OutputDepthLessEqual
| Register::OutputCoverage
| Register::OutputStencilRef
| Register::ThreadId
| Register::ThreadGroupId
| Register::ThreadIdInGroup
| Register::ThreadIdInGroupFlattened
| Register::OutputControlPointId
| Register::ForkInstanceId
| Register::JoinInstanceId
| Register::InputDomainPoint
| Register::GsInstanceId => 0,
Register::Constant
| Register::IndexableTemp
| Register::InputControlPoint
| Register::OutputControlPoint => 2,
_ => 1,
}
}
fn from_type(n: u32) -> Option<Self> {
Some(match n {
0 => Register::Temp,
1 => Register::Input,
2 => Register::Output,
3 => Register::IndexableTemp,
4 => Register::Immediate,
6 => Register::Sampler,
7 => Register::Texture,
8 => Register::Constant,
9 => Register::ImmediateBuffer,
11 => Register::PrimitiveId,
12 => Register::OutputDepth,
13 => Register::Null,
15 => Register::OutputCoverage,
16 => Register::Stream,
22 => Register::OutputControlPointId,
23 => Register::ForkInstanceId,
24 => Register::JoinInstanceId,
25 => Register::InputControlPoint,
26 => Register::OutputControlPoint,
27 => Register::InputPatchConstant,
28 => Register::InputDomainPoint,
37 => Register::GsInstanceId,
30 => Register::Uav,
31 => Register::GroupSharedMemory,
32 => Register::ThreadId,
33 => Register::ThreadGroupId,
34 => Register::ThreadIdInGroup,
36 => Register::ThreadIdInGroupFlattened,
35 => Register::InputCoverage,
38 => Register::OutputDepthGreaterEqual,
39 => Register::OutputDepthLessEqual,
41 => Register::OutputStencilRef,
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Selection {
Mask(u8),
Swizzle([u8; 4]),
Scalar(u8),
None,
}
#[derive(Clone, Debug)]
pub struct Operand {
pub register: Register,
pub selection: Selection,
pub indices: Vec<Index>,
pub immediate: [u32; 4],
pub modifier: u8,
pub precision: u8,
pub non_uniform: bool,
}
#[derive(Clone, Debug)]
pub struct Index {
pub base: u32,
pub relative: Option<Box<Operand>>,
}
#[derive(Clone, Debug)]
pub struct Instruction {
pub word: usize,
pub opcode: u16,
pub control: u32,
pub extensions: Vec<u32>,
pub saturate: bool,
pub nonzero: bool,
pub operands: Vec<Operand>,
}
impl Instruction {
pub fn name(&self) -> &'static str {
opcode_name(self.opcode)
}
pub fn sample_offset(&self) -> Option<[i32; 3]> {
self.extension(1).map(|e| {
let field = |shift: u32| (((e >> shift) & 15) as i32) << 28 >> 28;
[field(9), field(13), field(17)]
})
}
pub fn resource_dimension(&self) -> Option<(u32, u32)> {
self.extension(2).map(|e| ((e >> 6) & 31, (e >> 11) & 0xf_ffff))
}
pub fn return_type(&self) -> Option<u32> {
self.extension(3).map(|e| (e >> 6) & 0xffff)
}
fn extension(&self, kind: u32) -> Option<u32> {
self.extensions.iter().copied().find(|e| e & 63 == kind)
}
}
pub fn opcode_name(opcode: u16) -> &'static str {
NAMES
.get(usize::from(opcode))
.copied()
.unwrap_or("unknown opcode")
}
pub type ReturnCodes = [u8; 4];
#[derive(Clone, Debug)]
pub struct Declaration {
pub word: usize,
pub opcode: u16,
pub control: u32,
pub kind: Declared,
}
#[derive(Clone, Debug)]
pub enum Declared {
Temps(u32),
IndexableTemp {
register: u32,
size: u32,
components: u32,
},
GlobalFlags(u32),
ConstantBuffer {
slot: u32,
rows: u32,
dynamic: bool,
dimensions: usize,
},
Sampler {
slot: u32,
mode: u32,
},
Resource {
slot: u32,
dimension: u32,
samples: u32,
returns: ReturnCodes,
token: u32,
},
ResourceRaw {
slot: u32,
},
ResourceStructured {
slot: u32,
stride: u32,
},
UavTyped {
slot: u32,
dimension: u32,
returns: ReturnCodes,
token: u32,
control: u32,
},
UavRaw {
slot: u32,
control: u32,
},
UavStructured {
slot: u32,
stride: u32,
control: u32,
},
ThreadGroup([u32; 3]),
GroupSharedRaw {
register: u32,
bytes: u32,
},
GroupSharedStructured {
register: u32,
stride: u32,
count: u32,
},
Input {
operand: Operand,
interpolation: Option<u32>,
name: Option<u32>,
},
Output {
operand: Operand,
name: Option<u32>,
},
ImmediateBuffer(Vec<[u32; 4]>),
CustomData {
class: u32,
},
Other {
opcode: u16,
control: u32,
words: Vec<u32>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProgramType {
Pixel,
Vertex,
Geometry,
Hull,
Domain,
Compute,
}
impl ProgramType {
pub fn name(self) -> &'static str {
match self {
ProgramType::Pixel => "ps",
ProgramType::Vertex => "vs",
ProgramType::Geometry => "gs",
ProgramType::Hull => "hs",
ProgramType::Domain => "ds",
ProgramType::Compute => "cs",
}
}
}
#[derive(Clone, Debug)]
pub struct Decoded {
pub kind: ProgramType,
pub major: u32,
pub minor: u32,
pub declarations: Vec<Declaration>,
pub instructions: Vec<Instruction>,
}
impl Decoded {
pub fn target(&self) -> String {
format!("{}_{}_{}", self.kind.name(), self.major, self.minor)
}
}
struct Cursor<'a> {
words: &'a [u32],
at: usize,
origin: usize,
kind: ProgramType,
}
impl Cursor<'_> {
fn position(&self) -> usize {
self.origin.saturating_add(self.at)
}
fn take(&mut self) -> Result<u32> {
let value = *self
.words
.get(self.at)
.ok_or_else(|| error(self.position(), "truncated instruction"))?;
self.at = self.at.saturating_add(1);
Ok(value)
}
fn rest(&mut self) -> Vec<u32> {
let rest = self.words.get(self.at..).unwrap_or_default().to_vec();
self.at = self.words.len();
rest
}
fn operand(&mut self, depth: usize) -> Result<Operand> {
let pos = self.position();
if depth > 2 {
return Err(error(pos, "nested relative operand exceeds bound"));
}
let t = self.take()?;
let components = t & 3;
let selection = match components {
0 => Selection::None,
1 => Selection::Scalar(0),
2 => match (t >> 2) & 3 {
0 => Selection::Mask(low8((t >> 4) & 15)),
1 => Selection::Swizzle([4, 6, 8, 10].map(|shift| low8((t >> shift) & 3))),
2 => Selection::Scalar(low8((t >> 4) & 3)),
_ => return Err(error(pos, "reserved component selection")),
},
_ => return Err(error(pos, "unsupported N-component operand")),
};
let kind = (t >> 12) & 255;
let register = Register::from_type(kind)
.ok_or_else(|| error(pos, format!("unsupported operand register type {kind}")))?;
let (mut modifier, mut precision, mut non_uniform) = (0, 0, false);
if t >> 31 != 0 {
let mut ext = self.take()?;
loop {
if ext & 63 != 1 {
return Err(error(pos, "unsupported extended operand (precision/modifier)"));
}
modifier = low8((ext >> 6) & 255);
precision = low8((ext >> 14) & 7);
non_uniform = ext & (1 << 17) != 0;
if ext >> 31 == 0 {
break;
}
ext = self.take()?;
}
if modifier > 3 {
return Err(error(pos, "unsupported extended operand (precision/modifier)"));
}
}
let dimensions = (t >> 20) & 3;
let expected = match (register, self.kind) {
(Register::Input, ProgramType::Geometry | ProgramType::Hull | ProgramType::Domain) => 2,
_ => register.dimensions(),
};
if dimensions != expected {
return Err(error(pos, "unexpected operand index dimensions"));
}
let mut indices = Vec::new();
for shift in [22, 25, 28].into_iter().take(dimensions as usize) {
let representation = (t >> shift) & 7;
if !matches!(representation, 0 | 2 | 3) {
return Err(error(pos, "unsupported 64-bit/reserved index"));
}
let base = if representation != 2 { self.take()? } else { 0 };
let relative = if representation != 0 {
Some(Box::new(self.operand(depth.saturating_add(1))?))
} else {
None
};
indices.push(Index { base, relative });
}
let mut immediate = [0; 4];
if register == Register::Immediate {
match components {
1 => immediate = [self.take()?; 4],
2 => {
for x in &mut immediate {
*x = self.take()?;
}
}
_ => return Err(error(pos, "immediate requires one or four components")),
}
}
Ok(Operand {
register,
selection,
indices,
immediate,
modifier,
precision,
non_uniform,
})
}
}
fn return_codes(token: u32) -> ReturnCodes {
[0, 4, 8, 12].map(|shift| low8((token >> shift) & 15))
}
fn is_declaration(opcode: u16) -> bool {
matches!(opcode, 88..=106 | 113..=116 | 143..=162 | 206)
}
pub fn decode(words: &[u32]) -> Result<Decoded> {
let (Some(&version), Some(&length)) = (words.first(), words.get(1)) else {
return Err(error(0, "token stream shorter than its header"));
};
if usize::try_from(length).ok() != Some(words.len()) {
return Err(error(
1,
format!(
"the declared length is {length} words, the stream has {}",
words.len()
),
));
}
let kind = match version >> 16 {
0 => ProgramType::Pixel,
1 => ProgramType::Vertex,
2 => ProgramType::Geometry,
3 => ProgramType::Hull,
4 => ProgramType::Domain,
5 => ProgramType::Compute,
other => {
return Err(error(
0,
format!("program type {other} is not a Shader Model 4 or 5 stage"),
));
}
};
let mut result = Decoded {
kind,
major: (version >> 4) & 15,
minor: version & 15,
declarations: Vec::new(),
instructions: Vec::new(),
};
let mut at = 2;
while let Some(&t) = words.get(at) {
let opcode = low16(t & 0x7ff);
if opcode == 53 {
let len = words
.get(at.saturating_add(1))
.and_then(|&n| usize::try_from(n).ok())
.unwrap_or(0);
let end = at.checked_add(len).filter(|&end| len >= 2 && end <= words.len());
let (Some(end), Some(body)) = (end, words.get(at.saturating_add(2)..at.saturating_add(len)))
else {
return Err(error(
at,
format!(
"customdata of {len} words (at least 2) does not fit the stream's {} words",
words.len()
),
));
};
let class = t >> 11;
let kind = if class == 3 {
let (rows, rest) = body.as_chunks::<4>();
if !rest.is_empty() {
return Err(error(
at,
format!(
"an immediate constant buffer of {} words, expected whole rows of 4",
body.len()
),
));
}
Declared::ImmediateBuffer(rows.to_vec())
} else {
Declared::CustomData { class }
};
result.declarations.push(Declaration {
word: at,
opcode,
control: class,
kind,
});
at = end;
continue;
}
let len = usize::try_from((t >> 24) & 127).unwrap_or(0);
if len == 0 {
return Err(error(at, "instruction length 0 (at least 1)"));
}
let end = at.checked_add(len).filter(|&end| end <= words.len());
let (Some(end), Some(body)) = (end, words.get(at.saturating_add(1)..at.saturating_add(len))) else {
return Err(error(
at,
format!(
"instruction length {len} (at least 1) does not fit the stream's {} words",
words.len()
),
));
};
let mut c = Cursor {
words: body,
at: 0,
origin: at.saturating_add(1),
kind,
};
let mut extensions = Vec::new();
let mut extended = t >> 31 != 0;
while extended {
let ext = c.take()?;
extensions.push(ext);
extended = ext >> 31 != 0;
}
let control = (t >> 11) & 0x1fff;
if is_declaration(opcode) {
let kind = declaration(opcode, control, &mut c)?;
if c.at != c.words.len() {
return Err(error(
at,
format!(
"{} words after the operands of {}",
c.words.len().saturating_sub(c.at),
opcode_name(opcode)
),
));
}
result.declarations.push(Declaration {
word: at,
opcode,
control,
kind,
});
} else {
let mut operands = Vec::new();
while c.at < c.words.len() {
operands.push(c.operand(0)?);
}
result.instructions.push(Instruction {
word: at,
opcode,
control,
extensions,
saturate: t & 0x2000 != 0,
nonzero: t & 0x4_0000 != 0,
operands,
});
}
at = end;
}
Ok(result)
}
fn declaration(opcode: u16, control: u32, c: &mut Cursor<'_>) -> Result<Declared> {
let pos = c.origin.saturating_sub(1);
let slot = |o: &Operand| -> Result<u32> {
o.indices
.first()
.map(|i| i.base)
.ok_or_else(|| error(pos, "missing declaration index"))
};
Ok(match opcode {
104 => Declared::Temps(c.take()?),
105 => Declared::IndexableTemp {
register: c.take()?,
size: c.take()?,
components: c.take()?,
},
106 => Declared::GlobalFlags(control),
88 => {
let o = c.operand(0)?;
let token = c.take()?;
Declared::Resource {
slot: slot(&o)?,
dimension: control & 31,
samples: (control >> 5) & 127,
returns: return_codes(token),
token,
}
}
89 => {
let o = c.operand(0)?;
Declared::ConstantBuffer {
slot: slot(&o)?,
rows: o.indices.get(1).map_or(0, |i| i.base),
dynamic: control & 1 != 0,
dimensions: o.indices.len(),
}
}
90 => {
let o = c.operand(0)?;
Declared::Sampler {
slot: slot(&o)?,
mode: control & 15,
}
}
95..=100 => {
let operand = c.operand(0)?;
let pixel = matches!(opcode, 98..=100);
let interpolation = pixel.then_some(control & 15);
let name = matches!(opcode, 96 | 97 | 99 | 100)
.then(|| c.take())
.transpose()?;
Declared::Input {
operand,
interpolation,
name,
}
}
101..=103 => {
let operand = c.operand(0)?;
let name = (opcode != 101).then(|| c.take()).transpose()?;
Declared::Output { operand, name }
}
156 => {
let o = c.operand(0)?;
let token = c.take()?;
Declared::UavTyped {
slot: slot(&o)?,
dimension: control & 31,
returns: return_codes(token),
token,
control,
}
}
157 => {
let o = c.operand(0)?;
Declared::UavRaw {
slot: slot(&o)?,
control,
}
}
158 => {
let o = c.operand(0)?;
Declared::UavStructured {
slot: slot(&o)?,
stride: c.take()?,
control,
}
}
155 => Declared::ThreadGroup([c.take()?, c.take()?, c.take()?]),
159 => {
let o = c.operand(0)?;
Declared::GroupSharedRaw {
register: slot(&o)?,
bytes: c.take()?,
}
}
160 => {
let o = c.operand(0)?;
Declared::GroupSharedStructured {
register: slot(&o)?,
stride: c.take()?,
count: c.take()?,
}
}
161 => {
let o = c.operand(0)?;
Declared::ResourceRaw { slot: slot(&o)? }
}
162 => {
let o = c.operand(0)?;
Declared::ResourceStructured {
slot: slot(&o)?,
stride: c.take()?,
}
}
_ => Declared::Other {
opcode,
control,
words: c.rest(),
},
})
}