1use std::collections::BTreeMap;
4use std::fmt::Write;
5
6use crate::{
7 Comparison, Constant, ConstantTable, DebugInfo, Declared, Destination, Error, Instruction, Item, Literal,
8 Modifier, Opcode, Precision, Program, Register, RegisterType, Relative, Result, Source, SourceLine,
9 Stage, TextureType, Type, read,
10};
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14#[non_exhaustive]
15pub struct Options {
16 pub header: bool,
19 pub default_values: bool,
22 pub numbering: bool,
26 pub offsets: bool,
29 pub hex_literals: bool,
31 pub debug_info: bool,
34}
35
36impl Default for Options {
37 fn default() -> Self {
38 Self {
39 header: true,
40 default_values: false,
41 numbering: false,
42 offsets: false,
43 hex_literals: false,
44 debug_info: true,
45 }
46 }
47}
48
49impl Options {
50 pub fn from_flags(flags: u32) -> Result<Self> {
53 let refuse = |detail: String| Error { word: 0, detail };
54 if flags & 0x01 != 0 {
55 return Err(refuse(
56 "flag 0x01, D3D_DISASM_ENABLE_COLOR_CODE (an HTML listing), is not written".into(),
57 ));
58 }
59 if flags & 0x08 != 0 {
60 return Err(refuse(
61 "flag 0x08, D3D_DISASM_ENABLE_INSTRUCTION_CYCLE, which Microsoft's disassembler refuses too, is not \
62 written"
63 .into(),
64 ));
65 }
66 let unknown = flags & !0xf6;
67 if unknown != 0 {
68 return Err(refuse(format!(
69 "flag bit 0x{:x} is not a D3D_DISASM flag",
70 1u32 << unknown.trailing_zeros()
71 )));
72 }
73 Ok(Self {
74 header: flags & 0x40 == 0,
75 default_values: flags & 0x02 != 0,
76 numbering: flags & 0x04 != 0,
77 offsets: flags & 0x20 != 0,
78 hex_literals: flags & 0x80 != 0,
79 debug_info: flags & 0x10 == 0,
80 })
81 }
82}
83
84const LANES: [char; 4] = ['x', 'y', 'z', 'w'];
85
86pub fn disassemble(bytes: &[u8]) -> Result<String> {
90 disassemble_with(bytes, &Options::default())
91}
92
93pub fn disassemble_with(bytes: &[u8], options: &Options) -> Result<String> {
95 let program = read(bytes)?;
96 let mut out = String::new();
97 if let Some(table) = program.constants.as_ref().filter(|_| options.header) {
99 header(&mut out, table, options.default_values);
100 }
101 let _ = writeln!(out, " {}", program.version);
102 let debug = program.debug.as_ref().filter(|_| options.debug_info);
103 let notes = debug.map(variables).unwrap_or_default();
104 let records: BTreeMap<u32, usize> = debug
106 .map(|d| d.lines.iter().enumerate().map(|(n, l)| (l.offset, n)).collect())
107 .unwrap_or_default();
108 let mut source: Option<(u16, u16)> = None;
110 let mut depth: usize = 0;
111 let mut slot: u32 = 0;
113 let mut first = true;
115 for item in &program.items {
116 if matches!(item, Item::Comment(_)) {
117 continue;
118 }
119 let record = u32::try_from(item.word().saturating_mul(4))
120 .ok()
121 .and_then(|offset| records.get(&offset))
122 .copied();
123 let line = record.and_then(|n| debug.and_then(|d| d.lines.get(n)));
125 if let (
126 Some(d),
127 Some(SourceLine {
128 line,
129 file: Some(file),
130 ..
131 }),
132 ) = (debug, line)
133 {
134 match source {
135 Some((f, l)) if f == *file => {
136 if line.abs_diff(l) > 2 {
137 let _ = writeln!(out, "\n#line {line}");
138 }
139 }
140 _ => {
141 let path = d.files.get(usize::from(*file)).map_or("", String::as_str);
142 let _ = writeln!(out, "\n#line {line} \"{path}\"");
143 }
144 }
145 source = Some((*file, *line));
146 }
147 let mut prefix = String::new();
150 if options.numbering {
151 match item {
152 Item::Instruction(i) if !i.coissue && !matches!(i.opcode, Opcode::Nop | Opcode::Label) => {
153 let colon = if options.offsets { ' ' } else { ':' };
154 let _ = write!(prefix, "{slot:>4}{colon} ");
155 slot = slot.saturating_add(slots(i, false, &program).1);
156 }
157 Item::Instruction(i) => {
158 slot = slot.saturating_add(slots(i, first, &program).1);
159 prefix.push_str(" ");
160 }
161 _ => prefix.push_str(" "),
162 }
163 }
164 if matches!(item, Item::Instruction(_)) {
165 first = false;
166 }
167 if options.offsets {
168 let _ = write!(prefix, "0x{:08X}: ", item.word().saturating_mul(4));
169 }
170 let indent = match item {
172 Item::Instruction(i) if i.coissue => " + ",
173 _ => " ",
174 };
175 let line = match item {
176 Item::Comment(_) => continue,
177 Item::Declaration(d) => format!("{}{}", " ".repeat(depth), declaration(d, &program)),
179 Item::Definition(d) => format!(
180 "{}{}",
181 " ".repeat(depth),
182 definition(&d.literal, &d.destination, &program, options)
183 ),
184 Item::Instruction(i) => {
185 if matches!(
186 i.opcode,
187 Opcode::Else | Opcode::EndIf | Opcode::EndLoop | Opcode::EndRep
188 ) {
189 depth = depth.saturating_sub(1);
190 }
191 let text = instruction(i, &program);
192 let line = format!("{}{text}", " ".repeat(depth));
193 if matches!(
194 i.opcode,
195 Opcode::If | Opcode::IfC | Opcode::Else | Opcode::Loop | Opcode::Rep
196 ) {
197 depth = depth.saturating_add(1);
198 }
199 line
200 }
201 };
202 let _ = write!(out, "{prefix}{indent}{}", line.trim_end());
203 if let Some(written) = record.and_then(|n| notes.get(n)).filter(|w| !w.is_empty()) {
204 let _ = write!(out, " // {}", written.join(", "));
205 }
206 out.push('\n');
207 }
208 out.push('\n');
209 footer(&mut out, &program);
210 Ok(out)
211}
212
213fn variables(debug: &DebugInfo) -> Vec<Vec<String>> {
218 let mut notes = vec![Vec::new(); debug.lines.len()];
219 for v in &debug.variables {
220 let name = match &v.scope {
221 Some(scope) if *scope == debug.entry => v.name.clone(),
222 Some(scope) => format!("{scope}::{}", v.name),
223 None => format!("::{}", v.name),
224 };
225 for l in &v.locations {
226 let components: Vec<String> = l.components.iter().flatten().map(u16::to_string).collect();
227 if let Some(n) = usize::try_from(l.instruction).ok().and_then(|n| notes.get_mut(n)) {
228 n.push(format!("{name}<{}>", components.join(",")));
229 }
230 }
231 }
232 notes
233}
234
235fn type_kind(kind: u16) -> &'static str {
238 match kind {
239 0 => "void",
240 1 => "bool",
241 2 => "int",
242 3 => "float",
243 4 => "string",
244 5 => "Texture",
245 6 => "Texture1D",
246 7 => "Texture2D",
247 8 => "Texture3D",
248 9 => "TextureCube",
249 10 => "sampler",
250 11 => "sampler1D",
251 12 => "sampler2D",
252 13 => "sampler3D",
253 14 => "samplerCUBE",
254 15 => "PixelShader",
255 16 => "VertexShader",
256 17 => "pixelfragment",
257 18 => "vertexfragment",
258 19 => "uint",
259 20 => "byte",
260 21 => "GeometryShader",
261 22 => "RasterizerState",
262 23 => "DepthStencilState",
263 24 => "BlendState",
264 25 => "Buffer",
265 28 => "Texture1DArray",
266 29 => "Texture2DArray",
267 30 => "RenderTargetView",
268 31 => "DepthStencilView",
269 32 => "Texture2DMS",
270 33 => "Texture2DMSArray",
271 34 => "TextureCubeArray",
272 35 => "HullShader",
273 36 => "DomainShader",
274 37 => "interface",
275 38 => "ComputeShader",
276 39 => "double",
277 52 => "min8float",
278 53 => "min10float",
279 54 => "min16float",
280 55 => "min12int",
281 56 => "min16int",
282 57 => "min16uint",
283 _ => "UNKNOWN",
284 }
285}
286
287fn type_name(ty: &Type) -> String {
290 let kind = type_kind(ty.kind);
291 match ty.class {
292 0 | 4 => kind.to_owned(),
293 1 => format!("{kind}{}", ty.columns),
294 2 => format!("row_major {kind}{}x{}", ty.rows, ty.columns),
295 3 => format!("{kind}{}x{}", ty.rows, ty.columns),
296 _ => String::new(),
297 }
298}
299
300fn bank(set: u16) -> &'static str {
302 match set {
303 0 => "b",
304 1 => "i",
305 3 => "s",
306 _ => "c",
307 }
308}
309
310fn header(out: &mut String, table: &ConstantTable, defaults: bool) {
314 let _ = writeln!(out, "//\n// Generated by {}", table.creator);
315 let mut constants: Vec<&Constant> = table.constants.iter().collect();
316 if constants.is_empty() {
317 return;
318 }
319 out.push_str("//\n// Parameters:\n//\n");
322 let mut parameters = constants.clone();
323 parameters.dedup_by(|a, b| a.name == b.name);
324 let parameters: Vec<(&Type, &str)> = parameters.iter().map(|c| (&c.ty, c.name.as_str())).collect();
325 list(out, 3, ¶meters);
326 out.push_str("//\n//\n// Registers:\n//\n");
327 constants.sort_by_key(|c| (c.register_set, c.register));
328 let width = constants.iter().map(|c| c.name.len()).max().unwrap_or(0).max(12);
330 let _ = writeln!(out, "// {:<width$} Reg Size", "Name");
331 let _ = writeln!(out, "// {} ----- ----", "-".repeat(width));
332 for c in &constants {
333 let register = format!("{}{}", bank(c.register_set), c.register);
334 let _ = writeln!(out, "// {:<width$} {register:<5} {:>4}", c.name, c.count);
335 }
336 out.push_str("//\n");
337 if defaults && constants.iter().any(|c| c.default.is_some()) {
338 default_values(out, &constants);
339 }
340 out.push('\n');
341}
342
343fn default_values(out: &mut String, constants: &[&Constant]) {
349 out.push_str("//\n// Default values:\n//\n");
350 for c in constants {
351 let Some(default) = &c.default else { continue };
352 let _ = writeln!(out, "// {}", c.name);
353 let words: Vec<u32> = default
354 .as_chunks::<4>()
355 .0
356 .iter()
357 .map(|w| u32::from_le_bytes(*w))
358 .collect();
359 let values: Vec<String> = match c.register_set {
361 0 => words
362 .iter()
363 .map(|&w| (if w != 0 { "true" } else { "false" }).to_owned())
364 .collect(),
365 set => words
366 .chunks(4)
367 .map(|row| {
368 let row: Vec<String> = if set == 1 {
369 row.iter().map(|w| w.cast_signed().to_string()).collect()
370 } else {
371 row.iter().map(|&w| general(f32::from_bits(w), 6)).collect()
372 };
373 format!("{{ {} }}", row.join(", "))
374 })
375 .collect(),
376 };
377 for (register, value) in (u32::from(c.register)..).zip(values) {
378 let register = format!("{}{register}", bank(c.register_set));
379 let _ = writeln!(out, "// {register:<5}= {value};");
380 }
381 out.push_str("//\n");
382 }
383}
384
385fn list(out: &mut String, indent: usize, items: &[(&Type, &str)]) {
397 let pad = " ".repeat(indent);
398 let mut previous: Option<&Type> = None;
399 for &(ty, name) in items {
400 if previous.is_some_and(|p| p.class == 5 || ty.class == 5) {
401 let _ = writeln!(out, "//{pad}");
402 }
403 previous = Some(ty);
404 let array = if ty.elements > 1 {
405 format!("[{}]", ty.elements)
406 } else {
407 String::new()
408 };
409 if ty.class == 5 {
410 let _ = write!(out, "//{pad}struct\n//{pad}{{\n");
411 let members: Vec<(&Type, &str)> = ty.members.iter().map(|m| (&m.ty, m.name.as_str())).collect();
412 list(out, indent.saturating_add(4), &members);
413 let _ = writeln!(out, "//\n//{pad}}} {name}{array};");
414 } else {
415 let _ = writeln!(out, "//{pad}{} {name}{array};", type_name(ty));
416 }
417 }
418}
419
420fn register(r: Register, program: &Program) -> String {
421 let n = r.number;
422 let sm3 = program.version.major == 3;
424 let indexed = |names: &[&str]| -> String {
425 usize::try_from(n)
426 .ok()
427 .and_then(|i| names.get(i))
428 .map_or_else(|| format!("?{n}"), |s| (*s).to_owned())
429 };
430 match r.kind {
431 RegisterType::Temp => format!("r{n}"),
432 RegisterType::Input => format!("v{n}"),
433 RegisterType::Const => format!("c{n}"),
434 RegisterType::Const2 => format!("c{}", n.saturating_add(2048)),
435 RegisterType::Const3 => format!("c{}", n.saturating_add(4096)),
436 RegisterType::Const4 => format!("c{}", n.saturating_add(6144)),
437 RegisterType::Address if program.version.stage == Stage::Pixel => format!("t{n}"),
438 RegisterType::Address => "a0".into(),
439 RegisterType::RasterizerOut => indexed(&["oPos", "oFog", "oPts"]),
440 RegisterType::AttributeOut => format!("oD{n}"),
441 RegisterType::Output if sm3 => format!("o{n}"),
442 RegisterType::Output => format!("oT{n}"),
443 RegisterType::ConstInt => format!("i{n}"),
444 RegisterType::ColorOut => format!("oC{n}"),
445 RegisterType::DepthOut => "oDepth".into(),
446 RegisterType::Sampler => format!("s{n}"),
447 RegisterType::ConstBool => format!("b{n}"),
448 RegisterType::Loop => "aL".into(),
449 RegisterType::Misc => indexed(&["vPos", "vFace"]),
450 RegisterType::Label => format!("l{n}"),
451 RegisterType::Predicate => format!("p{n}"),
452 RegisterType::Texture => format!("t{n}"),
453 }
454}
455
456fn lane(l: u8) -> char {
457 LANES.get(usize::from(l)).copied().unwrap_or('?')
458}
459
460fn swizzle(s: [u8; 4]) -> String {
462 match s {
463 [0, 1, 2, 3] => String::new(),
464 [a, b, c, d] if a == b && b == c && c == d => format!(".{}", lane(a)),
465 _ => format!(".{}", s.iter().map(|&l| lane(l)).collect::<String>()),
466 }
467}
468
469fn relative(r: Option<Relative>, program: &Program) -> String {
470 r.map_or(String::new(), |r| {
471 format!("[{}{}]", register(r.register, program), swizzle(r.swizzle))
472 })
473}
474
475fn source(s: &Source, program: &Program) -> String {
476 let name = format!(
477 "{}{}",
478 register(s.register, program),
479 relative(s.relative, program)
480 );
481 let body = match s.modifier {
482 Modifier::None | Modifier::Negate | Modifier::Not => name,
483 Modifier::Bias | Modifier::BiasNegate => format!("{name}_bias"),
484 Modifier::Sign | Modifier::SignNegate => format!("{name}_bx2"),
485 Modifier::Complement => format!("1-{name}"),
486 Modifier::X2 | Modifier::X2Negate => format!("{name}_x2"),
487 Modifier::DivideZ => format!("{name}_dz"),
488 Modifier::DivideW => format!("{name}_dw"),
489 Modifier::Abs | Modifier::AbsNegate => format!("{name}_abs"),
490 };
491 let sign = match s.modifier {
492 Modifier::Negate
493 | Modifier::BiasNegate
494 | Modifier::SignNegate
495 | Modifier::X2Negate
496 | Modifier::AbsNegate => "-",
497 Modifier::Not => "!",
498 _ => "",
499 };
500 format!("{sign}{body}{}{}", swizzle(s.swizzle), precision(s.precision))
501}
502
503fn precision(p: Precision) -> &'static str {
505 match p {
506 Precision::Min16 => " {min16f}",
507 Precision::Min2_8 => " {min2_8f}",
508 Precision::Default | Precision::Unnamed => "",
509 }
510}
511
512fn destination(d: &Destination, program: &Program) -> String {
513 let mask = if d.mask == 15 {
514 String::new()
515 } else {
516 let lanes: String = (0..4u8).filter(|n| d.mask & (1 << n) != 0).map(lane).collect();
517 format!(".{lanes}")
518 };
519 format!(
520 "{}{}{mask}{}",
521 register(d.register, program),
522 relative(d.relative, program),
523 precision(d.precision)
524 )
525}
526
527fn suffixes(d: Option<&Destination>) -> String {
529 let Some(d) = d else { return String::new() };
530 let mut s = match d.shift {
531 1 => "_x2",
532 2 => "_x4",
533 3 => "_x8",
534 -1 => "_d2",
535 -2 => "_d4",
536 -3 => "_d8",
537 _ => "",
538 }
539 .to_owned();
540 if d.saturate {
541 s.push_str("_sat");
542 }
543 if d.partial_precision {
544 s.push_str("_pp");
545 }
546 if d.centroid {
547 s.push_str("_centroid");
548 }
549 s
550}
551
552fn mnemonic(i: &Instruction, program: &Program) -> String {
553 let v = program.version;
554 let from14 = v.major > 1 || (v.stage == Stage::Pixel && v.minor == 4);
556 let comparison = i.comparison().map_or("", |c| match c {
557 Comparison::Greater => "_gt",
558 Comparison::Equal => "_eq",
559 Comparison::GreaterEqual => "_ge",
560 Comparison::Less => "_lt",
561 Comparison::NotEqual => "_ne",
562 Comparison::LessEqual => "_le",
563 });
564 match i.opcode {
565 Opcode::Tex if from14 => match i.control {
566 0 => "texld",
567 1 => "texldp",
568 _ => "texldb",
569 }
570 .to_owned(),
571 Opcode::TexCoord if from14 => "texcrd".into(),
572 Opcode::IfC => format!("if{comparison}"),
573 Opcode::BreakC => format!("break{comparison}"),
574 Opcode::SetP => format!("setp{comparison}"),
575 other => other.name().into(),
576 }
577}
578
579fn instruction(i: &Instruction, program: &Program) -> String {
580 if i.opcode == Opcode::BreakP {
581 return breakp(i, program);
582 }
583 let mut args = Vec::new();
584 if let Some(d) = &i.destination {
585 args.push(destination(d, program));
586 }
587 args.extend(i.sources.iter().map(|s| source(s, program)));
588 let predicate = i.predicate.as_ref().map_or(String::new(), |p| {
590 let p = Source {
591 precision: Precision::Default,
592 ..*p
593 };
594 format!("({}) ", source(&p, program))
595 });
596 format!(
597 "{predicate}{}{} {}",
598 mnemonic(i, program),
599 suffixes(i.destination.as_ref()),
600 args.join(", ")
601 )
602}
603
604fn breakp(i: &Instruction, program: &Program) -> String {
610 let Some(p) = i.sources.first() else {
611 return "break".to_owned();
612 };
613 let bits = p.swizzle.iter().enumerate().fold(0u8, |bits, (lane, from)| {
614 let at = u32::try_from(lane).map_or(8, |l| l.saturating_mul(2));
615 bits | (from & 3).checked_shl(at).unwrap_or(0)
616 });
617 let shift = i8::try_from(p.modifier.code()).map_or(0, |m| if m >= 8 { m.wrapping_sub(16) } else { m });
619 let read = Destination {
620 word: p.word,
621 register: p.register,
622 mask: bits & 15,
623 saturate: bits & 16 != 0,
624 partial_precision: bits & 32 != 0,
625 centroid: bits & 64 != 0,
626 shift,
627 relative: None,
628 precision: p.precision,
629 };
630 format!("break{} {}", suffixes(Some(&read)), destination(&read, program))
631}
632
633const USAGES: [&str; 14] = [
635 "position",
636 "blendweight",
637 "blendindices",
638 "normal",
639 "psize",
640 "texcoord",
641 "tangent",
642 "binormal",
643 "tessfactor",
644 "positiont",
645 "color",
646 "fog",
647 "depth",
648 "sample",
649];
650
651fn declaration(d: &crate::Declaration, program: &Program) -> String {
652 let what = match d.declared {
653 Declared::Sampler(TextureType::D2) => "_2d".to_owned(),
654 Declared::Sampler(TextureType::Cube) => "_cube".to_owned(),
655 Declared::Sampler(TextureType::Volume) => "_volume".to_owned(),
656 Declared::Semantic { usage: 0, index: 0 } if program.version.stage == Stage::Pixel => String::new(),
658 Declared::Semantic { usage, index } => {
659 let index = if index == 0 {
660 String::new()
661 } else {
662 index.to_string()
663 };
664 let usage = USAGES.get(usize::from(usage)).copied().unwrap_or("unknown");
665 format!("_{usage}{index}")
666 }
667 };
668 format!(
669 "dcl{what}{} {}",
670 suffixes(Some(&d.destination)),
671 destination(&d.destination, program)
672 )
673}
674
675fn definition(literal: &Literal, register_: &Destination, program: &Program, options: &Options) -> String {
678 let (op, values) = match literal {
679 Literal::Float(f) => (
680 "def",
681 f.iter()
682 .map(|&w| {
683 if options.hex_literals {
684 format!("0x{w:08x}")
685 } else {
686 general(f32::from_bits(w), 9)
687 }
688 })
689 .collect::<Vec<_>>()
690 .join(", "),
691 ),
692 Literal::Integer(i) => (
693 "defi",
694 i.iter().map(ToString::to_string).collect::<Vec<_>>().join(", "),
695 ),
696 Literal::Bool(b) => ("defb", (if *b != 0 { "true" } else { "false" }).to_owned()),
697 };
698 format!(
699 "{op}{} {}, {values}",
700 suffixes(Some(register_)),
701 destination(register_, program)
702 )
703}
704
705fn general(value: f32, digits: usize) -> String {
709 let bits = value.to_bits();
710 let value = f64::from(value);
711 if value == 0.0 {
712 return if value.is_sign_negative() { "-0" } else { "0" }.to_owned();
713 }
714 if !value.is_finite() {
715 let sign = if value.is_sign_negative() { "-" } else { "" };
716 let name = if value.is_infinite() {
717 "INF"
718 } else if bits == 0xffc0_0000 {
719 "IND"
720 } else {
721 "QNAN"
722 };
723 return format!("{sign}1.#{name}");
724 }
725 let scientific = format!("{value:.precision$e}", precision = digits.saturating_sub(1));
726 let (mantissa, exponent) = scientific.split_once('e').unwrap_or((&scientific, "0"));
727 let exponent: i32 = exponent.parse().unwrap_or(0);
728 let trim = |text: &str| -> String {
729 if text.contains('.') {
730 text.trim_end_matches('0').trim_end_matches('.').to_owned()
731 } else {
732 text.to_owned()
733 }
734 };
735 let digits = i32::try_from(digits).unwrap_or(i32::MAX);
736 if !(-4..digits).contains(&exponent) {
737 let sign = if exponent < 0 { '-' } else { '+' };
738 return format!("{}e{sign}{:03}", trim(mantissa), exponent.unsigned_abs());
739 }
740 let decimals = usize::try_from(digits.saturating_sub(1).saturating_sub(exponent)).unwrap_or(0);
741 trim(&format!("{value:.decimals$}"))
742}
743
744fn slots(i: &Instruction, first: bool, program: &Program) -> (u32, u32) {
749 if (i.coissue && !first) || i.opcode == Opcode::Nop {
752 return (0, 0);
753 }
754 let v = program.version;
755 let pixel1 = v.stage == Stage::Pixel && v.major == 1;
756 let vertex1 = v.stage == Stage::Vertex && v.major == 1;
757 let (texture, arithmetic): (u32, u32) = match i.opcode {
758 Opcode::Tex => (1, 0),
759 Opcode::TexCoord
762 | Opcode::TexBem
763 | Opcode::TexBemL
764 | Opcode::TexReg2Ar
765 | Opcode::TexReg2Gb
766 | Opcode::TexReg2Rgb
767 | Opcode::TexM3x2Pad
768 | Opcode::TexM3x2Tex
769 | Opcode::TexM3x2Depth
770 | Opcode::TexM3x3Pad
771 | Opcode::TexM3x3Tex
772 | Opcode::TexM3x3Spec
773 | Opcode::TexM3x3VSpec
774 | Opcode::TexM3x3
775 | Opcode::TexDp3
776 | Opcode::TexDp3Tex => (1, 0),
777 Opcode::TexLdl => (2, 0),
778 Opcode::TexLdd => (1, 2),
779 Opcode::Exp | Opcode::Log if vertex1 => (0, 10),
781 Opcode::Frc if vertex1 => (0, 3),
782 Opcode::SinCos => (0, 8),
783 Opcode::M4x4 | Opcode::M3x4 => (0, 4),
784 Opcode::Nrm | Opcode::Pow | Opcode::M4x3 | Opcode::M3x3 | Opcode::Sgn => (0, 3),
785 Opcode::If
786 | Opcode::IfC
787 | Opcode::BreakC
788 | Opcode::BreakP
789 | Opcode::CallNz
790 | Opcode::Rep
791 | Opcode::Loop => (0, 3),
792 Opcode::EndRep
793 | Opcode::EndLoop
794 | Opcode::Call
795 | Opcode::M3x2
796 | Opcode::Crs
797 | Opcode::Dsx
798 | Opcode::Dsy
799 | Opcode::Dp2Add
800 | Opcode::Bem => (0, 2),
801 Opcode::Label | Opcode::Phase => (0, 0),
802 _ => (0, 1),
803 };
804 if pixel1 {
806 return (texture, arithmetic.min(1));
807 }
808 let predicate = u32::from(i.predicate.is_some());
810 (texture, arithmetic.saturating_add(predicate))
811}
812
813fn footer(out: &mut String, program: &Program) {
815 let (texture, arithmetic) = program
816 .instructions()
817 .enumerate()
818 .fold((0u32, 0u32), |(t, a), (n, i)| {
819 let (ti, ai) = slots(i, n == 0, program);
820 (t.saturating_add(ti), a.saturating_add(ai))
821 });
822 let total = texture.saturating_add(arithmetic);
823 let slots = if total == 1 { "slot" } else { "slots" };
824 let _ = write!(out, "// approximately {total} instruction {slots} used");
825 if texture > 0 {
826 let _ = write!(out, " ({texture} texture, {arithmetic} arithmetic)");
827 }
828 out.push('\n');
829}