use crate::opcodes::Shape;
use crate::{ConstantTable, DebugInfo, Opcode, Result, Stage, Version, error};
const MAX_WORDS: usize = 1_048_576;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RegisterType {
Temp,
Input,
Const,
Address,
Texture,
RasterizerOut,
AttributeOut,
Output,
ConstInt,
ColorOut,
DepthOut,
Sampler,
Const2,
Const3,
Const4,
ConstBool,
Loop,
Misc,
Label,
Predicate,
}
impl RegisterType {
pub fn code(self) -> u8 {
use RegisterType::*;
match self {
Temp => 0,
Input => 1,
Const => 2,
Address | Texture => 3,
RasterizerOut => 4,
AttributeOut => 5,
Output => 6,
ConstInt => 7,
ColorOut => 8,
DepthOut => 9,
Sampler => 10,
Const2 => 11,
Const3 => 12,
Const4 => 13,
ConstBool => 14,
Loop => 15,
Misc => 17,
Label => 18,
Predicate => 19,
}
}
fn of(code: u32, version: Version) -> std::result::Result<(Self, Option<u32>), String> {
use RegisterType::*;
let vertex = version.stage == Stage::Vertex;
let sm1 = version.major == 1;
let sm3 = version.major == 3;
let flow = !sm1 && (vertex || version.minor > 0 || sm3);
Ok(match code {
0 => (Temp, None),
1 => (Input, None),
2 => (Const, None),
3 if vertex => (Address, Some(0)),
3 if !sm3 => (Texture, None),
4 if vertex && !sm3 => (RasterizerOut, Some(2)),
5 if vertex && !sm3 => (AttributeOut, None),
6 if vertex => (Output, None),
7 if flow => (ConstInt, None),
8 if !vertex => (ColorOut, None),
9 if !vertex && !(sm1 && version.minor < 4) => (DepthOut, Some(0)),
10 if (!vertex && !sm1) || sm3 => (Sampler, None),
11 if vertex => (Const2, None),
12 if vertex => (Const3, None),
13 if vertex => (Const4, None),
14 if flow => (ConstBool, None),
15 if !sm1 && (vertex || sm3) => (Loop, Some(0)),
17 if !vertex && sm3 => (Misc, Some(1)),
18 if flow => (Label, None),
19 if !sm1 && (version.minor > 0 || sm3) => (Predicate, Some(0)),
16 | 20.. => return Err("is not a Direct3D 9 register type".into()),
_ => return Err(format!("is not a register of {version}")),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Register {
pub kind: RegisterType,
pub number: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Modifier {
None,
Negate,
Bias,
BiasNegate,
Sign,
SignNegate,
Complement,
X2,
X2Negate,
DivideZ,
DivideW,
Abs,
AbsNegate,
Not,
}
impl Modifier {
pub fn code(self) -> u8 {
use Modifier::*;
match self {
None => 0,
Negate => 1,
Bias => 2,
BiasNegate => 3,
Sign => 4,
SignNegate => 5,
Complement => 6,
X2 => 7,
X2Negate => 8,
DivideZ => 9,
DivideW => 10,
Abs => 11,
AbsNegate => 12,
Not => 13,
}
}
fn of(code: u32) -> Option<Self> {
use Modifier::*;
Some(match code {
0 => None,
1 => Negate,
2 => Bias,
3 => BiasNegate,
4 => Sign,
5 => SignNegate,
6 => Complement,
7 => X2,
8 => X2Negate,
9 => DivideZ,
10 => DivideW,
11 => Abs,
12 => AbsNegate,
13 => Not,
_ => return Option::None,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Relative {
pub word: Option<usize>,
pub register: Register,
pub swizzle: [u8; 4],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Destination {
pub word: usize,
pub register: Register,
pub mask: u8,
pub saturate: bool,
pub partial_precision: bool,
pub centroid: bool,
pub shift: i8,
pub relative: Option<Relative>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Source {
pub word: usize,
pub register: Register,
pub swizzle: [u8; 4],
pub modifier: Modifier,
pub relative: Option<Relative>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Comparison {
Greater,
Equal,
GreaterEqual,
Less,
NotEqual,
LessEqual,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Instruction {
pub word: usize,
pub opcode: Opcode,
pub control: u8,
pub coissue: bool,
pub predicate: Option<Source>,
pub destination: Option<Destination>,
pub sources: Vec<Source>,
}
impl Instruction {
pub fn comparison(&self) -> Option<Comparison> {
if !matches!(self.opcode, Opcode::IfC | Opcode::BreakC | Opcode::SetP) {
return None;
}
Some(match self.control {
1 => Comparison::Greater,
2 => Comparison::Equal,
3 => Comparison::GreaterEqual,
4 => Comparison::Less,
5 => Comparison::NotEqual,
_ => Comparison::LessEqual,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TextureType {
D2,
Cube,
Volume,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Declared {
Sampler(TextureType),
Semantic {
usage: u8,
index: u8,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Declaration {
pub word: usize,
pub token: u32,
pub declared: Declared,
pub destination: Destination,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Literal {
Float([u32; 4]),
Integer([i32; 4]),
Bool(u32),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Definition {
pub word: usize,
pub destination: Destination,
pub literal: Literal,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Comment {
pub word: usize,
pub data: Vec<u32>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Item {
Comment(Comment),
Declaration(Declaration),
Definition(Definition),
Instruction(Instruction),
}
impl Item {
pub fn word(&self) -> usize {
match self {
Item::Comment(c) => c.word,
Item::Declaration(d) => d.word,
Item::Definition(d) => d.word,
Item::Instruction(i) => i.word,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Program {
pub version: Version,
pub items: Vec<Item>,
pub end: usize,
pub constants: Option<ConstantTable>,
pub debug: Option<DebugInfo>,
}
impl Program {
pub fn comments(&self) -> impl Iterator<Item = &Comment> {
self.items.iter().filter_map(|i| match i {
Item::Comment(c) => Some(c),
_ => None,
})
}
pub fn declarations(&self) -> impl Iterator<Item = &Declaration> {
self.items.iter().filter_map(|i| match i {
Item::Declaration(d) => Some(d),
_ => None,
})
}
pub fn definitions(&self) -> impl Iterator<Item = &Definition> {
self.items.iter().filter_map(|i| match i {
Item::Definition(d) => Some(d),
_ => None,
})
}
pub fn instructions(&self) -> impl Iterator<Item = &Instruction> {
self.items.iter().filter_map(|i| match i {
Item::Instruction(i) => Some(i),
_ => None,
})
}
}
pub fn read(bytes: &[u8]) -> Result<Program> {
collect(records(bytes)?)
}
pub fn frame(bytes: &[u8]) -> Result<(Program, &[u8])> {
let program = collect(start(bytes, false)?)?;
let span = bytes
.get(..program.end.saturating_add(1).saturating_mul(4))
.ok_or_else(|| error(program.end, "END is past the buffer"))?;
Ok((program, span))
}
pub fn records(bytes: &[u8]) -> Result<Records<'_>> {
if !bytes.len().is_multiple_of(4) || bytes.len() > MAX_WORDS.saturating_mul(4) {
return Err(error(
0,
format!(
"token stream of {} bytes is not whole words or exceeds 1,048,576 words",
bytes.len()
),
));
}
start(bytes, true)
}
fn start(bytes: &[u8], whole: bool) -> Result<Records<'_>> {
let words = Words { bytes };
let token = words.get(0).ok_or_else(|| error(0, "no version token"))?;
let version = Version::from_token(token).ok_or_else(|| {
error(
0,
format!(
"version token {token:#010x} is not a Direct3D 9 vertex (0xfffe) or pixel (0xffff) program of \
version 1.0 to 3.0"
),
)
})?;
Ok(Records {
words,
version,
at: 1,
whole,
end: None,
done: false,
constants: None,
debug: None,
})
}
fn collect(mut records: Records<'_>) -> Result<Program> {
let mut items = Vec::new();
for item in records.by_ref() {
items.push(item?);
}
Ok(Program {
version: records.version,
items,
end: records.end.unwrap_or(0),
constants: records.constants,
debug: records.debug,
})
}
#[derive(Debug)]
pub struct Records<'a> {
words: Words<'a>,
version: Version,
at: usize,
whole: bool,
end: Option<usize>,
done: bool,
constants: Option<ConstantTable>,
debug: Option<DebugInfo>,
}
impl Records<'_> {
pub fn version(&self) -> Version {
self.version
}
pub fn constants(&self) -> Option<&ConstantTable> {
self.constants.as_ref()
}
pub fn debug(&self) -> Option<&DebugInfo> {
self.debug.as_ref()
}
pub fn end(&self) -> Option<usize> {
self.end
}
}
impl Iterator for Records<'_> {
type Item = Result<Item>;
fn next(&mut self) -> Option<Result<Item>> {
if self.done {
return None;
}
match self.record() {
Ok(Some(item)) => Some(Ok(item)),
Ok(None) => {
self.done = true;
let words = self.words.len();
(self.whole && self.at != words).then(|| {
Err(error(
self.at,
format!("{} words after END", words.saturating_sub(self.at)),
))
})
}
Err(e) => {
self.done = true;
Some(Err(e))
}
}
}
}
#[derive(Debug)]
struct Words<'a> {
bytes: &'a [u8],
}
impl Words<'_> {
fn len(&self) -> usize {
(self.bytes.len() / 4).min(MAX_WORDS)
}
fn get(&self, at: usize) -> Option<u32> {
if at >= MAX_WORDS {
return None;
}
let start = at.checked_mul(4)?;
let word: [u8; 4] = self.bytes.get(start..start.checked_add(4)?)?.try_into().ok()?;
Some(u32::from_le_bytes(word))
}
}
fn bits(word: u32, shift: u32, mask: u32) -> u8 {
u8::try_from(word.checked_shr(shift).unwrap_or(0) & mask & 0xff).unwrap_or(u8::MAX)
}
fn register(token: u32, at: usize, version: Version) -> Result<Register> {
if token & 0x8000_0000 == 0 {
return Err(error(at, format!("operand token {token:#010x} lacks bit 31")));
}
if token & 0xc000 != 0 {
return Err(error(
at,
format!("operand token {token:#010x} sets reserved bits 14 to 15"),
));
}
let code = ((token >> 28) & 7) | ((token >> 8) & 0x18);
let number = token & 0x7ff;
let (kind, last) =
RegisterType::of(code, version).map_err(|why| error(at, format!("register type {code} {why}")))?;
if last.is_some_and(|last| number > last) {
return Err(error(
at,
format!(
"register {kind:?} {number}, where the type has registers 0 to {}",
last.unwrap_or(0)
),
));
}
Ok(Register { kind, number })
}
struct Operands<'a> {
words: &'a Words<'a>,
at: usize,
end: usize,
instruction: usize,
version: Version,
}
impl Operands<'_> {
fn next(&mut self) -> Result<(usize, u32)> {
let at = self.at;
let token = (at < self.end)
.then(|| self.words.get(at))
.flatten()
.ok_or_else(|| {
error(
self.instruction,
format!(
"instruction's operand span extends past {} (it ends at word {})",
if self.end < self.words.len() {
"its stated length"
} else {
"the stream"
},
self.end.min(self.words.len())
),
)
})?;
self.at = at.saturating_add(1);
Ok((at, token))
}
fn relative(&mut self, operand: usize, what: &str) -> Result<Relative> {
let version = self.version;
if version.major == 1 {
return Ok(Relative {
word: None,
register: Register {
kind: RegisterType::Address,
number: 0,
},
swizzle: [0; 4],
});
}
let (at, token) = self.next().map_err(|e| {
error(
operand,
format!(
"relative addressing of {what} without its address token: {}",
e.detail
),
)
})?;
let register = register(token & !0x2000, at, version)?;
if !matches!(register.kind, RegisterType::Address | RegisterType::Loop) || token & 0x2000 != 0 {
return Err(error(
at,
format!(
"relative address token of {what} names {register:?}{}, where it is the address register a0 or the \
loop counter aL",
if token & 0x2000 != 0 {
" relatively addressed itself"
} else {
""
}
),
));
}
if (token >> 24) & 15 != 0 {
return Err(error(
at,
format!(
"relative address token of {what} has source modifier {}, where it has none",
(token >> 24) & 15
),
));
}
Ok(Relative {
word: Some(at),
register,
swizzle: [16, 18, 20, 22].map(|shift| bits(token, shift, 3)),
})
}
fn destination(&mut self) -> Result<Destination> {
let (at, token) = self.next()?;
let version = self.version;
let register = register(token & !0x2000, at, version)?;
let modifiers = (token >> 20) & 15;
if modifiers & 8 != 0 {
return Err(error(
at,
format!("destination {token:#010x} sets result modifier bit 23"),
));
}
let shift = bits(token, 24, 15);
let shift = if shift >= 8 {
i8::try_from(i16::from(shift).saturating_sub(16)).unwrap_or(0)
} else {
i8::try_from(shift).unwrap_or(0)
};
let pixel1 = version.stage == Stage::Pixel && version.major == 1;
if !(shift == 0 || (pixel1 && (-3..=3).contains(&shift))) {
return Err(error(
at,
format!(
"destination {token:#010x} has result shift {shift}, where it is 0 (or -3 to 3 in pixel 1.x)"
),
));
}
let relative = if token & 0x2000 != 0 {
if version.major == 1 {
return Err(error(
at,
"relative addressing of a destination in Shader Model 1",
));
}
Some(self.relative(at, "a destination")?)
} else {
None
};
Ok(Destination {
word: at,
register,
mask: bits(token, 16, 15),
saturate: modifiers & 1 != 0,
partial_precision: modifiers & 2 != 0,
centroid: modifiers & 4 != 0,
shift,
relative,
})
}
fn source(&mut self) -> Result<Source> {
let (at, token) = self.next()?;
let version = self.version;
let register = register(token & !0x2000, at, version)?;
let modifier = Modifier::of((token >> 24) & 15).ok_or_else(|| {
error(
at,
format!("source modifier {} is not one of 0 to 13", (token >> 24) & 15),
)
})?;
let relative = if token & 0x2000 != 0 {
if version.major == 1 && version.stage == Stage::Pixel {
return Err(error(at, "relative addressing in a pixel 1.x program"));
}
Some(self.relative(at, "a source")?)
} else {
None
};
Ok(Source {
word: at,
register,
swizzle: [16, 18, 20, 22].map(|shift| bits(token, shift, 3)),
modifier,
relative,
})
}
}
impl Records<'_> {
fn record(&mut self) -> Result<Option<Item>> {
let version = self.version;
let words = &self.words;
let mut at = self.at;
let word = at;
let token = words.get(word).ok_or_else(|| {
error(
word,
if word >= MAX_WORDS {
"the program exceeds 1,048,576 words without END"
} else {
"missing END"
},
)
})?;
at = at.saturating_add(1);
if token == 0xffff {
self.end = Some(word);
self.at = at;
return Ok(None);
}
let op = token & 0xffff;
if op == 0xfffe {
if token & 0x8000_0000 != 0 {
return Err(error(word, "COMMENT reserved bit"));
}
let count = usize::try_from((token >> 16) & 0x7fff).unwrap_or(usize::MAX);
let end = at.saturating_add(count);
let data: Vec<u32> = (at..end).map_while(|n| words.get(n)).collect();
if data.len() != count {
return Err(error(
word,
format!(
"instruction/comment span of {count} words extends past the stream's {} words",
words.len()
),
));
}
if let Some((&tag, rest)) = data.split_first() {
let bytes = || -> Vec<u8> { rest.iter().flat_map(|w| w.to_le_bytes()).collect() };
if tag == u32::from_le_bytes(*b"CTAB") {
if self.constants.is_some() {
return Err(error(word, "duplicate CTAB"));
}
self.constants = Some(crate::ctab::read(
&bytes(),
word.saturating_add(2),
version.token(),
)?);
} else if tag == u32::from_le_bytes(*b"DBUG") {
if self.debug.is_some() {
return Err(error(word, "duplicate DBUG"));
}
self.debug = Some(crate::debug::read(&bytes(), word.saturating_add(2))?);
}
}
self.at = end;
return Ok(Some(Item::Comment(Comment { word, data })));
}
let opcode = u16::try_from(op)
.ok()
.and_then(Opcode::from_code)
.ok_or_else(|| error(word, format!("opcode {op} is not a Direct3D 9 instruction")))?;
if !opcode.in_version(version) {
let other = Version {
stage: match version.stage {
Stage::Vertex => Stage::Pixel,
Stage::Pixel => Stage::Vertex,
},
..version
};
let detail = if opcode.in_version(other) {
let stage = match other.stage {
Stage::Vertex => "vertex",
Stage::Pixel => "pixel",
};
format!(
"{} (opcode {op}) is a {stage} instruction, not one of {version}",
opcode.name()
)
} else {
format!(
"{} (opcode {op}) is not an instruction of {version}",
opcode.name()
)
};
return Err(error(word, detail));
}
let shape = opcode.shape(version);
let sm1 = version.major == 1;
let length = usize::from(bits(token, 24, 15));
let predicated = token & 0x1000_0000 != 0;
let coissue = token & 0x4000_0000 != 0;
let control = bits(token, 16, 0xff);
if token & 0xa000_0000 != 0 {
return Err(error(
word,
format!("instruction token {token:#010x} sets reserved bit 29 or 31"),
));
}
if coissue && !(sm1 && version.stage == Stage::Pixel) {
return Err(error(word, "co-issue outside a pixel 1.x program"));
}
if predicated && (sm1 || (version.major == 2 && version.minor == 0)) {
return Err(error(
word,
format!("predication in {version}, which has no predicate register"),
));
}
if shape == Shape::Phase && token != 0xfffd {
return Err(error(
word,
format!("phase token {token:#010x} has bits beyond the opcode"),
));
}
let controlled = match opcode {
Opcode::Tex if !sm1 => (0..=2).contains(&control),
Opcode::IfC | Opcode::BreakC | Opcode::SetP => (1..=6).contains(&control),
_ => control == 0,
};
if !controlled {
return Err(error(
word,
format!(
"{} has control bits {control:#x}, which it does not define",
opcode.name()
),
));
}
let end = if sm1 {
words.len()
} else {
let end = at.saturating_add(length);
if end > words.len() {
return Err(error(
word,
format!(
"instruction/comment span of {length} operands extends past the stream's {} words",
words.len()
),
));
}
end
};
let mut operands = Operands {
words,
at,
end,
instruction: word,
version,
};
let item = match shape {
Shape::Phase => Item::Instruction(Instruction {
word,
opcode,
control,
coissue,
predicate: None,
destination: None,
sources: Vec::new(),
}),
Shape::Declaration => {
if predicated {
return Err(error(word, "predicated dcl"));
}
let (token_at, token) = operands.next()?;
let destination = operands.destination()?;
Item::Declaration(Declaration {
word,
token,
declared: declared(token, token_at, destination.register)?,
destination,
})
}
Shape::Definition { words: count } => {
if predicated {
return Err(error(word, format!("predicated {}", opcode.name())));
}
let destination = operands.destination()?;
let expected = match opcode {
Opcode::Def => RegisterType::Const,
Opcode::DefI => RegisterType::ConstInt,
_ => RegisterType::ConstBool,
};
if destination.register.kind != expected || destination.relative.is_some() {
return Err(error(
destination.word,
format!(
"{} defines {:?}, where it defines a {expected:?} register",
opcode.name(),
destination.register
),
));
}
let mut values = [0u32; 4];
for value in values.iter_mut().take(count) {
*value = operands.next()?.1;
}
let literal = match opcode {
Opcode::Def => Literal::Float(values),
Opcode::DefI => Literal::Integer(values.map(|v| i32::from_le_bytes(v.to_le_bytes()))),
_ => {
let [value, ..] = values;
Literal::Bool(value)
}
};
Item::Definition(Definition {
word,
destination,
literal,
})
}
Shape::Operands { destination, sources } => {
let destination = if destination {
Some(operands.destination()?)
} else {
None
};
let predicate = if predicated {
let p = operands.source()?;
if p.register.kind != RegisterType::Predicate
|| !matches!(p.modifier, Modifier::None | Modifier::Not)
{
return Err(error(
p.word,
format!(
"the predicate token names {:?} with {:?}, where it is p0 or !p0",
p.register, p.modifier
),
));
}
Some(p)
} else {
None
};
let mut read = Vec::with_capacity(sources);
for _ in 0..sources {
read.push(operands.source()?);
}
Item::Instruction(Instruction {
word,
opcode,
control,
coissue,
predicate,
destination,
sources: read,
})
}
};
let used = operands.at.saturating_sub(at);
if sm1 {
if length != 0 && length != used {
return Err(error(
word,
format!(
"{} states {length} operand tokens, where its operands take {used}",
opcode.name()
),
));
}
} else if used != length {
return Err(error(
word,
format!(
"{} states {length} operand tokens, where its operands take {used}",
opcode.name()
),
));
}
self.at = operands.at;
Ok(Some(item))
}
}
fn declared(token: u32, at: usize, register: Register) -> Result<Declared> {
if token & 0x8000_0000 == 0 {
return Err(error(at, format!("DCL token {token:#010x} lacks bit 31")));
}
if register.kind == RegisterType::Sampler {
return Ok(Declared::Sampler(match token {
0x9000_0000 => TextureType::D2,
0x9800_0000 => TextureType::Cube,
0xa000_0000 => TextureType::Volume,
_ => {
return Err(error(
at,
format!(
"DCL sampler texture type {token:#x}, where it is 0x90000000 (2d), 0x98000000 (cube) or \
0xa0000000 (volume)"
),
));
}
}));
}
if token & !0x800f_000f != 0 {
return Err(error(at, "DCL semantic reserved bits"));
}
let usage = bits(token, 0, 15);
if usage > 13 {
return Err(error(at, format!("DCL usage {usage}, where it is 0 to 13")));
}
Ok(Declared::Semantic {
usage,
index: bits(token, 16, 15),
})
}