1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::ctab::{ConstantInfo, ConstantTable, RegisterSet, TypeClass, ValueType};
4use crate::disasm::{
5 DeclUsage, Instruction, Opcode, RegisterKey, RegisterType, ResultModifier, SamplerTextureType,
6 ShaderKind, SourceModifier, mask_len,
7};
8use crate::disasm::{ShaderModel, parse_shader};
9
10#[derive(Debug, Clone)]
11struct DeclInfo {
12 reg: RegisterKey,
13 semantic: String,
14 sampler_type: Option<SamplerTextureType>,
15}
16
17#[derive(Debug, Clone)]
18struct DefFloat {
19 reg: RegisterKey,
20 values: [f32; 4],
21}
22
23#[derive(Debug, Clone)]
24struct DefInt {
25 reg: RegisterKey,
26 values: [i32; 4],
27}
28
29#[derive(Debug, Clone)]
30struct DefBool {
31 reg: RegisterKey,
32 value: bool,
33}
34
35#[derive(Debug, Clone)]
36struct Context<'a> {
37 shader: &'a ShaderModel,
38 ctab: Option<&'a ConstantTable>,
39 decls: BTreeMap<RegisterKey, DeclInfo>,
40 sampler_decls: BTreeMap<u16, SamplerTextureType>,
41 def_float: BTreeMap<u16, DefFloat>,
42 def_int: BTreeMap<u16, DefInt>,
43 def_bool: BTreeMap<u16, DefBool>,
44 used_inputs: BTreeSet<RegisterKey>,
45 used_outputs: BTreeSet<RegisterKey>,
46 used_temps: BTreeSet<RegisterKey>,
47 used_samplers: BTreeSet<u16>,
48}
49
50pub fn decompile_hlsl(data: &[u8], ctab: Option<&ConstantTable>) -> String {
51 match parse_shader(data) {
52 Ok(shader) => decompile_model(&shader, ctab),
53 Err(_) => String::new(),
54 }
55}
56
57pub fn decompile_model(shader: &ShaderModel, ctab: Option<&ConstantTable>) -> String {
58 let ctx = analyze(shader, ctab);
59 decompile_direct(&ctx)
60}
61
62fn decompile_direct(ctx: &Context<'_>) -> String {
63 let _shader = ctx.shader;
64 let mut out = String::new();
65 emit_uniforms(&mut out, ctx);
66 emit_def_constants(&mut out, ctx);
67 emit_structs(&mut out, ctx);
68 emit_main(&mut out, ctx);
69
70 out
71}
72
73fn can_use_folded_writer(shader: &ShaderModel) -> bool {
74 shader.instructions.iter().all(|inst| {
75 matches!(
76 inst.opcode,
77 Opcode::Comment
78 | Opcode::Dcl
79 | Opcode::Def
80 | Opcode::DefI
81 | Opcode::DefB
82 | Opcode::Nop
83 | Opcode::End
84 | Opcode::Mov
85 | Opcode::MovA
86 | Opcode::Add
87 | Opcode::Sub
88 | Opcode::Mul
89 | Opcode::Mad
90 | Opcode::Rcp
91 | Opcode::Rsq
92 | Opcode::Dp3
93 | Opcode::Dp4
94 | Opcode::Min
95 | Opcode::Max
96 | Opcode::Slt
97 | Opcode::Sge
98 | Opcode::Exp
99 | Opcode::ExpP
100 | Opcode::Log
101 | Opcode::LogP
102 | Opcode::Frc
103 | Opcode::Pow
104 | Opcode::Abs
105 | Opcode::Nrm
106 | Opcode::SinCos
107 | Opcode::Cmp
108 | Opcode::Cnd
109 | Opcode::Crs
110 | Opcode::Dp2Add
111 | Opcode::M4x4
112 | Opcode::M4x3
113 | Opcode::M3x4
114 | Opcode::M3x3
115 | Opcode::M3x2
116 | Opcode::Tex
117 | Opcode::TexLdl
118 | Opcode::TexLdd
119 | Opcode::TexKill
120 )
121 })
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
125struct ComponentKey {
126 reg: RegisterKey,
127 component: usize,
128}
129
130#[derive(Debug, Clone)]
131struct FoldedState {
132 values: BTreeMap<ComponentKey, String>,
133 clip_exprs: Vec<String>,
134}
135
136fn decompile_folded(ctx: &Context<'_>) -> String {
137 let mut state = FoldedState {
138 values: BTreeMap::new(),
139 clip_exprs: Vec::new(),
140 };
141 initialize_folded_state(ctx, &mut state);
142
143 for inst in &ctx.shader.instructions {
144 fold_instruction(ctx, &mut state, inst);
145 }
146
147 let mut out = String::new();
148 emit_uniforms(&mut out, ctx);
149 emit_def_constants(&mut out, ctx);
150 emit_structs(&mut out, ctx);
151
152 let input_name = input_struct_name(ctx.shader.kind);
153 let output_name = output_struct_name(ctx.shader.kind);
154 out.push_str(&format!("{} main({} input) {{\n", output_name, input_name));
155 out.push_str(&format!(" {} output;\n", output_name));
156
157 for clip in &state.clip_exprs {
158 out.push_str(&format!(" clip({});\n", clip));
159 }
160
161 for reg in &ctx.used_outputs {
162 let ty = output_field_type(*reg);
163 let value = folded_vector_expr(ctx, &state, *reg, vector_len_for_type(ty));
164 out.push_str(&format!(
165 " output.{} = {};\n",
166 output_field_name(*reg),
167 value
168 ));
169 }
170
171 out.push_str(" return output;\n");
172 out.push_str("}\n");
173 out
174}
175
176fn initialize_folded_state(ctx: &Context<'_>, state: &mut FoldedState) {
177 for decl in ctx.decls.values() {
178 let len = vector_len_for_type(input_field_type(ctx, decl.reg));
179 for component in 0..len {
180 let key = ComponentKey {
181 reg: decl.reg,
182 component,
183 };
184 state.values.insert(
185 key,
186 format!(
187 "input.{}{}",
188 input_field_name(decl.reg),
189 component_suffix(component)
190 ),
191 );
192 }
193 }
194
195 for reg in &ctx.used_outputs {
196 for component in 0..vector_len_for_type(output_field_type(*reg)) {
197 state.values.insert(
198 ComponentKey {
199 reg: *reg,
200 component,
201 },
202 scalar_zero(output_field_type(*reg)).to_string(),
203 );
204 }
205 }
206}
207
208fn fold_instruction(ctx: &Context<'_>, state: &mut FoldedState, inst: &Instruction) {
209 match inst.opcode {
210 Opcode::Comment | Opcode::Dcl | Opcode::Nop | Opcode::End => {}
211 Opcode::Def | Opcode::DefI | Opcode::DefB => fold_def_instruction(state, inst),
212 Opcode::TexKill => {
213 if let Some(reg) = inst.dest_register() {
214 let mut comps = Vec::new();
215 for component in components_from_mask(inst.dest_write_mask()) {
216 comps.push(component_value(ctx, state, reg, component));
217 }
218 if !comps.is_empty() {
219 state.clip_exprs.push(vector_constructor(&comps));
220 }
221 }
222 }
223 _ if inst.opcode.has_destination() => {
224 let Some(dst_reg) = inst.dest_register() else {
225 return;
226 };
227 let dst_components = components_from_mask(inst.dest_write_mask());
228 let old = state.values.clone();
229 let mut next_values: Vec<(ComponentKey, String)> = Vec::new();
230 for (write_index, dst_component) in dst_components.iter().copied().enumerate() {
231 if let Some(mut expr) =
232 folded_instruction_component(ctx, &old, inst, write_index, dst_component)
233 {
234 expr = apply_result_modifier(expr, inst.dest_modifier());
235 next_values.push((
236 ComponentKey {
237 reg: dst_reg,
238 component: dst_component,
239 },
240 expr,
241 ));
242 }
243 }
244 for (key, expr) in next_values {
245 state.values.insert(key, expr);
246 }
247 }
248 _ => {}
249 }
250}
251
252fn fold_def_instruction(state: &mut FoldedState, inst: &Instruction) {
253 let Some(reg) = inst.dest_register() else {
254 return;
255 };
256 match inst.opcode {
257 Opcode::Def => {
258 for component in 0..4 {
259 state.values.insert(
260 ComponentKey { reg, component },
261 fmt_f32(inst.get_float_param(component + 1)),
262 );
263 }
264 }
265 Opcode::DefI => {
266 for component in 0..4 {
267 state.values.insert(
268 ComponentKey { reg, component },
269 inst.get_int_param(component + 1).to_string(),
270 );
271 }
272 }
273 Opcode::DefB => {
274 state.values.insert(
275 ComponentKey { reg, component: 0 },
276 if inst.get_int_param(1) != 0 {
277 "true".to_string()
278 } else {
279 "false".to_string()
280 },
281 );
282 }
283 _ => {}
284 }
285}
286
287fn folded_instruction_component(
288 ctx: &Context<'_>,
289 old: &BTreeMap<ComponentKey, String>,
290 inst: &Instruction,
291 write_index: usize,
292 dst_component: usize,
293) -> Option<String> {
294 let c = dst_component;
295 let s =
296 |param: usize, component: usize| source_component_expr(ctx, old, inst, param, component);
297 Some(match inst.opcode {
298 Opcode::Mov | Opcode::MovA => s(1, c),
299 Opcode::Add => format!("({} + {})", s(1, c), s(2, c)),
300 Opcode::Sub => format!("({} - {})", s(1, c), s(2, c)),
301 Opcode::Mul => format!("({} * {})", s(1, c), s(2, c)),
302 Opcode::Mad => format!("({} * {} + {})", s(1, c), s(2, c), s(3, c)),
303 Opcode::Rcp => format!("(1.0 / {})", s(1, c)),
304 Opcode::Rsq => format!("rsqrt({})", s(1, c)),
305 Opcode::Dp3 => {
306 dot_expr_from_components(|component| s(1, component), |component| s(2, component), 3)
307 }
308 Opcode::Dp4 => {
309 dot_expr_from_components(|component| s(1, component), |component| s(2, component), 4)
310 }
311 Opcode::Min => format!("min({}, {})", s(1, c), s(2, c)),
312 Opcode::Max => format!("max({}, {})", s(1, c), s(2, c)),
313 Opcode::Slt => format!("({} < {} ? 1.0 : 0.0)", s(1, c), s(2, c)),
314 Opcode::Sge => format!("({} >= {} ? 1.0 : 0.0)", s(1, c), s(2, c)),
315 Opcode::Exp | Opcode::ExpP => format!("exp2({})", s(1, c)),
316 Opcode::Log | Opcode::LogP => format!("log2({})", s(1, c)),
317 Opcode::Frc => format!("frac({})", s(1, c)),
318 Opcode::Pow => format!("pow({}, {})", s(1, c), s(2, c)),
319 Opcode::Abs => format!("abs({})", s(1, c)),
320 Opcode::Nrm => normalize_component_expr(|component| s(1, component), c),
321 Opcode::SinCos => {
322 if write_index == 0 {
323 format!("cos({})", s(1, c))
324 } else {
325 format!("sin({})", s(1, c))
326 }
327 }
328 Opcode::Cmp => format!("({} >= 0 ? {} : {})", s(1, c), s(2, c), s(3, c)),
329 Opcode::Cnd => format!("({} > 0.5 ? {} : {})", s(1, c), s(2, c), s(3, c)),
330 Opcode::Crs => {
331 cross_component_expr(|component| s(1, component), |component| s(2, component), c)
332 }
333 Opcode::Dp2Add => format!(
334 "(({} * {}) + ({} * {}) + {})",
335 s(1, 0),
336 s(2, 0),
337 s(1, 1),
338 s(2, 1),
339 s(3, 0)
340 ),
341 Opcode::M4x4 => matrix_component_expr(ctx, inst, old, 4, c),
342 Opcode::M4x3 => matrix_component_expr(ctx, inst, old, 4, c),
343 Opcode::M3x4 => matrix_component_expr(ctx, inst, old, 3, c),
344 Opcode::M3x3 => matrix_component_expr(ctx, inst, old, 3, c),
345 Opcode::M3x2 => matrix_component_expr(ctx, inst, old, 3, c),
346 Opcode::Tex | Opcode::TexLdl | Opcode::TexLdd => texture_component_expr(ctx, old, inst, c),
347 _ => return None,
348 })
349}
350
351fn source_component_expr(
352 ctx: &Context<'_>,
353 old: &BTreeMap<ComponentKey, String>,
354 inst: &Instruction,
355 param_index: usize,
356 component_index: usize,
357) -> String {
358 let Some(reg) = inst.source_register(param_index) else {
359 return "0.0".to_string();
360 };
361 let mut component = component_index.min(3);
362 if reg.ty != RegisterType::MiscType || reg.number != 1 {
363 component = inst.source_swizzle(param_index)[component];
364 }
365 let expr = component_value_from(old, ctx, reg, component);
366 apply_source_modifier(expr, inst.source_modifier(param_index))
367}
368
369fn component_value(
370 ctx: &Context<'_>,
371 state: &FoldedState,
372 reg: RegisterKey,
373 component: usize,
374) -> String {
375 component_value_from(&state.values, ctx, reg, component)
376}
377
378fn component_value_from(
379 values: &BTreeMap<ComponentKey, String>,
380 ctx: &Context<'_>,
381 reg: RegisterKey,
382 component: usize,
383) -> String {
384 if let Some(v) = values.get(&ComponentKey { reg, component }) {
385 return v.clone();
386 }
387
388 match reg.ty {
389 RegisterType::Input | RegisterType::Texture | RegisterType::MiscType => format!(
390 "input.{}{}",
391 input_field_name(reg),
392 component_suffix(component)
393 ),
394 RegisterType::Const => const_component_expr(ctx, reg.number, component),
395 RegisterType::ConstInt => format!(
396 "{}{}",
397 int_const_expr(ctx, reg.number),
398 component_suffix(component)
399 ),
400 RegisterType::ConstBool => bool_const_expr(ctx, reg.number),
401 RegisterType::Sampler => sampler_name(ctx, reg.number),
402 RegisterType::ColorOut
403 | RegisterType::DepthOut
404 | RegisterType::RastOut
405 | RegisterType::AttrOut
406 | RegisterType::Output => {
407 format!(
408 "output.{}{}",
409 output_field_name(reg),
410 component_suffix(component)
411 )
412 }
413 _ => format!(
414 "{}{}",
415 temp_name(reg, ctx.shader.kind),
416 component_suffix(component)
417 ),
418 }
419}
420
421fn const_component_expr(ctx: &Context<'_>, index: u16, component: usize) -> String {
422 let base = const_row_expr(ctx, index);
423 format!("{}{}", base, component_suffix(component))
424}
425
426fn folded_vector_expr(
427 ctx: &Context<'_>,
428 state: &FoldedState,
429 reg: RegisterKey,
430 len: usize,
431) -> String {
432 let mut comps = Vec::new();
433 for component in 0..len {
434 comps.push(component_value(ctx, state, reg, component));
435 }
436 vector_constructor(&comps)
437}
438
439fn vector_constructor(comps: &[String]) -> String {
440 match comps.len() {
441 0 => "0.0".to_string(),
442 1 => comps[0].clone(),
443 2 => format!("float2({}, {})", comps[0], comps[1]),
444 3 => format!("float3({}, {}, {})", comps[0], comps[1], comps[2]),
445 _ => format!(
446 "float4({}, {}, {}, {})",
447 comps[0], comps[1], comps[2], comps[3]
448 ),
449 }
450}
451
452fn dot_expr_from_components<F, G>(mut a: F, mut b: G, len: usize) -> String
453where
454 F: FnMut(usize) -> String,
455 G: FnMut(usize) -> String,
456{
457 let mut parts = Vec::new();
458 for component in 0..len {
459 parts.push(format!("({} * {})", a(component), b(component)));
460 }
461 format!("({})", parts.join(" + "))
462}
463
464fn normalize_component_expr<F>(mut value: F, component: usize) -> String
465where
466 F: FnMut(usize) -> String,
467{
468 let x = value(0);
469 let y = value(1);
470 let z = value(2);
471 let denom = format!("sqrt(({} * {}) + ({} * {}) + ({} * {}))", x, x, y, y, z, z);
472 format!("({} / {})", value(component.min(2)), denom)
473}
474
475fn cross_component_expr<F, G>(mut a: F, mut b: G, component: usize) -> String
476where
477 F: FnMut(usize) -> String,
478 G: FnMut(usize) -> String,
479{
480 match component {
481 0 => format!("(({}) * ({}) - ({}) * ({}))", a(1), b(2), a(2), b(1)),
482 1 => format!("(({}) * ({}) - ({}) * ({}))", a(2), b(0), a(0), b(2)),
483 _ => format!("(({}) * ({}) - ({}) * ({}))", a(0), b(1), a(1), b(0)),
484 }
485}
486
487fn matrix_component_expr(
488 ctx: &Context<'_>,
489 inst: &Instruction,
490 old: &BTreeMap<ComponentKey, String>,
491 vec_len: usize,
492 component: usize,
493) -> String {
494 let terms = (0..vec_len)
495 .map(|i| {
496 let v = source_component_expr(ctx, old, inst, 1, i);
497 let m = if let Some(reg) = inst.source_register(2) {
498 if reg.ty == RegisterType::Const {
499 const_component_expr(ctx, reg.number + component as u16, i)
500 } else {
501 source_component_expr(ctx, old, inst, 2, i)
502 }
503 } else {
504 "0.0".to_string()
505 };
506 format!("({} * {})", v, m)
507 })
508 .collect::<Vec<_>>();
509 format!("({})", terms.join(" + "))
510}
511
512fn texture_component_expr(
513 ctx: &Context<'_>,
514 old: &BTreeMap<ComponentKey, String>,
515 inst: &Instruction,
516 component: usize,
517) -> String {
518 let sampler = inst.source_register(2).map(|r| r.number).unwrap_or(0);
519 let sampler_name = sampler_name(ctx, sampler);
520 let sampler_ty = sampler_texture_type(ctx, sampler);
521 let dim = sampler_ty.hlsl_dim();
522 let coord_components = if inst.opcode == Opcode::Tex { dim } else { 4 };
523 let coord = (0..coord_components)
524 .map(|i| source_component_expr(ctx, old, inst, 1, i))
525 .collect::<Vec<_>>();
526 let coord_expr = vector_constructor(&coord);
527 let base = texture_intrinsic(inst, sampler_ty);
528 let sample = match inst.opcode {
529 Opcode::TexLdd => {
530 let ddx = (0..dim)
531 .map(|i| source_component_expr(ctx, old, inst, 3, i))
532 .collect::<Vec<_>>();
533 let ddy = (0..dim)
534 .map(|i| source_component_expr(ctx, old, inst, 4, i))
535 .collect::<Vec<_>>();
536 format!(
537 "{}({}, {}, {}, {})",
538 base,
539 sampler_name,
540 coord_expr,
541 vector_constructor(&ddx),
542 vector_constructor(&ddy)
543 )
544 }
545 _ => format!("{}({}, {})", base, sampler_name, coord_expr),
546 };
547 format!("{}{}", sample, component_suffix(component))
548}
549
550fn components_from_mask(mask: u8) -> Vec<usize> {
551 let m = if mask == 0 { 0xf } else { mask };
552 let mut out = Vec::new();
553 for component in 0..4 {
554 if (m & (1 << component)) != 0 {
555 out.push(component);
556 }
557 }
558 out
559}
560
561fn vector_len_for_type(ty: &str) -> usize {
562 match ty {
563 "float" | "bool" => 1,
564 "float2" | "int2" | "bool2" => 2,
565 "float3" | "int3" | "bool3" => 3,
566 _ => 4,
567 }
568}
569
570fn scalar_zero(ty: &str) -> &'static str {
571 match ty {
572 "bool" | "bool4" => "false",
573 _ => "0.0",
574 }
575}
576
577fn component_suffix(component: usize) -> &'static str {
578 match component {
579 0 => ".x",
580 1 => ".y",
581 2 => ".z",
582 _ => ".w",
583 }
584}
585
586fn analyze<'a>(shader: &'a ShaderModel, ctab: Option<&'a ConstantTable>) -> Context<'a> {
587 let mut ctx = Context {
588 shader,
589 ctab,
590 decls: BTreeMap::new(),
591 sampler_decls: BTreeMap::new(),
592 def_float: BTreeMap::new(),
593 def_int: BTreeMap::new(),
594 def_bool: BTreeMap::new(),
595 used_inputs: BTreeSet::new(),
596 used_outputs: BTreeSet::new(),
597 used_temps: BTreeSet::new(),
598 used_samplers: BTreeSet::new(),
599 };
600
601 for inst in &shader.instructions {
602 match inst.opcode {
603 Opcode::Dcl => {
604 if let Some(reg) = inst.dest_register() {
605 if reg.ty == RegisterType::Sampler {
606 let sampler_ty = inst.decl_sampler_type();
607 ctx.sampler_decls.insert(reg.number, sampler_ty);
608 ctx.used_samplers.insert(reg.number);
609 } else {
610 let semantic = semantic_from_decl(shader.kind, inst);
611 ctx.decls.insert(
612 reg,
613 DeclInfo {
614 reg,
615 semantic,
616 sampler_type: None,
617 },
618 );
619 }
620 }
621 }
622 Opcode::Def => {
623 if let Some(reg) = inst.dest_register()
624 && reg.ty == RegisterType::Const
625 {
626 ctx.def_float.insert(
627 reg.number,
628 DefFloat {
629 reg,
630 values: [
631 inst.get_float_param(1),
632 inst.get_float_param(2),
633 inst.get_float_param(3),
634 inst.get_float_param(4),
635 ],
636 },
637 );
638 }
639 }
640 Opcode::DefI => {
641 if let Some(reg) = inst.dest_register()
642 && reg.ty == RegisterType::ConstInt
643 {
644 ctx.def_int.insert(
645 reg.number,
646 DefInt {
647 reg,
648 values: [
649 inst.get_int_param(1),
650 inst.get_int_param(2),
651 inst.get_int_param(3),
652 inst.get_int_param(4),
653 ],
654 },
655 );
656 }
657 }
658 Opcode::DefB => {
659 if let Some(reg) = inst.dest_register()
660 && reg.ty == RegisterType::ConstBool
661 {
662 ctx.def_bool.insert(
663 reg.number,
664 DefBool {
665 reg,
666 value: inst.get_int_param(1) != 0,
667 },
668 );
669 }
670 }
671 _ => {}
672 }
673
674 if inst.opcode == Opcode::End || inst.opcode == Opcode::Comment {
675 continue;
676 }
677
678 if inst.opcode == Opcode::TexKill {
679 if let Some(reg) = inst.dest_register() {
680 classify_source_register(&mut ctx, reg);
681 }
682 continue;
683 }
684
685 if inst.opcode.has_destination()
686 && let Some(reg) = inst.dest_register()
687 {
688 classify_dest_register(&mut ctx, reg);
689 }
690
691 let first_src = if inst.opcode.has_destination() { 1 } else { 0 };
692 for pi in first_src..inst.params.len() {
693 if inst.opcode == Opcode::Dcl
694 || inst.opcode == Opcode::Def
695 || inst.opcode == Opcode::DefI
696 || inst.opcode == Opcode::DefB
697 {
698 continue;
699 }
700 if let Some(reg) = inst.source_register(pi) {
701 classify_source_register(&mut ctx, reg);
702 }
703 }
704 }
705
706 infer_missing_decls(&mut ctx);
707 ctx
708}
709
710fn classify_dest_register(ctx: &mut Context<'_>, reg: RegisterKey) {
711 match reg.ty {
712 RegisterType::Temp
713 | RegisterType::TempFloat16
714 | RegisterType::Texture
715 | RegisterType::Predicate => {
716 if !(ctx.shader.kind == ShaderKind::Pixel && reg.ty == RegisterType::Texture) {
717 ctx.used_temps.insert(reg);
718 }
719 }
720 RegisterType::RastOut
721 | RegisterType::AttrOut
722 | RegisterType::Output
723 | RegisterType::ColorOut
724 | RegisterType::DepthOut => {
725 ctx.used_outputs.insert(reg);
726 }
727 _ => {}
728 }
729}
730
731fn classify_source_register(ctx: &mut Context<'_>, reg: RegisterKey) {
732 match reg.ty {
733 RegisterType::Input | RegisterType::MiscType => {
734 ctx.used_inputs.insert(reg);
735 }
736 RegisterType::Texture => {
737 if ctx.shader.kind == ShaderKind::Pixel {
738 ctx.used_inputs.insert(reg);
739 } else {
740 ctx.used_temps.insert(reg);
741 }
742 }
743 RegisterType::Temp | RegisterType::TempFloat16 | RegisterType::Predicate => {
744 ctx.used_temps.insert(reg);
745 }
746 RegisterType::Sampler => {
747 ctx.used_samplers.insert(reg.number);
748 }
749 RegisterType::RastOut
750 | RegisterType::AttrOut
751 | RegisterType::Output
752 | RegisterType::ColorOut
753 | RegisterType::DepthOut => {
754 ctx.used_outputs.insert(reg);
755 }
756 _ => {}
757 }
758}
759
760fn infer_missing_decls(ctx: &mut Context<'_>) {
761 for reg in ctx.used_inputs.clone() {
762 if ctx.decls.contains_key(®) {
763 continue;
764 }
765 let semantic = inferred_input_semantic(ctx.shader.kind, reg);
766 ctx.decls.insert(
767 reg,
768 DeclInfo {
769 reg,
770 semantic,
771 sampler_type: None,
772 },
773 );
774 }
775 for sampler in ctx.used_samplers.clone() {
776 ctx.sampler_decls
777 .entry(sampler)
778 .or_insert(SamplerTextureType::TwoD);
779 }
780 if ctx.shader.kind == ShaderKind::Pixel && ctx.used_outputs.is_empty() {
781 ctx.used_outputs.insert(RegisterKey {
782 ty: RegisterType::ColorOut,
783 number: 0,
784 });
785 }
786}
787
788fn semantic_from_decl(kind: ShaderKind, inst: &Instruction) -> String {
789 if let Some(reg) = inst.dest_register()
790 && reg.ty == RegisterType::MiscType
791 {
792 return match reg.number {
793 0 => "VPOS".to_string(),
794 1 => "VFACE".to_string(),
795 _ => format!("TEXCOORD{}", reg.number),
796 };
797 }
798 if let Some(reg) = inst.dest_register()
799 && kind == ShaderKind::Pixel
800 {
801 match reg.ty {
802 RegisterType::Texture => return format!("TEXCOORD{}", reg.number),
803 RegisterType::Input => return format!("COLOR{}", reg.number),
804 _ => {}
805 }
806 }
807 let usage = inst.decl_usage();
808 let index = inst.decl_index();
809 let prefix = usage.semantic_prefix();
810 if index == 0
811 && !matches!(
812 usage,
813 DeclUsage::TexCoord | DeclUsage::Color | DeclUsage::Position
814 )
815 {
816 prefix.to_string()
817 } else {
818 format!("{}{}", prefix, index)
819 }
820}
821
822fn inferred_input_semantic(kind: ShaderKind, reg: RegisterKey) -> String {
823 match (kind, reg.ty) {
824 (ShaderKind::Pixel, RegisterType::Texture) => format!("TEXCOORD{}", reg.number),
825 (ShaderKind::Pixel, RegisterType::Input) => format!("COLOR{}", reg.number),
826 (_, RegisterType::Input) => match reg.number {
827 0 => "POSITION0".to_string(),
828 1 => "NORMAL0".to_string(),
829 n => format!("TEXCOORD{}", n.saturating_sub(2)),
830 },
831 (_, RegisterType::MiscType) => match reg.number {
832 0 => "VPOS".to_string(),
833 1 => "VFACE".to_string(),
834 _ => format!("TEXCOORD{}", reg.number),
835 },
836 _ => format!("TEXCOORD{}", reg.number),
837 }
838}
839
840fn output_semantic(ctx: &Context<'_>, reg: RegisterKey) -> String {
841 if let Some(decl) = ctx.decls.get(®) {
842 return decl.semantic.clone();
843 }
844 match reg.ty {
845 RegisterType::ColorOut => format!("COLOR{}", reg.number),
846 RegisterType::DepthOut => "DEPTH".to_string(),
847 RegisterType::RastOut => match reg.number {
848 0 => "POSITION0".to_string(),
849 1 => "FOG".to_string(),
850 2 => "PSIZE".to_string(),
851 n => format!("TEXCOORD{}", n),
852 },
853 RegisterType::AttrOut => format!("COLOR{}", reg.number),
854 RegisterType::Output => format!("TEXCOORD{}", reg.number),
855 _ => format!("TEXCOORD{}", reg.number),
856 }
857}
858
859fn emit_uniforms(out: &mut String, ctx: &Context<'_>) {
860 if let Some(ctab) = ctx.ctab {
861 for c in &ctab.constants {
862 let ty = c.hlsl_decl_type();
863 out.push_str(&format!("uniform {} {};\n", ty, sanitize_ident(&c.name)));
864 }
865 }
866
867 let mut emitted_sampler_fallback = false;
868 for sampler in &ctx.used_samplers {
869 if sampler_constant(ctx, *sampler).is_some() {
870 continue;
871 }
872 let ty = sampler_type_name(
873 ctx.sampler_decls
874 .get(sampler)
875 .copied()
876 .unwrap_or(SamplerTextureType::TwoD),
877 );
878 out.push_str(&format!("uniform {} s{};\n", ty, sampler));
879 emitted_sampler_fallback = true;
880 }
881 if ctx.ctab.is_some() || emitted_sampler_fallback {
882 out.push('\n');
883 }
884}
885
886fn emit_def_constants(out: &mut String, ctx: &Context<'_>) {
887 let mut any = false;
888 for (idx, def) in &ctx.def_float {
889 if float_constant(ctx, *idx).is_some() {
890 continue;
891 }
892 out.push_str(&format!(
893 "static const float4 c{} = float4({}, {}, {}, {});\n",
894 idx,
895 fmt_f32(def.values[0]),
896 fmt_f32(def.values[1]),
897 fmt_f32(def.values[2]),
898 fmt_f32(def.values[3])
899 ));
900 any = true;
901 }
902 for (idx, def) in &ctx.def_int {
903 out.push_str(&format!(
904 "static const int4 i{} = int4({}, {}, {}, {});\n",
905 idx, def.values[0], def.values[1], def.values[2], def.values[3]
906 ));
907 any = true;
908 }
909 for (idx, def) in &ctx.def_bool {
910 out.push_str(&format!(
911 "static const bool b{} = {};\n",
912 idx,
913 if def.value { "true" } else { "false" }
914 ));
915 any = true;
916 }
917 if any {
918 out.push('\n');
919 }
920}
921
922fn emit_structs(out: &mut String, ctx: &Context<'_>) {
923 let input_name = input_struct_name(ctx.shader.kind);
924 let output_name = output_struct_name(ctx.shader.kind);
925
926 out.push_str(&format!("struct {} {{\n", input_name));
927 for decl in ctx.decls.values() {
928 let field = input_field_name(decl.reg);
929 let ty = input_field_type(ctx, decl.reg);
930 out.push_str(&format!(" {} {} : {};\n", ty, field, decl.semantic));
931 }
932 out.push_str("};\n\n");
933
934 out.push_str(&format!("struct {} {{\n", output_name));
935 for reg in &ctx.used_outputs {
936 let field = output_field_name(*reg);
937 let sem = output_semantic(ctx, *reg);
938 let ty = output_field_type(*reg);
939 out.push_str(&format!(" {} {} : {};\n", ty, field, sem));
940 }
941 out.push_str("};\n\n");
942}
943
944fn emit_main(out: &mut String, ctx: &Context<'_>) {
945 let input_name = input_struct_name(ctx.shader.kind);
946 let output_name = output_struct_name(ctx.shader.kind);
947 out.push_str(&format!("{} main({} input) {{\n", output_name, input_name));
948 out.push_str(&format!(" {} output;\n", output_name));
949
950 for reg in &ctx.used_outputs {
951 out.push_str(&format!(
952 " output.{} = {};\n",
953 output_field_name(*reg),
954 zero_value(output_field_type(*reg))
955 ));
956 }
957 for reg in &ctx.used_temps {
958 out.push_str(&format!(
959 " {} {} = {};\n",
960 temp_type(*reg),
961 temp_name(*reg, ctx.shader.kind),
962 zero_value(temp_type(*reg))
963 ));
964 }
965 if !ctx.used_outputs.is_empty() || !ctx.used_temps.is_empty() {
966 out.push('\n');
967 }
968
969 let mut indent = 1usize;
970 for inst in &ctx.shader.instructions {
971 emit_instruction(out, ctx, inst, &mut indent);
972 }
973
974 out.push_str(" return output;\n");
975 out.push_str("}\n");
976}
977
978fn emit_instruction(out: &mut String, ctx: &Context<'_>, inst: &Instruction, indent: &mut usize) {
979 match inst.opcode {
980 Opcode::Comment
981 | Opcode::Dcl
982 | Opcode::Def
983 | Opcode::DefI
984 | Opcode::DefB
985 | Opcode::Nop
986 | Opcode::End => {}
987 Opcode::TexKill => {
988 if let Some(reg) = inst.dest_register() {
989 let expr = format!(
990 "{}{}",
991 register_base(ctx, reg),
992 hlsl_mask_suffix(inst.dest_write_mask())
993 );
994 line(out, *indent, &format!("clip({});", expr));
995 }
996 }
997 Opcode::If => {
998 let cond = source_expr(ctx, inst, 0, 1);
999 line(out, *indent, &format!("if ({}) {{", cond));
1000 *indent += 1;
1001 }
1002 Opcode::IfC => {
1003 let a = source_expr(ctx, inst, 0, 4);
1004 let b = source_expr(ctx, inst, 1, 4);
1005 line(
1006 out,
1007 *indent,
1008 &format!("if (all({} {} {})) {{", a, cmp_op(inst.comparison()), b),
1009 );
1010 *indent += 1;
1011 }
1012 Opcode::Else => {
1013 if *indent > 0 {
1014 *indent -= 1;
1015 }
1016 line(out, *indent, "} else {");
1017 *indent += 1;
1018 }
1019 Opcode::EndIf => {
1020 if *indent > 0 {
1021 *indent -= 1;
1022 }
1023 line(out, *indent, "}");
1024 }
1025 Opcode::Break => line(out, *indent, "break;"),
1026 Opcode::BreakC => {
1027 let a = source_expr(ctx, inst, 0, 4);
1028 let b = source_expr(ctx, inst, 1, 4);
1029 line(
1030 out,
1031 *indent,
1032 &format!("if (all({} {} {})) break;", a, cmp_op(inst.comparison()), b),
1033 );
1034 }
1035 Opcode::Rep => {
1036 let n = source_expr(ctx, inst, 0, 1);
1037 line(
1038 out,
1039 *indent,
1040 &format!(
1041 "for (int _rep{} = 0; _rep{} < (int)({}); ++_rep{}) {{",
1042 inst.offset, inst.offset, n, inst.offset
1043 ),
1044 );
1045 *indent += 1;
1046 }
1047 Opcode::Loop => {
1048 line(
1049 out,
1050 *indent,
1051 &format!(
1052 "for (int _loop{} = 0; ; ++_loop{}) {{",
1053 inst.offset, inst.offset
1054 ),
1055 );
1056 *indent += 1;
1057 }
1058 Opcode::EndRep | Opcode::EndLoop => {
1059 if *indent > 0 {
1060 *indent -= 1;
1061 }
1062 line(out, *indent, "}");
1063 }
1064 Opcode::Ret => line(out, *indent, "return output;"),
1065 _ if inst.opcode.has_destination() => {
1066 if let Some((dst, rhs)) = assignment_expr(ctx, inst) {
1067 line(out, *indent, &format!("{} = {};", dst, rhs));
1068 }
1069 }
1070 _ => {}
1071 }
1072}
1073
1074fn assignment_expr(ctx: &Context<'_>, inst: &Instruction) -> Option<(String, String)> {
1075 let reg = inst.dest_register()?;
1076 let mask = inst.dest_write_mask();
1077 let dst = format!("{}{}", register_base(ctx, reg), hlsl_mask_suffix(mask));
1078 let n = mask_len(mask);
1079 let mut rhs = match inst.opcode {
1080 Opcode::Mov | Opcode::MovA => source_expr(ctx, inst, 1, n),
1081 Opcode::Add => bin(ctx, inst, n, "+"),
1082 Opcode::Sub => bin(ctx, inst, n, "-"),
1083 Opcode::Mul => bin(ctx, inst, n, "*"),
1084 Opcode::Mad => format!(
1085 "({} * {} + {})",
1086 source_expr(ctx, inst, 1, n),
1087 source_expr(ctx, inst, 2, n),
1088 source_expr(ctx, inst, 3, n)
1089 ),
1090 Opcode::Rcp => format!("(1.0 / {})", source_expr(ctx, inst, 1, 1)),
1091 Opcode::Rsq => format!("rsqrt({})", source_expr(ctx, inst, 1, 1)),
1092 Opcode::Dp3 => format!(
1093 "dot({}, {})",
1094 source_expr(ctx, inst, 1, 3),
1095 source_expr(ctx, inst, 2, 3)
1096 ),
1097 Opcode::Dp4 => format!(
1098 "dot({}, {})",
1099 source_expr(ctx, inst, 1, 4),
1100 source_expr(ctx, inst, 2, 4)
1101 ),
1102 Opcode::Min => format!(
1103 "min({}, {})",
1104 source_expr(ctx, inst, 1, n),
1105 source_expr(ctx, inst, 2, n)
1106 ),
1107 Opcode::Max => format!(
1108 "max({}, {})",
1109 source_expr(ctx, inst, 1, n),
1110 source_expr(ctx, inst, 2, n)
1111 ),
1112 Opcode::Slt => format!(
1113 "(1.0 - step({}, {}))",
1114 source_expr(ctx, inst, 2, n),
1115 source_expr(ctx, inst, 1, n)
1116 ),
1117 Opcode::Sge => format!(
1118 "step({}, {})",
1119 source_expr(ctx, inst, 2, n),
1120 source_expr(ctx, inst, 1, n)
1121 ),
1122 Opcode::Exp | Opcode::ExpP => format!("exp2({})", source_expr(ctx, inst, 1, 1)),
1123 Opcode::Log | Opcode::LogP => format!("log2({})", source_expr(ctx, inst, 1, 1)),
1124 Opcode::Lit => format!("lit({})", source_expr(ctx, inst, 1, 4)),
1125 Opcode::Dst => format!(
1126 "dst({}, {})",
1127 source_expr(ctx, inst, 1, 4),
1128 source_expr(ctx, inst, 2, 4)
1129 ),
1130 Opcode::Lrp => format!(
1131 "lerp({}, {}, {})",
1132 source_expr(ctx, inst, 3, n),
1133 source_expr(ctx, inst, 2, n),
1134 source_expr(ctx, inst, 1, n)
1135 ),
1136 Opcode::Frc => format!("frac({})", source_expr(ctx, inst, 1, n)),
1137 Opcode::Pow => format!(
1138 "pow({}, {})",
1139 source_expr(ctx, inst, 1, 1),
1140 source_expr(ctx, inst, 2, 1)
1141 ),
1142 Opcode::Abs => format!("abs({})", source_expr(ctx, inst, 1, n)),
1143 Opcode::Nrm => format!("normalize({})", source_expr(ctx, inst, 1, 3)),
1144 Opcode::SinCos => {
1145 if mask & 1 != 0 && mask_len(mask) == 1 {
1146 format!("cos({})", source_expr(ctx, inst, 1, 1))
1147 } else if mask & 2 != 0 && mask_len(mask) == 1 {
1148 format!("sin({})", source_expr(ctx, inst, 1, 1))
1149 } else {
1150 format!("float2(cos({0}), sin({0}))", source_expr(ctx, inst, 1, 1))
1151 }
1152 }
1153 Opcode::Cmp => format!(
1154 "({} >= 0 ? {} : {})",
1155 source_expr(ctx, inst, 1, n),
1156 source_expr(ctx, inst, 3, n),
1157 source_expr(ctx, inst, 2, n)
1158 ),
1159 Opcode::Cnd => format!(
1160 "({} > 0.5 ? {} : {})",
1161 source_expr(ctx, inst, 1, n),
1162 source_expr(ctx, inst, 2, n),
1163 source_expr(ctx, inst, 3, n)
1164 ),
1165 Opcode::Crs => format!(
1166 "cross({}, {})",
1167 source_expr(ctx, inst, 1, 3),
1168 source_expr(ctx, inst, 2, 3)
1169 ),
1170 Opcode::Dp2Add => format!(
1171 "(dot({}, {}) + {})",
1172 source_expr(ctx, inst, 1, 2),
1173 source_expr(ctx, inst, 2, 2),
1174 source_expr(ctx, inst, 3, 1)
1175 ),
1176 Opcode::M4x4 => matrix_mul_expr(ctx, inst, 4, 4),
1177 Opcode::M4x3 => matrix_mul_expr(ctx, inst, 4, 3),
1178 Opcode::M3x4 => matrix_mul_expr(ctx, inst, 3, 4),
1179 Opcode::M3x3 => matrix_mul_expr(ctx, inst, 3, 3),
1180 Opcode::M3x2 => matrix_mul_expr(ctx, inst, 3, 2),
1181 Opcode::Tex | Opcode::TexLdl | Opcode::TexLdd => texture_expr(ctx, inst),
1182 Opcode::TexCoord => source_expr(ctx, inst, 1, n),
1183 Opcode::Dsx => format!("ddx({})", source_expr(ctx, inst, 1, n)),
1184 Opcode::Dsy => format!("ddy({})", source_expr(ctx, inst, 1, n)),
1185 Opcode::Sgn => format!("sign({})", source_expr(ctx, inst, 1, n)),
1186 _ => return None,
1187 };
1188 rhs = apply_result_modifier(rhs, inst.dest_modifier());
1189 Some((dst, rhs))
1190}
1191
1192fn bin(ctx: &Context<'_>, inst: &Instruction, n: usize, op: &str) -> String {
1193 format!(
1194 "({} {} {})",
1195 source_expr(ctx, inst, 1, n),
1196 op,
1197 source_expr(ctx, inst, 2, n)
1198 )
1199}
1200
1201fn matrix_mul_expr(
1202 ctx: &Context<'_>,
1203 inst: &Instruction,
1204 vec_len: usize,
1205 out_len: usize,
1206) -> String {
1207 let v = source_expr(ctx, inst, 1, vec_len);
1208 if let Some(reg) = inst.source_register(2)
1209 && reg.ty == RegisterType::Const
1210 {
1211 if let Some(name) = matrix_constant_name(ctx, reg.number, out_len) {
1212 return format!("mul({}, {})", v, name);
1213 }
1214 let rows = (0..out_len)
1215 .map(|i| const_row_expr(ctx, reg.number + i as u16))
1216 .collect::<Vec<_>>()
1217 .join(", ");
1218 let ty = format!("float{}x{}", out_len, vec_len);
1219 return format!("mul({}, {}({}))", v, ty, rows);
1220 }
1221 format!("mul({}, {})", v, source_expr(ctx, inst, 2, vec_len))
1222}
1223
1224fn texture_expr(ctx: &Context<'_>, inst: &Instruction) -> String {
1225 let sampler = inst.source_register(2).map(|r| r.number).unwrap_or(0);
1226 let sampler_name = sampler_name(ctx, sampler);
1227 let sampler_ty = sampler_texture_type(ctx, sampler);
1228 let dim = sampler_ty.hlsl_dim();
1229 let coord = source_expr(
1230 ctx,
1231 inst,
1232 1,
1233 if inst.opcode == Opcode::Tex { dim } else { 4 },
1234 );
1235 let base = texture_intrinsic(inst, sampler_ty);
1236 match inst.opcode {
1237 Opcode::TexLdd => {
1238 let ddx = source_expr(ctx, inst, 3, dim);
1239 let ddy = source_expr(ctx, inst, 4, dim);
1240 format!("{}({}, {}, {}, {})", base, sampler_name, coord, ddx, ddy)
1241 }
1242 _ => format!("{}({}, {})", base, sampler_name, coord),
1243 }
1244}
1245
1246fn texture_intrinsic(inst: &Instruction, sampler_ty: SamplerTextureType) -> &'static str {
1247 let controls = inst.texld_controls();
1248 match (inst.opcode, sampler_ty, controls) {
1249 (Opcode::TexLdl, SamplerTextureType::Cube, _) => "texCUBElod",
1250 (Opcode::TexLdl, SamplerTextureType::Volume, _) => "tex3Dlod",
1251 (Opcode::TexLdl, _, _) => "tex2Dlod",
1252 (Opcode::TexLdd, SamplerTextureType::Cube, _) => "texCUBEgrad",
1253 (Opcode::TexLdd, SamplerTextureType::Volume, _) => "tex3Dgrad",
1254 (Opcode::TexLdd, _, _) => "tex2Dgrad",
1255 (Opcode::Tex, SamplerTextureType::Cube, 1) => "texCUBEproj",
1256 (Opcode::Tex, SamplerTextureType::Cube, 2) => "texCUBEbias",
1257 (Opcode::Tex, SamplerTextureType::Cube, _) => "texCUBE",
1258 (Opcode::Tex, SamplerTextureType::Volume, 1) => "tex3Dproj",
1259 (Opcode::Tex, SamplerTextureType::Volume, 2) => "tex3Dbias",
1260 (Opcode::Tex, SamplerTextureType::Volume, _) => "tex3D",
1261 (Opcode::Tex, _, 1) => "tex2Dproj",
1262 (Opcode::Tex, _, 2) => "tex2Dbias",
1263 (Opcode::Tex, _, _) => "tex2D",
1264 _ => "tex2D",
1265 }
1266}
1267
1268fn source_expr(ctx: &Context<'_>, inst: &Instruction, param_index: usize, count: usize) -> String {
1269 let Some(reg) = inst.source_register(param_index) else {
1270 return "0".to_string();
1271 };
1272 let mut expr = register_base(ctx, reg);
1273 let swz = inst.source_swizzle(param_index);
1274 expr.push_str(&hlsl_swizzle_suffix(swz, count));
1275 apply_source_modifier(expr, inst.source_modifier(param_index))
1276}
1277
1278fn register_base(ctx: &Context<'_>, reg: RegisterKey) -> String {
1279 match (reg.ty, ctx.shader.kind) {
1280 (RegisterType::Temp | RegisterType::TempFloat16 | RegisterType::Predicate, _) => {
1281 temp_name(reg, ctx.shader.kind)
1282 }
1283 (RegisterType::Texture, ShaderKind::Vertex) => temp_name(reg, ctx.shader.kind),
1284 (RegisterType::Texture | RegisterType::Input | RegisterType::MiscType, _) => {
1285 format!("input.{}", input_field_name(reg))
1286 }
1287 (RegisterType::Const, _) => const_row_expr(ctx, reg.number),
1288 (RegisterType::ConstInt, _) => int_const_expr(ctx, reg.number),
1289 (RegisterType::ConstBool, _) => bool_const_expr(ctx, reg.number),
1290 (RegisterType::Sampler, _) => sampler_name(ctx, reg.number),
1291 (
1292 RegisterType::ColorOut
1293 | RegisterType::DepthOut
1294 | RegisterType::RastOut
1295 | RegisterType::AttrOut
1296 | RegisterType::Output,
1297 _,
1298 ) => format!("output.{}", output_field_name(reg)),
1299 (RegisterType::Loop, _) => "_loop".to_string(),
1300 (RegisterType::Label, _) => format!("label{}", reg.number),
1301 _ => format!("u{}", reg.number),
1302 }
1303}
1304
1305fn input_field_name(reg: RegisterKey) -> String {
1306 match reg.ty {
1307 RegisterType::Input => format!("v{}", reg.number),
1308 RegisterType::Texture => format!("t{}", reg.number),
1309 RegisterType::MiscType => match reg.number {
1310 0 => "vPos".to_string(),
1311 1 => "vFace".to_string(),
1312 _ => format!("vMisc{}", reg.number),
1313 },
1314 _ => format!("in{}", reg.number),
1315 }
1316}
1317
1318fn output_field_name(reg: RegisterKey) -> String {
1319 match reg.ty {
1320 RegisterType::ColorOut => format!("oC{}", reg.number),
1321 RegisterType::DepthOut => "oDepth".to_string(),
1322 RegisterType::RastOut => match reg.number {
1323 0 => "oPos".to_string(),
1324 1 => "oFog".to_string(),
1325 2 => "oPts".to_string(),
1326 _ => format!("o{}", reg.number),
1327 },
1328 RegisterType::AttrOut => format!("oD{}", reg.number),
1329 RegisterType::Output => format!("o{}", reg.number),
1330 _ => format!("out{}", reg.number),
1331 }
1332}
1333
1334fn temp_name(reg: RegisterKey, kind: ShaderKind) -> String {
1335 match (reg.ty, kind) {
1336 (RegisterType::Texture, ShaderKind::Vertex) => format!("a{}", reg.number),
1337 (RegisterType::Predicate, _) => format!("p{}", reg.number),
1338 (RegisterType::TempFloat16, _) => format!("h{}", reg.number),
1339 _ => format!("r{}", reg.number),
1340 }
1341}
1342
1343fn temp_type(reg: RegisterKey) -> &'static str {
1344 match reg.ty {
1345 RegisterType::Predicate => "bool4",
1346 RegisterType::Texture => "float4",
1347 RegisterType::TempFloat16 => "half4",
1348 _ => "float4",
1349 }
1350}
1351
1352fn input_field_type(ctx: &Context<'_>, reg: RegisterKey) -> &'static str {
1353 match reg {
1354 RegisterKey {
1355 ty: RegisterType::MiscType,
1356 number: 1,
1357 } => "float",
1358 _ => {
1359 if let Some(decl) = ctx.decls.get(®) {
1360 if decl.semantic.starts_with("TEXCOORD") {
1361 return "float4";
1362 }
1363 if decl.semantic.starts_with("COLOR") {
1364 return "float4";
1365 }
1366 }
1367 "float4"
1368 }
1369 }
1370}
1371
1372fn output_field_type(reg: RegisterKey) -> &'static str {
1373 match reg {
1374 RegisterKey {
1375 ty: RegisterType::DepthOut,
1376 ..
1377 } => "float",
1378 RegisterKey {
1379 ty: RegisterType::RastOut,
1380 number: 1 | 2,
1381 } => "float",
1382 _ => "float4",
1383 }
1384}
1385
1386fn input_struct_name(kind: ShaderKind) -> &'static str {
1387 match kind {
1388 ShaderKind::Vertex => "VS_INPUT",
1389 ShaderKind::Pixel => "PS_INPUT",
1390 }
1391}
1392
1393fn output_struct_name(kind: ShaderKind) -> &'static str {
1394 match kind {
1395 ShaderKind::Vertex => "VS_OUTPUT",
1396 ShaderKind::Pixel => "PS_OUTPUT",
1397 }
1398}
1399
1400fn zero_value(ty: &str) -> &'static str {
1401 match ty {
1402 "float" => "0.0",
1403 "float2" => "float2(0.0, 0.0)",
1404 "float3" => "float3(0.0, 0.0, 0.0)",
1405 "half4" => "half4(0.0, 0.0, 0.0, 0.0)",
1406 "bool4" => "bool4(false, false, false, false)",
1407 _ => "float4(0.0, 0.0, 0.0, 0.0)",
1408 }
1409}
1410
1411fn hlsl_mask_suffix(mask: u8) -> String {
1412 let m = if mask == 0 { 0xf } else { mask };
1413 if m == 0xf {
1414 String::new()
1415 } else {
1416 let mut s = String::from(".");
1417 for (i, c) in ['x', 'y', 'z', 'w'].iter().enumerate() {
1418 if (m & (1 << i)) != 0 {
1419 s.push(*c);
1420 }
1421 }
1422 s
1423 }
1424}
1425
1426fn hlsl_swizzle_suffix(swizzle: [usize; 4], count: usize) -> String {
1427 let count = count.clamp(1, 4);
1428 let identity = [0usize, 1, 2, 3];
1429 if count == 4 && swizzle == identity {
1430 return String::new();
1431 }
1432 let names = ['x', 'y', 'z', 'w'];
1433 let mut s = String::from(".");
1434 for i in 0..count {
1435 s.push(names[swizzle[i]]);
1436 }
1437 s
1438}
1439
1440fn apply_source_modifier(expr: String, modifier: SourceModifier) -> String {
1441 match modifier {
1442 SourceModifier::None => expr,
1443 SourceModifier::Negate => format!("-({})", expr),
1444 SourceModifier::Bias => format!("(({}) - 0.5)", expr),
1445 SourceModifier::BiasAndNegate => format!("-(({}) - 0.5)", expr),
1446 SourceModifier::Sign => format!("((({}) - 0.5) * 2.0)", expr),
1447 SourceModifier::SignAndNegate => format!("-((({}) - 0.5) * 2.0)", expr),
1448 SourceModifier::Complement => format!("(1.0 - ({}))", expr),
1449 SourceModifier::X2 => format!("(({}) * 2.0)", expr),
1450 SourceModifier::X2AndNegate => format!("-(({}) * 2.0)", expr),
1451 SourceModifier::DivideByZ => format!("(({}) / ({}).z)", expr, expr),
1452 SourceModifier::DivideByW => format!("(({}) / ({}).w)", expr, expr),
1453 SourceModifier::Abs => format!("abs({})", expr),
1454 SourceModifier::AbsAndNegate => format!("-abs({})", expr),
1455 SourceModifier::Not => format!("!({})", expr),
1456 SourceModifier::Unknown(_) => expr,
1457 }
1458}
1459
1460fn apply_result_modifier(expr: String, modifier: ResultModifier) -> String {
1461 if modifier.saturate {
1462 format!("saturate({})", expr)
1463 } else {
1464 expr
1465 }
1466}
1467
1468fn line(out: &mut String, indent: usize, s: &str) {
1469 for _ in 0..indent {
1470 out.push_str(" ");
1471 }
1472 out.push_str(s);
1473 out.push('\n');
1474}
1475
1476fn cmp_op(code: u8) -> &'static str {
1477 match code {
1478 1 => ">",
1479 2 => "==",
1480 3 => ">=",
1481 4 => "<",
1482 5 => "!=",
1483 6 => "<=",
1484 _ => "!=",
1485 }
1486}
1487
1488fn sampler_texture_type(ctx: &Context<'_>, sampler: u16) -> SamplerTextureType {
1489 if let Some(c) = sampler_constant(ctx, sampler)
1490 && let Some(t) = &c.type_info
1491 {
1492 return match t.value_type {
1493 ValueType::SamplerCube => SamplerTextureType::Cube,
1494 ValueType::Sampler3D => SamplerTextureType::Volume,
1495 _ => SamplerTextureType::TwoD,
1496 };
1497 }
1498 ctx.sampler_decls
1499 .get(&sampler)
1500 .copied()
1501 .unwrap_or(SamplerTextureType::TwoD)
1502}
1503
1504fn sampler_type_name(ty: SamplerTextureType) -> &'static str {
1505 match ty {
1506 SamplerTextureType::Cube => "samplerCUBE",
1507 SamplerTextureType::Volume => "sampler3D",
1508 SamplerTextureType::TwoD | SamplerTextureType::Unknown => "sampler2D",
1509 }
1510}
1511
1512fn sampler_name(ctx: &Context<'_>, sampler: u16) -> String {
1513 sampler_constant(ctx, sampler)
1514 .map(|c| sanitize_ident(&c.name))
1515 .unwrap_or_else(|| format!("s{}", sampler))
1516}
1517
1518fn sampler_constant<'a>(ctx: &'a Context<'_>, sampler: u16) -> Option<&'a ConstantInfo> {
1519 ctx.ctab?
1520 .constants
1521 .iter()
1522 .find(|c| c.register_set == RegisterSet::Sampler && c.register_index == sampler)
1523}
1524
1525fn float_constant<'a>(ctx: &'a Context<'_>, index: u16) -> Option<(&'a ConstantInfo, u16)> {
1526 let ctab = ctx.ctab?;
1527 ctab.constants.iter().find_map(|c| {
1528 if c.register_set == RegisterSet::Float4
1529 && index >= c.register_index
1530 && index < c.register_index.saturating_add(c.register_count)
1531 {
1532 Some((c, index - c.register_index))
1533 } else {
1534 None
1535 }
1536 })
1537}
1538
1539fn matrix_constant_name(ctx: &Context<'_>, index: u16, rows_needed: usize) -> Option<String> {
1540 let (c, row) = float_constant(ctx, index)?;
1541 if row != 0 || c.register_count < rows_needed as u16 {
1542 return None;
1543 }
1544 let Some(t) = &c.type_info else {
1545 return None;
1546 };
1547 match t.class {
1548 TypeClass::MatrixRows | TypeClass::MatrixColumns => Some(sanitize_ident(&c.name)),
1549 _ => None,
1550 }
1551}
1552
1553fn const_row_expr(ctx: &Context<'_>, index: u16) -> String {
1554 if let Some((c, row)) = float_constant(ctx, index) {
1555 let name = sanitize_ident(&c.name);
1556 if c.register_count <= 1 {
1557 return name;
1558 }
1559 if let Some(t) = &c.type_info {
1560 match t.class {
1561 TypeClass::MatrixRows | TypeClass::MatrixColumns => {
1562 return format!("{}[{}]", name, row);
1563 }
1564 TypeClass::Vector | TypeClass::Scalar => return name,
1565 _ => {}
1566 }
1567 }
1568 return format!("{}[{}]", name, row);
1569 }
1570 format!("c{}", index)
1571}
1572
1573fn int_const_expr(ctx: &Context<'_>, index: u16) -> String {
1574 if let Some(ctab) = ctx.ctab
1575 && let Some(c) = ctab
1576 .constants
1577 .iter()
1578 .find(|c| c.register_set == RegisterSet::Int4 && c.register_index == index)
1579 {
1580 return sanitize_ident(&c.name);
1581 }
1582 format!("i{}", index)
1583}
1584
1585fn bool_const_expr(ctx: &Context<'_>, index: u16) -> String {
1586 if let Some(ctab) = ctx.ctab
1587 && let Some(c) = ctab
1588 .constants
1589 .iter()
1590 .find(|c| c.register_set == RegisterSet::Bool && c.register_index == index)
1591 {
1592 return sanitize_ident(&c.name);
1593 }
1594 format!("b{}", index)
1595}
1596
1597fn fmt_f32(v: f32) -> String {
1598 if v.is_nan() {
1599 "(0.0 / 0.0)".to_string()
1600 } else if v.is_infinite() {
1601 if v.is_sign_positive() {
1602 "(1.0 / 0.0)".to_string()
1603 } else {
1604 "(-1.0 / 0.0)".to_string()
1605 }
1606 } else {
1607 let mut s = format!("{:.9}", v);
1608 while s.contains('.') && s.ends_with('0') {
1609 s.pop();
1610 }
1611 if s.ends_with('.') {
1612 s.push('0');
1613 }
1614 if !s.contains('.') && !s.contains('e') && !s.contains('E') {
1615 s.push_str(".0");
1616 }
1617 s
1618 }
1619}
1620
1621fn sanitize_ident(s: &str) -> String {
1622 let mut out = String::new();
1623 for (i, ch) in s.chars().enumerate() {
1624 let ok = ch == '_' || ch.is_ascii_alphanumeric();
1625 if i == 0 {
1626 if ok && (ch == '_' || ch.is_ascii_alphabetic()) {
1627 out.push(ch);
1628 } else {
1629 out.push('_');
1630 if ok {
1631 out.push(ch);
1632 }
1633 }
1634 } else if ok {
1635 out.push(ch);
1636 } else {
1637 out.push('_');
1638 }
1639 }
1640 if out.is_empty() {
1641 "unnamed".to_string()
1642 } else {
1643 out
1644 }
1645}