1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::ctab::{ConstantInfo, ConstantTable, RegisterSet, TypeClass, ValueType};
4use crate::disasm::{
5 mask_len, DeclUsage, Instruction, Opcode, RegisterKey, RegisterType, ResultModifier,
6 SamplerTextureType, ShaderKind, SourceModifier,
7};
8use crate::disasm::{parse_shader, ShaderModel};
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 { values: BTreeMap::new(), clip_exprs: Vec::new() };
138 initialize_folded_state(ctx, &mut state);
139
140 for inst in &ctx.shader.instructions {
141 fold_instruction(ctx, &mut state, inst);
142 }
143
144 let mut out = String::new();
145 emit_uniforms(&mut out, ctx);
146 emit_def_constants(&mut out, ctx);
147 emit_structs(&mut out, ctx);
148
149 let input_name = input_struct_name(ctx.shader.kind);
150 let output_name = output_struct_name(ctx.shader.kind);
151 out.push_str(&format!("{} main({} input) {{\n", output_name, input_name));
152 out.push_str(&format!(" {} output;\n", output_name));
153
154 for clip in &state.clip_exprs {
155 out.push_str(&format!(" clip({});\n", clip));
156 }
157
158 for reg in &ctx.used_outputs {
159 let ty = output_field_type(*reg);
160 let value = folded_vector_expr(ctx, &state, *reg, vector_len_for_type(ty));
161 out.push_str(&format!(" output.{} = {};\n", output_field_name(*reg), value));
162 }
163
164 out.push_str(" return output;\n");
165 out.push_str("}\n");
166 out
167}
168
169fn initialize_folded_state(ctx: &Context<'_>, state: &mut FoldedState) {
170 for decl in ctx.decls.values() {
171 let len = vector_len_for_type(input_field_type(ctx, decl.reg));
172 for component in 0..len {
173 let key = ComponentKey { reg: decl.reg, component };
174 state.values.insert(key, format!("input.{}{}", input_field_name(decl.reg), component_suffix(component)));
175 }
176 }
177
178 for reg in &ctx.used_outputs {
179 for component in 0..vector_len_for_type(output_field_type(*reg)) {
180 state.values.insert(ComponentKey { reg: *reg, component }, scalar_zero(output_field_type(*reg)).to_string());
181 }
182 }
183}
184
185fn fold_instruction(ctx: &Context<'_>, state: &mut FoldedState, inst: &Instruction) {
186 match inst.opcode {
187 Opcode::Comment | Opcode::Dcl | Opcode::Nop | Opcode::End => {}
188 Opcode::Def | Opcode::DefI | Opcode::DefB => fold_def_instruction(state, inst),
189 Opcode::TexKill => {
190 if let Some(reg) = inst.dest_register() {
191 let mut comps = Vec::new();
192 for component in components_from_mask(inst.dest_write_mask()) {
193 comps.push(component_value(ctx, state, reg, component));
194 }
195 if !comps.is_empty() {
196 state.clip_exprs.push(vector_constructor(&comps));
197 }
198 }
199 }
200 _ if inst.opcode.has_destination() => {
201 let Some(dst_reg) = inst.dest_register() else { return; };
202 let dst_components = components_from_mask(inst.dest_write_mask());
203 let old = state.values.clone();
204 let mut next_values: Vec<(ComponentKey, String)> = Vec::new();
205 for (write_index, dst_component) in dst_components.iter().copied().enumerate() {
206 if let Some(mut expr) = folded_instruction_component(ctx, &old, inst, write_index, dst_component) {
207 expr = apply_result_modifier(expr, inst.dest_modifier());
208 next_values.push((ComponentKey { reg: dst_reg, component: dst_component }, expr));
209 }
210 }
211 for (key, expr) in next_values {
212 state.values.insert(key, expr);
213 }
214 }
215 _ => {}
216 }
217}
218
219fn fold_def_instruction(state: &mut FoldedState, inst: &Instruction) {
220 let Some(reg) = inst.dest_register() else { return; };
221 match inst.opcode {
222 Opcode::Def => {
223 for component in 0..4 {
224 state.values.insert(ComponentKey { reg, component }, fmt_f32(inst.get_float_param(component + 1)));
225 }
226 }
227 Opcode::DefI => {
228 for component in 0..4 {
229 state.values.insert(ComponentKey { reg, component }, inst.get_int_param(component + 1).to_string());
230 }
231 }
232 Opcode::DefB => {
233 state.values.insert(ComponentKey { reg, component: 0 }, if inst.get_int_param(1) != 0 { "true".to_string() } else { "false".to_string() });
234 }
235 _ => {}
236 }
237}
238
239fn folded_instruction_component(
240 ctx: &Context<'_>,
241 old: &BTreeMap<ComponentKey, String>,
242 inst: &Instruction,
243 write_index: usize,
244 dst_component: usize,
245) -> Option<String> {
246 let c = dst_component;
247 let s = |param: usize, component: usize| source_component_expr(ctx, old, inst, param, component);
248 Some(match inst.opcode {
249 Opcode::Mov | Opcode::MovA => s(1, c),
250 Opcode::Add => format!("({} + {})", s(1, c), s(2, c)),
251 Opcode::Sub => format!("({} - {})", s(1, c), s(2, c)),
252 Opcode::Mul => format!("({} * {})", s(1, c), s(2, c)),
253 Opcode::Mad => format!("({} * {} + {})", s(1, c), s(2, c), s(3, c)),
254 Opcode::Rcp => format!("(1.0 / {})", s(1, c)),
255 Opcode::Rsq => format!("rsqrt({})", s(1, c)),
256 Opcode::Dp3 => dot_expr_from_components(|component| s(1, component), |component| s(2, component), 3),
257 Opcode::Dp4 => dot_expr_from_components(|component| s(1, component), |component| s(2, component), 4),
258 Opcode::Min => format!("min({}, {})", s(1, c), s(2, c)),
259 Opcode::Max => format!("max({}, {})", s(1, c), s(2, c)),
260 Opcode::Slt => format!("({} < {} ? 1.0 : 0.0)", s(1, c), s(2, c)),
261 Opcode::Sge => format!("({} >= {} ? 1.0 : 0.0)", s(1, c), s(2, c)),
262 Opcode::Exp | Opcode::ExpP => format!("exp2({})", s(1, c)),
263 Opcode::Log | Opcode::LogP => format!("log2({})", s(1, c)),
264 Opcode::Frc => format!("frac({})", s(1, c)),
265 Opcode::Pow => format!("pow({}, {})", s(1, c), s(2, c)),
266 Opcode::Abs => format!("abs({})", s(1, c)),
267 Opcode::Nrm => normalize_component_expr(|component| s(1, component), c),
268 Opcode::SinCos => {
269 if write_index == 0 { format!("cos({})", s(1, c)) } else { format!("sin({})", s(1, c)) }
270 }
271 Opcode::Cmp => format!("({} >= 0 ? {} : {})", s(1, c), s(2, c), s(3, c)),
272 Opcode::Cnd => format!("({} > 0.5 ? {} : {})", s(1, c), s(2, c), s(3, c)),
273 Opcode::Crs => cross_component_expr(|component| s(1, component), |component| s(2, component), c),
274 Opcode::Dp2Add => format!("(({} * {}) + ({} * {}) + {})", s(1, 0), s(2, 0), s(1, 1), s(2, 1), s(3, 0)),
275 Opcode::M4x4 => matrix_component_expr(ctx, inst, old, 4, c),
276 Opcode::M4x3 => matrix_component_expr(ctx, inst, old, 4, c),
277 Opcode::M3x4 => matrix_component_expr(ctx, inst, old, 3, c),
278 Opcode::M3x3 => matrix_component_expr(ctx, inst, old, 3, c),
279 Opcode::M3x2 => matrix_component_expr(ctx, inst, old, 3, c),
280 Opcode::Tex | Opcode::TexLdl | Opcode::TexLdd => texture_component_expr(ctx, old, inst, c),
281 _ => return None,
282 })
283}
284
285fn source_component_expr(
286 ctx: &Context<'_>,
287 old: &BTreeMap<ComponentKey, String>,
288 inst: &Instruction,
289 param_index: usize,
290 component_index: usize,
291) -> String {
292 let Some(reg) = inst.source_register(param_index) else { return "0.0".to_string(); };
293 let mut component = component_index.min(3);
294 if reg.ty != RegisterType::MiscType || reg.number != 1 {
295 component = inst.source_swizzle(param_index)[component];
296 }
297 let expr = component_value_from(old, ctx, reg, component);
298 apply_source_modifier(expr, inst.source_modifier(param_index))
299}
300
301fn component_value(ctx: &Context<'_>, state: &FoldedState, reg: RegisterKey, component: usize) -> String {
302 component_value_from(&state.values, ctx, reg, component)
303}
304
305fn component_value_from(values: &BTreeMap<ComponentKey, String>, ctx: &Context<'_>, reg: RegisterKey, component: usize) -> String {
306 if let Some(v) = values.get(&ComponentKey { reg, component }) {
307 return v.clone();
308 }
309
310 match reg.ty {
311 RegisterType::Input | RegisterType::Texture | RegisterType::MiscType => format!("input.{}{}", input_field_name(reg), component_suffix(component)),
312 RegisterType::Const => const_component_expr(ctx, reg.number, component),
313 RegisterType::ConstInt => format!("{}{}", int_const_expr(ctx, reg.number), component_suffix(component)),
314 RegisterType::ConstBool => bool_const_expr(ctx, reg.number),
315 RegisterType::Sampler => sampler_name(ctx, reg.number),
316 RegisterType::ColorOut | RegisterType::DepthOut | RegisterType::RastOut | RegisterType::AttrOut | RegisterType::Output => {
317 format!("output.{}{}", output_field_name(reg), component_suffix(component))
318 }
319 _ => format!("{}{}", temp_name(reg, ctx.shader.kind), component_suffix(component)),
320 }
321}
322
323fn const_component_expr(ctx: &Context<'_>, index: u16, component: usize) -> String {
324 let base = const_row_expr(ctx, index);
325 format!("{}{}", base, component_suffix(component))
326}
327
328fn folded_vector_expr(ctx: &Context<'_>, state: &FoldedState, reg: RegisterKey, len: usize) -> String {
329 let mut comps = Vec::new();
330 for component in 0..len {
331 comps.push(component_value(ctx, state, reg, component));
332 }
333 vector_constructor(&comps)
334}
335
336fn vector_constructor(comps: &[String]) -> String {
337 match comps.len() {
338 0 => "0.0".to_string(),
339 1 => comps[0].clone(),
340 2 => format!("float2({}, {})", comps[0], comps[1]),
341 3 => format!("float3({}, {}, {})", comps[0], comps[1], comps[2]),
342 _ => format!("float4({}, {}, {}, {})", comps[0], comps[1], comps[2], comps[3]),
343 }
344}
345
346fn dot_expr_from_components<F, G>(mut a: F, mut b: G, len: usize) -> String
347where
348 F: FnMut(usize) -> String,
349 G: FnMut(usize) -> String,
350{
351 let mut parts = Vec::new();
352 for component in 0..len {
353 parts.push(format!("({} * {})", a(component), b(component)));
354 }
355 format!("({})", parts.join(" + "))
356}
357
358fn normalize_component_expr<F>(mut value: F, component: usize) -> String
359where
360 F: FnMut(usize) -> String,
361{
362 let x = value(0);
363 let y = value(1);
364 let z = value(2);
365 let denom = format!("sqrt(({} * {}) + ({} * {}) + ({} * {}))", x, x, y, y, z, z);
366 format!("({} / {})", value(component.min(2)), denom)
367}
368
369fn cross_component_expr<F, G>(mut a: F, mut b: G, component: usize) -> String
370where
371 F: FnMut(usize) -> String,
372 G: FnMut(usize) -> String,
373{
374 match component {
375 0 => format!("(({}) * ({}) - ({}) * ({}))", a(1), b(2), a(2), b(1)),
376 1 => format!("(({}) * ({}) - ({}) * ({}))", a(2), b(0), a(0), b(2)),
377 _ => format!("(({}) * ({}) - ({}) * ({}))", a(0), b(1), a(1), b(0)),
378 }
379}
380
381fn matrix_component_expr(
382 ctx: &Context<'_>,
383 inst: &Instruction,
384 old: &BTreeMap<ComponentKey, String>,
385 vec_len: usize,
386 component: usize,
387) -> String {
388 let terms = (0..vec_len)
389 .map(|i| {
390 let v = source_component_expr(ctx, old, inst, 1, i);
391 let m = if let Some(reg) = inst.source_register(2) {
392 if reg.ty == RegisterType::Const {
393 const_component_expr(ctx, reg.number + component as u16, i)
394 } else {
395 source_component_expr(ctx, old, inst, 2, i)
396 }
397 } else {
398 "0.0".to_string()
399 };
400 format!("({} * {})", v, m)
401 })
402 .collect::<Vec<_>>();
403 format!("({})", terms.join(" + "))
404}
405
406fn texture_component_expr(ctx: &Context<'_>, old: &BTreeMap<ComponentKey, String>, inst: &Instruction, component: usize) -> String {
407 let sampler = inst.source_register(2).map(|r| r.number).unwrap_or(0);
408 let sampler_name = sampler_name(ctx, sampler);
409 let sampler_ty = sampler_texture_type(ctx, sampler);
410 let dim = sampler_ty.hlsl_dim();
411 let coord_components = if inst.opcode == Opcode::Tex { dim } else { 4 };
412 let coord = (0..coord_components)
413 .map(|i| source_component_expr(ctx, old, inst, 1, i))
414 .collect::<Vec<_>>();
415 let coord_expr = vector_constructor(&coord);
416 let base = texture_intrinsic(inst, sampler_ty);
417 let sample = match inst.opcode {
418 Opcode::TexLdd => {
419 let ddx = (0..dim).map(|i| source_component_expr(ctx, old, inst, 3, i)).collect::<Vec<_>>();
420 let ddy = (0..dim).map(|i| source_component_expr(ctx, old, inst, 4, i)).collect::<Vec<_>>();
421 format!("{}({}, {}, {}, {})", base, sampler_name, coord_expr, vector_constructor(&ddx), vector_constructor(&ddy))
422 }
423 _ => format!("{}({}, {})", base, sampler_name, coord_expr),
424 };
425 format!("{}{}", sample, component_suffix(component))
426}
427
428fn components_from_mask(mask: u8) -> Vec<usize> {
429 let m = if mask == 0 { 0xf } else { mask };
430 let mut out = Vec::new();
431 for component in 0..4 {
432 if (m & (1 << component)) != 0 {
433 out.push(component);
434 }
435 }
436 out
437}
438
439fn vector_len_for_type(ty: &str) -> usize {
440 match ty {
441 "float" | "bool" => 1,
442 "float2" | "int2" | "bool2" => 2,
443 "float3" | "int3" | "bool3" => 3,
444 _ => 4,
445 }
446}
447
448fn scalar_zero(ty: &str) -> &'static str {
449 match ty {
450 "bool" | "bool4" => "false",
451 _ => "0.0",
452 }
453}
454
455fn component_suffix(component: usize) -> &'static str {
456 match component {
457 0 => ".x",
458 1 => ".y",
459 2 => ".z",
460 _ => ".w",
461 }
462}
463
464fn analyze<'a>(shader: &'a ShaderModel, ctab: Option<&'a ConstantTable>) -> Context<'a> {
465 let mut ctx = Context {
466 shader,
467 ctab,
468 decls: BTreeMap::new(),
469 sampler_decls: BTreeMap::new(),
470 def_float: BTreeMap::new(),
471 def_int: BTreeMap::new(),
472 def_bool: BTreeMap::new(),
473 used_inputs: BTreeSet::new(),
474 used_outputs: BTreeSet::new(),
475 used_temps: BTreeSet::new(),
476 used_samplers: BTreeSet::new(),
477 };
478
479 for inst in &shader.instructions {
480 match inst.opcode {
481 Opcode::Dcl => {
482 if let Some(reg) = inst.dest_register() {
483 if reg.ty == RegisterType::Sampler {
484 let sampler_ty = inst.decl_sampler_type();
485 ctx.sampler_decls.insert(reg.number, sampler_ty);
486 ctx.used_samplers.insert(reg.number);
487 } else {
488 let semantic = semantic_from_decl(shader.kind, inst);
489 ctx.decls.insert(reg, DeclInfo { reg, semantic, sampler_type: None });
490 }
491 }
492 }
493 Opcode::Def => {
494 if let Some(reg) = inst.dest_register() {
495 if reg.ty == RegisterType::Const {
496 ctx.def_float.insert(reg.number, DefFloat {
497 reg,
498 values: [inst.get_float_param(1), inst.get_float_param(2), inst.get_float_param(3), inst.get_float_param(4)],
499 });
500 }
501 }
502 }
503 Opcode::DefI => {
504 if let Some(reg) = inst.dest_register() {
505 if reg.ty == RegisterType::ConstInt {
506 ctx.def_int.insert(reg.number, DefInt {
507 reg,
508 values: [inst.get_int_param(1), inst.get_int_param(2), inst.get_int_param(3), inst.get_int_param(4)],
509 });
510 }
511 }
512 }
513 Opcode::DefB => {
514 if let Some(reg) = inst.dest_register() {
515 if reg.ty == RegisterType::ConstBool {
516 ctx.def_bool.insert(reg.number, DefBool { reg, value: inst.get_int_param(1) != 0 });
517 }
518 }
519 }
520 _ => {}
521 }
522
523 if inst.opcode == Opcode::End || inst.opcode == Opcode::Comment {
524 continue;
525 }
526
527 if inst.opcode == Opcode::TexKill {
528 if let Some(reg) = inst.dest_register() {
529 classify_source_register(&mut ctx, reg);
530 }
531 continue;
532 }
533
534 if inst.opcode.has_destination() {
535 if let Some(reg) = inst.dest_register() {
536 classify_dest_register(&mut ctx, reg);
537 }
538 }
539
540 let first_src = if inst.opcode.has_destination() { 1 } else { 0 };
541 for pi in first_src..inst.params.len() {
542 if inst.opcode == Opcode::Dcl || inst.opcode == Opcode::Def || inst.opcode == Opcode::DefI || inst.opcode == Opcode::DefB {
543 continue;
544 }
545 if let Some(reg) = inst.source_register(pi) {
546 classify_source_register(&mut ctx, reg);
547 }
548 }
549 }
550
551 infer_missing_decls(&mut ctx);
552 ctx
553}
554
555fn classify_dest_register(ctx: &mut Context<'_>, reg: RegisterKey) {
556 match reg.ty {
557 RegisterType::Temp | RegisterType::TempFloat16 | RegisterType::Texture | RegisterType::Predicate => {
558 if !(ctx.shader.kind == ShaderKind::Pixel && reg.ty == RegisterType::Texture) {
559 ctx.used_temps.insert(reg);
560 }
561 }
562 RegisterType::RastOut | RegisterType::AttrOut | RegisterType::Output | RegisterType::ColorOut | RegisterType::DepthOut => {
563 ctx.used_outputs.insert(reg);
564 }
565 _ => {}
566 }
567}
568
569fn classify_source_register(ctx: &mut Context<'_>, reg: RegisterKey) {
570 match reg.ty {
571 RegisterType::Input | RegisterType::MiscType => {
572 ctx.used_inputs.insert(reg);
573 }
574 RegisterType::Texture => {
575 if ctx.shader.kind == ShaderKind::Pixel {
576 ctx.used_inputs.insert(reg);
577 } else {
578 ctx.used_temps.insert(reg);
579 }
580 }
581 RegisterType::Temp | RegisterType::TempFloat16 | RegisterType::Predicate => {
582 ctx.used_temps.insert(reg);
583 }
584 RegisterType::Sampler => {
585 ctx.used_samplers.insert(reg.number);
586 }
587 RegisterType::RastOut | RegisterType::AttrOut | RegisterType::Output | RegisterType::ColorOut | RegisterType::DepthOut => {
588 ctx.used_outputs.insert(reg);
589 }
590 _ => {}
591 }
592}
593
594fn infer_missing_decls(ctx: &mut Context<'_>) {
595 for reg in ctx.used_inputs.clone() {
596 if ctx.decls.contains_key(®) {
597 continue;
598 }
599 let semantic = inferred_input_semantic(ctx.shader.kind, reg);
600 ctx.decls.insert(reg, DeclInfo { reg, semantic, sampler_type: None });
601 }
602 for sampler in ctx.used_samplers.clone() {
603 ctx.sampler_decls.entry(sampler).or_insert(SamplerTextureType::TwoD);
604 }
605 if ctx.shader.kind == ShaderKind::Pixel && ctx.used_outputs.is_empty() {
606 ctx.used_outputs.insert(RegisterKey { ty: RegisterType::ColorOut, number: 0 });
607 }
608}
609
610fn semantic_from_decl(kind: ShaderKind, inst: &Instruction) -> String {
611 if let Some(reg) = inst.dest_register() {
612 if reg.ty == RegisterType::MiscType {
613 return match reg.number {
614 0 => "VPOS".to_string(),
615 1 => "VFACE".to_string(),
616 _ => format!("TEXCOORD{}", reg.number),
617 };
618 }
619 }
620 if let Some(reg) = inst.dest_register() {
621 if kind == ShaderKind::Pixel {
622 match reg.ty {
623 RegisterType::Texture => return format!("TEXCOORD{}", reg.number),
624 RegisterType::Input => return format!("COLOR{}", reg.number),
625 _ => {}
626 }
627 }
628 }
629 let usage = inst.decl_usage();
630 let index = inst.decl_index();
631 let prefix = usage.semantic_prefix();
632 if index == 0 && !matches!(usage, DeclUsage::TexCoord | DeclUsage::Color | DeclUsage::Position) {
633 prefix.to_string()
634 } else {
635 format!("{}{}", prefix, index)
636 }
637}
638
639fn inferred_input_semantic(kind: ShaderKind, reg: RegisterKey) -> String {
640 match (kind, reg.ty) {
641 (ShaderKind::Pixel, RegisterType::Texture) => format!("TEXCOORD{}", reg.number),
642 (ShaderKind::Pixel, RegisterType::Input) => format!("COLOR{}", reg.number),
643 (_, RegisterType::Input) => match reg.number {
644 0 => "POSITION0".to_string(),
645 1 => "NORMAL0".to_string(),
646 n => format!("TEXCOORD{}", n.saturating_sub(2)),
647 },
648 (_, RegisterType::MiscType) => match reg.number {
649 0 => "VPOS".to_string(),
650 1 => "VFACE".to_string(),
651 _ => format!("TEXCOORD{}", reg.number),
652 },
653 _ => format!("TEXCOORD{}", reg.number),
654 }
655}
656
657fn output_semantic(ctx: &Context<'_>, reg: RegisterKey) -> String {
658 if let Some(decl) = ctx.decls.get(®) {
659 return decl.semantic.clone();
660 }
661 match reg.ty {
662 RegisterType::ColorOut => format!("COLOR{}", reg.number),
663 RegisterType::DepthOut => "DEPTH".to_string(),
664 RegisterType::RastOut => match reg.number {
665 0 => "POSITION0".to_string(),
666 1 => "FOG".to_string(),
667 2 => "PSIZE".to_string(),
668 n => format!("TEXCOORD{}", n),
669 },
670 RegisterType::AttrOut => format!("COLOR{}", reg.number),
671 RegisterType::Output => format!("TEXCOORD{}", reg.number),
672 _ => format!("TEXCOORD{}", reg.number),
673 }
674}
675
676fn emit_uniforms(out: &mut String, ctx: &Context<'_>) {
677 if let Some(ctab) = ctx.ctab {
678 for c in &ctab.constants {
679 let ty = c.hlsl_decl_type();
680 out.push_str(&format!("uniform {} {};\n", ty, sanitize_ident(&c.name)));
681 }
682 }
683
684 let mut emitted_sampler_fallback = false;
685 for sampler in &ctx.used_samplers {
686 if sampler_constant(ctx, *sampler).is_some() {
687 continue;
688 }
689 let ty = sampler_type_name(ctx.sampler_decls.get(sampler).copied().unwrap_or(SamplerTextureType::TwoD));
690 out.push_str(&format!("uniform {} s{};\n", ty, sampler));
691 emitted_sampler_fallback = true;
692 }
693 if ctx.ctab.is_some() || emitted_sampler_fallback {
694 out.push('\n');
695 }
696}
697
698fn emit_def_constants(out: &mut String, ctx: &Context<'_>) {
699 let mut any = false;
700 for (idx, def) in &ctx.def_float {
701 if float_constant(ctx, *idx).is_some() {
702 continue;
703 }
704 out.push_str(&format!(
705 "static const float4 c{} = float4({}, {}, {}, {});\n",
706 idx,
707 fmt_f32(def.values[0]),
708 fmt_f32(def.values[1]),
709 fmt_f32(def.values[2]),
710 fmt_f32(def.values[3])
711 ));
712 any = true;
713 }
714 for (idx, def) in &ctx.def_int {
715 out.push_str(&format!(
716 "static const int4 i{} = int4({}, {}, {}, {});\n",
717 idx, def.values[0], def.values[1], def.values[2], def.values[3]
718 ));
719 any = true;
720 }
721 for (idx, def) in &ctx.def_bool {
722 out.push_str(&format!("static const bool b{} = {};\n", idx, if def.value { "true" } else { "false" }));
723 any = true;
724 }
725 if any {
726 out.push('\n');
727 }
728}
729
730fn emit_structs(out: &mut String, ctx: &Context<'_>) {
731 let input_name = input_struct_name(ctx.shader.kind);
732 let output_name = output_struct_name(ctx.shader.kind);
733
734 out.push_str(&format!("struct {} {{\n", input_name));
735 for decl in ctx.decls.values() {
736 let field = input_field_name(decl.reg);
737 let ty = input_field_type(ctx, decl.reg);
738 out.push_str(&format!(" {} {} : {};\n", ty, field, decl.semantic));
739 }
740 out.push_str("};\n\n");
741
742 out.push_str(&format!("struct {} {{\n", output_name));
743 for reg in &ctx.used_outputs {
744 let field = output_field_name(*reg);
745 let sem = output_semantic(ctx, *reg);
746 let ty = output_field_type(*reg);
747 out.push_str(&format!(" {} {} : {};\n", ty, field, sem));
748 }
749 out.push_str("};\n\n");
750}
751
752fn emit_main(out: &mut String, ctx: &Context<'_>) {
753 let input_name = input_struct_name(ctx.shader.kind);
754 let output_name = output_struct_name(ctx.shader.kind);
755 out.push_str(&format!("{} main({} input) {{\n", output_name, input_name));
756 out.push_str(&format!(" {} output;\n", output_name));
757
758 for reg in &ctx.used_outputs {
759 out.push_str(&format!(" output.{} = {};\n", output_field_name(*reg), zero_value(output_field_type(*reg))));
760 }
761 for reg in &ctx.used_temps {
762 out.push_str(&format!(" {} {} = {};\n", temp_type(*reg), temp_name(*reg, ctx.shader.kind), zero_value(temp_type(*reg))));
763 }
764 if !ctx.used_outputs.is_empty() || !ctx.used_temps.is_empty() {
765 out.push('\n');
766 }
767
768 let mut indent = 1usize;
769 for inst in &ctx.shader.instructions {
770 emit_instruction(out, ctx, inst, &mut indent);
771 }
772
773 out.push_str(" return output;\n");
774 out.push_str("}\n");
775}
776
777fn emit_instruction(out: &mut String, ctx: &Context<'_>, inst: &Instruction, indent: &mut usize) {
778 match inst.opcode {
779 Opcode::Comment | Opcode::Dcl | Opcode::Def | Opcode::DefI | Opcode::DefB | Opcode::Nop | Opcode::End => {}
780 Opcode::TexKill => {
781 if let Some(reg) = inst.dest_register() {
782 let expr = format!("{}{}", register_base(ctx, reg), hlsl_mask_suffix(inst.dest_write_mask()));
783 line(out, *indent, &format!("clip({});", expr));
784 }
785 }
786 Opcode::If => {
787 let cond = source_expr(ctx, inst, 0, 1);
788 line(out, *indent, &format!("if ({}) {{", cond));
789 *indent += 1;
790 }
791 Opcode::IfC => {
792 let a = source_expr(ctx, inst, 0, 4);
793 let b = source_expr(ctx, inst, 1, 4);
794 line(out, *indent, &format!("if (all({} {} {})) {{", a, cmp_op(inst.comparison()), b));
795 *indent += 1;
796 }
797 Opcode::Else => {
798 if *indent > 0 { *indent -= 1; }
799 line(out, *indent, "} else {");
800 *indent += 1;
801 }
802 Opcode::EndIf => {
803 if *indent > 0 { *indent -= 1; }
804 line(out, *indent, "}");
805 }
806 Opcode::Break => line(out, *indent, "break;"),
807 Opcode::BreakC => {
808 let a = source_expr(ctx, inst, 0, 4);
809 let b = source_expr(ctx, inst, 1, 4);
810 line(out, *indent, &format!("if (all({} {} {})) break;", a, cmp_op(inst.comparison()), b));
811 }
812 Opcode::Rep => {
813 let n = source_expr(ctx, inst, 0, 1);
814 line(out, *indent, &format!("for (int _rep{} = 0; _rep{} < (int)({}); ++_rep{}) {{", inst.offset, inst.offset, n, inst.offset));
815 *indent += 1;
816 }
817 Opcode::Loop => {
818 line(out, *indent, &format!("for (int _loop{} = 0; ; ++_loop{}) {{", inst.offset, inst.offset));
819 *indent += 1;
820 }
821 Opcode::EndRep | Opcode::EndLoop => {
822 if *indent > 0 { *indent -= 1; }
823 line(out, *indent, "}");
824 }
825 Opcode::Ret => line(out, *indent, "return output;"),
826 _ if inst.opcode.has_destination() => {
827 if let Some((dst, rhs)) = assignment_expr(ctx, inst) {
828 line(out, *indent, &format!("{} = {};", dst, rhs));
829 }
830 }
831 _ => {}
832 }
833}
834
835fn assignment_expr(ctx: &Context<'_>, inst: &Instruction) -> Option<(String, String)> {
836 let reg = inst.dest_register()?;
837 let mask = inst.dest_write_mask();
838 let dst = format!("{}{}", register_base(ctx, reg), hlsl_mask_suffix(mask));
839 let n = mask_len(mask);
840 let mut rhs = match inst.opcode {
841 Opcode::Mov | Opcode::MovA => source_expr(ctx, inst, 1, n),
842 Opcode::Add => bin(ctx, inst, n, "+"),
843 Opcode::Sub => bin(ctx, inst, n, "-"),
844 Opcode::Mul => bin(ctx, inst, n, "*"),
845 Opcode::Mad => format!("({} * {} + {})", source_expr(ctx, inst, 1, n), source_expr(ctx, inst, 2, n), source_expr(ctx, inst, 3, n)),
846 Opcode::Rcp => format!("(1.0 / {})", source_expr(ctx, inst, 1, 1)),
847 Opcode::Rsq => format!("rsqrt({})", source_expr(ctx, inst, 1, 1)),
848 Opcode::Dp3 => format!("dot({}, {})", source_expr(ctx, inst, 1, 3), source_expr(ctx, inst, 2, 3)),
849 Opcode::Dp4 => format!("dot({}, {})", source_expr(ctx, inst, 1, 4), source_expr(ctx, inst, 2, 4)),
850 Opcode::Min => format!("min({}, {})", source_expr(ctx, inst, 1, n), source_expr(ctx, inst, 2, n)),
851 Opcode::Max => format!("max({}, {})", source_expr(ctx, inst, 1, n), source_expr(ctx, inst, 2, n)),
852 Opcode::Slt => format!("(1.0 - step({}, {}))", source_expr(ctx, inst, 2, n), source_expr(ctx, inst, 1, n)),
853 Opcode::Sge => format!("step({}, {})", source_expr(ctx, inst, 2, n), source_expr(ctx, inst, 1, n)),
854 Opcode::Exp | Opcode::ExpP => format!("exp2({})", source_expr(ctx, inst, 1, 1)),
855 Opcode::Log | Opcode::LogP => format!("log2({})", source_expr(ctx, inst, 1, 1)),
856 Opcode::Lit => format!("lit({})", source_expr(ctx, inst, 1, 4)),
857 Opcode::Dst => format!("dst({}, {})", source_expr(ctx, inst, 1, 4), source_expr(ctx, inst, 2, 4)),
858 Opcode::Lrp => format!("lerp({}, {}, {})", source_expr(ctx, inst, 3, n), source_expr(ctx, inst, 2, n), source_expr(ctx, inst, 1, n)),
859 Opcode::Frc => format!("frac({})", source_expr(ctx, inst, 1, n)),
860 Opcode::Pow => format!("pow({}, {})", source_expr(ctx, inst, 1, 1), source_expr(ctx, inst, 2, 1)),
861 Opcode::Abs => format!("abs({})", source_expr(ctx, inst, 1, n)),
862 Opcode::Nrm => format!("normalize({})", source_expr(ctx, inst, 1, 3)),
863 Opcode::SinCos => {
864 if mask & 1 != 0 && mask_len(mask) == 1 {
865 format!("cos({})", source_expr(ctx, inst, 1, 1))
866 } else if mask & 2 != 0 && mask_len(mask) == 1 {
867 format!("sin({})", source_expr(ctx, inst, 1, 1))
868 } else {
869 format!("float2(cos({0}), sin({0}))", source_expr(ctx, inst, 1, 1))
870 }
871 }
872 Opcode::Cmp => format!("({} >= 0 ? {} : {})", source_expr(ctx, inst, 1, n), source_expr(ctx, inst, 3, n), source_expr(ctx, inst, 2, n)),
873 Opcode::Cnd => format!("({} > 0.5 ? {} : {})", source_expr(ctx, inst, 1, n), source_expr(ctx, inst, 2, n), source_expr(ctx, inst, 3, n)),
874 Opcode::Crs => format!("cross({}, {})", source_expr(ctx, inst, 1, 3), source_expr(ctx, inst, 2, 3)),
875 Opcode::Dp2Add => format!("(dot({}, {}) + {})", source_expr(ctx, inst, 1, 2), source_expr(ctx, inst, 2, 2), source_expr(ctx, inst, 3, 1)),
876 Opcode::M4x4 => matrix_mul_expr(ctx, inst, 4, 4),
877 Opcode::M4x3 => matrix_mul_expr(ctx, inst, 4, 3),
878 Opcode::M3x4 => matrix_mul_expr(ctx, inst, 3, 4),
879 Opcode::M3x3 => matrix_mul_expr(ctx, inst, 3, 3),
880 Opcode::M3x2 => matrix_mul_expr(ctx, inst, 3, 2),
881 Opcode::Tex | Opcode::TexLdl | Opcode::TexLdd => texture_expr(ctx, inst),
882 Opcode::TexCoord => source_expr(ctx, inst, 1, n),
883 Opcode::Dsx => format!("ddx({})", source_expr(ctx, inst, 1, n)),
884 Opcode::Dsy => format!("ddy({})", source_expr(ctx, inst, 1, n)),
885 Opcode::Sgn => format!("sign({})", source_expr(ctx, inst, 1, n)),
886 _ => return None,
887 };
888 rhs = apply_result_modifier(rhs, inst.dest_modifier());
889 Some((dst, rhs))
890}
891
892fn bin(ctx: &Context<'_>, inst: &Instruction, n: usize, op: &str) -> String {
893 format!("({} {} {})", source_expr(ctx, inst, 1, n), op, source_expr(ctx, inst, 2, n))
894}
895
896fn matrix_mul_expr(ctx: &Context<'_>, inst: &Instruction, vec_len: usize, out_len: usize) -> String {
897 let v = source_expr(ctx, inst, 1, vec_len);
898 if let Some(reg) = inst.source_register(2) {
899 if reg.ty == RegisterType::Const {
900 if let Some(name) = matrix_constant_name(ctx, reg.number, out_len) {
901 return format!("mul({}, {})", v, name);
902 }
903 let rows = (0..out_len)
904 .map(|i| const_row_expr(ctx, reg.number + i as u16))
905 .collect::<Vec<_>>()
906 .join(", ");
907 let ty = format!("float{}x{}", out_len, vec_len);
908 return format!("mul({}, {}({}))", v, ty, rows);
909 }
910 }
911 format!("mul({}, {})", v, source_expr(ctx, inst, 2, vec_len))
912}
913
914fn texture_expr(ctx: &Context<'_>, inst: &Instruction) -> String {
915 let sampler = inst.source_register(2).map(|r| r.number).unwrap_or(0);
916 let sampler_name = sampler_name(ctx, sampler);
917 let sampler_ty = sampler_texture_type(ctx, sampler);
918 let dim = sampler_ty.hlsl_dim();
919 let coord = source_expr(ctx, inst, 1, if inst.opcode == Opcode::Tex { dim } else { 4 });
920 let base = texture_intrinsic(inst, sampler_ty);
921 match inst.opcode {
922 Opcode::TexLdd => {
923 let ddx = source_expr(ctx, inst, 3, dim);
924 let ddy = source_expr(ctx, inst, 4, dim);
925 format!("{}({}, {}, {}, {})", base, sampler_name, coord, ddx, ddy)
926 }
927 _ => format!("{}({}, {})", base, sampler_name, coord),
928 }
929}
930
931fn texture_intrinsic(inst: &Instruction, sampler_ty: SamplerTextureType) -> &'static str {
932 let controls = inst.texld_controls();
933 match (inst.opcode, sampler_ty, controls) {
934 (Opcode::TexLdl, SamplerTextureType::Cube, _) => "texCUBElod",
935 (Opcode::TexLdl, SamplerTextureType::Volume, _) => "tex3Dlod",
936 (Opcode::TexLdl, _, _) => "tex2Dlod",
937 (Opcode::TexLdd, SamplerTextureType::Cube, _) => "texCUBEgrad",
938 (Opcode::TexLdd, SamplerTextureType::Volume, _) => "tex3Dgrad",
939 (Opcode::TexLdd, _, _) => "tex2Dgrad",
940 (Opcode::Tex, SamplerTextureType::Cube, 1) => "texCUBEproj",
941 (Opcode::Tex, SamplerTextureType::Cube, 2) => "texCUBEbias",
942 (Opcode::Tex, SamplerTextureType::Cube, _) => "texCUBE",
943 (Opcode::Tex, SamplerTextureType::Volume, 1) => "tex3Dproj",
944 (Opcode::Tex, SamplerTextureType::Volume, 2) => "tex3Dbias",
945 (Opcode::Tex, SamplerTextureType::Volume, _) => "tex3D",
946 (Opcode::Tex, _, 1) => "tex2Dproj",
947 (Opcode::Tex, _, 2) => "tex2Dbias",
948 (Opcode::Tex, _, _) => "tex2D",
949 _ => "tex2D",
950 }
951}
952
953fn source_expr(ctx: &Context<'_>, inst: &Instruction, param_index: usize, count: usize) -> String {
954 let Some(reg) = inst.source_register(param_index) else { return "0".to_string(); };
955 let mut expr = register_base(ctx, reg);
956 let swz = inst.source_swizzle(param_index);
957 expr.push_str(&hlsl_swizzle_suffix(swz, count));
958 apply_source_modifier(expr, inst.source_modifier(param_index))
959}
960
961fn register_base(ctx: &Context<'_>, reg: RegisterKey) -> String {
962 match reg.ty {
963 RegisterType::Temp | RegisterType::TempFloat16 | RegisterType::Predicate => temp_name(reg, ctx.shader.kind),
964 RegisterType::Texture if ctx.shader.kind == ShaderKind::Vertex => temp_name(reg, ctx.shader.kind),
965 RegisterType::Texture | RegisterType::Input | RegisterType::MiscType => format!("input.{}", input_field_name(reg)),
966 RegisterType::Const => const_row_expr(ctx, reg.number),
967 RegisterType::ConstInt => int_const_expr(ctx, reg.number),
968 RegisterType::ConstBool => bool_const_expr(ctx, reg.number),
969 RegisterType::Sampler => sampler_name(ctx, reg.number),
970 RegisterType::ColorOut | RegisterType::DepthOut | RegisterType::RastOut | RegisterType::AttrOut | RegisterType::Output => format!("output.{}", output_field_name(reg)),
971 RegisterType::Loop => "_loop".to_string(),
972 RegisterType::Label => format!("label{}", reg.number),
973 _ => format!("u{}", reg.number),
974 }
975}
976
977fn input_field_name(reg: RegisterKey) -> String {
978 match reg.ty {
979 RegisterType::Input => format!("v{}", reg.number),
980 RegisterType::Texture => format!("t{}", reg.number),
981 RegisterType::MiscType => match reg.number {
982 0 => "vPos".to_string(),
983 1 => "vFace".to_string(),
984 _ => format!("vMisc{}", reg.number),
985 },
986 _ => format!("in{}", reg.number),
987 }
988}
989
990fn output_field_name(reg: RegisterKey) -> String {
991 match reg.ty {
992 RegisterType::ColorOut => format!("oC{}", reg.number),
993 RegisterType::DepthOut => "oDepth".to_string(),
994 RegisterType::RastOut => match reg.number {
995 0 => "oPos".to_string(),
996 1 => "oFog".to_string(),
997 2 => "oPts".to_string(),
998 _ => format!("o{}", reg.number),
999 },
1000 RegisterType::AttrOut => format!("oD{}", reg.number),
1001 RegisterType::Output => format!("o{}", reg.number),
1002 _ => format!("out{}", reg.number),
1003 }
1004}
1005
1006fn temp_name(reg: RegisterKey, kind: ShaderKind) -> String {
1007 match reg.ty {
1008 RegisterType::Texture if kind == ShaderKind::Vertex => format!("a{}", reg.number),
1009 RegisterType::Predicate => format!("p{}", reg.number),
1010 RegisterType::TempFloat16 => format!("h{}", reg.number),
1011 _ => format!("r{}", reg.number),
1012 }
1013}
1014
1015fn temp_type(reg: RegisterKey) -> &'static str {
1016 match reg.ty {
1017 RegisterType::Predicate => "bool4",
1018 RegisterType::Texture => "float4",
1019 RegisterType::TempFloat16 => "half4",
1020 _ => "float4",
1021 }
1022}
1023
1024fn input_field_type(ctx: &Context<'_>, reg: RegisterKey) -> &'static str {
1025 match reg.ty {
1026 RegisterType::MiscType if reg.number == 1 => "float",
1027 _ => {
1028 if let Some(decl) = ctx.decls.get(®) {
1029 if decl.semantic.starts_with("TEXCOORD") {
1030 return "float4";
1031 }
1032 if decl.semantic.starts_with("COLOR") {
1033 return "float4";
1034 }
1035 }
1036 "float4"
1037 }
1038 }
1039}
1040
1041fn output_field_type(reg: RegisterKey) -> &'static str {
1042 match reg.ty {
1043 RegisterType::DepthOut => "float",
1044 RegisterType::RastOut if reg.number == 1 || reg.number == 2 => "float",
1045 _ => "float4",
1046 }
1047}
1048
1049fn input_struct_name(kind: ShaderKind) -> &'static str {
1050 match kind {
1051 ShaderKind::Vertex => "VS_INPUT",
1052 ShaderKind::Pixel => "PS_INPUT",
1053 }
1054}
1055
1056fn output_struct_name(kind: ShaderKind) -> &'static str {
1057 match kind {
1058 ShaderKind::Vertex => "VS_OUTPUT",
1059 ShaderKind::Pixel => "PS_OUTPUT",
1060 }
1061}
1062
1063fn zero_value(ty: &str) -> &'static str {
1064 match ty {
1065 "float" => "0.0",
1066 "float2" => "float2(0.0, 0.0)",
1067 "float3" => "float3(0.0, 0.0, 0.0)",
1068 "half4" => "half4(0.0, 0.0, 0.0, 0.0)",
1069 "bool4" => "bool4(false, false, false, false)",
1070 _ => "float4(0.0, 0.0, 0.0, 0.0)",
1071 }
1072}
1073
1074fn hlsl_mask_suffix(mask: u8) -> String {
1075 let m = if mask == 0 { 0xf } else { mask };
1076 if m == 0xf {
1077 String::new()
1078 } else {
1079 let mut s = String::from(".");
1080 for (i, c) in ['x', 'y', 'z', 'w'].iter().enumerate() {
1081 if (m & (1 << i)) != 0 {
1082 s.push(*c);
1083 }
1084 }
1085 s
1086 }
1087}
1088
1089fn hlsl_swizzle_suffix(swizzle: [usize; 4], count: usize) -> String {
1090 let count = count.clamp(1, 4);
1091 let identity = [0usize, 1, 2, 3];
1092 if count == 4 && swizzle == identity {
1093 return String::new();
1094 }
1095 let names = ['x', 'y', 'z', 'w'];
1096 let mut s = String::from(".");
1097 for i in 0..count {
1098 s.push(names[swizzle[i]]);
1099 }
1100 s
1101}
1102
1103fn apply_source_modifier(expr: String, modifier: SourceModifier) -> String {
1104 match modifier {
1105 SourceModifier::None => expr,
1106 SourceModifier::Negate => format!("-({})", expr),
1107 SourceModifier::Bias => format!("(({}) - 0.5)", expr),
1108 SourceModifier::BiasAndNegate => format!("-(({}) - 0.5)", expr),
1109 SourceModifier::Sign => format!("((({}) - 0.5) * 2.0)", expr),
1110 SourceModifier::SignAndNegate => format!("-((({}) - 0.5) * 2.0)", expr),
1111 SourceModifier::Complement => format!("(1.0 - ({}))", expr),
1112 SourceModifier::X2 => format!("(({}) * 2.0)", expr),
1113 SourceModifier::X2AndNegate => format!("-(({}) * 2.0)", expr),
1114 SourceModifier::DivideByZ => format!("(({}) / ({}).z)", expr, expr),
1115 SourceModifier::DivideByW => format!("(({}) / ({}).w)", expr, expr),
1116 SourceModifier::Abs => format!("abs({})", expr),
1117 SourceModifier::AbsAndNegate => format!("-abs({})", expr),
1118 SourceModifier::Not => format!("!({})", expr),
1119 SourceModifier::Unknown(_) => expr,
1120 }
1121}
1122
1123fn apply_result_modifier(expr: String, modifier: ResultModifier) -> String {
1124 if modifier.saturate {
1125 format!("saturate({})", expr)
1126 } else {
1127 expr
1128 }
1129}
1130
1131fn line(out: &mut String, indent: usize, s: &str) {
1132 for _ in 0..indent {
1133 out.push_str(" ");
1134 }
1135 out.push_str(s);
1136 out.push('\n');
1137}
1138
1139fn cmp_op(code: u8) -> &'static str {
1140 match code {
1141 1 => ">",
1142 2 => "==",
1143 3 => ">=",
1144 4 => "<",
1145 5 => "!=",
1146 6 => "<=",
1147 _ => "!=",
1148 }
1149}
1150
1151fn sampler_texture_type(ctx: &Context<'_>, sampler: u16) -> SamplerTextureType {
1152 if let Some(c) = sampler_constant(ctx, sampler) {
1153 if let Some(t) = &c.type_info {
1154 return match t.value_type {
1155 ValueType::SamplerCube => SamplerTextureType::Cube,
1156 ValueType::Sampler3D => SamplerTextureType::Volume,
1157 _ => SamplerTextureType::TwoD,
1158 };
1159 }
1160 }
1161 ctx.sampler_decls.get(&sampler).copied().unwrap_or(SamplerTextureType::TwoD)
1162}
1163
1164fn sampler_type_name(ty: SamplerTextureType) -> &'static str {
1165 match ty {
1166 SamplerTextureType::Cube => "samplerCUBE",
1167 SamplerTextureType::Volume => "sampler3D",
1168 SamplerTextureType::TwoD | SamplerTextureType::Unknown => "sampler2D",
1169 }
1170}
1171
1172fn sampler_name(ctx: &Context<'_>, sampler: u16) -> String {
1173 sampler_constant(ctx, sampler)
1174 .map(|c| sanitize_ident(&c.name))
1175 .unwrap_or_else(|| format!("s{}", sampler))
1176}
1177
1178fn sampler_constant<'a>(ctx: &'a Context<'_>, sampler: u16) -> Option<&'a ConstantInfo> {
1179 ctx.ctab?.constants.iter().find(|c| c.register_set == RegisterSet::Sampler && c.register_index == sampler)
1180}
1181
1182fn float_constant<'a>(ctx: &'a Context<'_>, index: u16) -> Option<(&'a ConstantInfo, u16)> {
1183 let ctab = ctx.ctab?;
1184 ctab.constants.iter().find_map(|c| {
1185 if c.register_set == RegisterSet::Float4
1186 && index >= c.register_index
1187 && index < c.register_index.saturating_add(c.register_count)
1188 {
1189 Some((c, index - c.register_index))
1190 } else {
1191 None
1192 }
1193 })
1194}
1195
1196fn matrix_constant_name(ctx: &Context<'_>, index: u16, rows_needed: usize) -> Option<String> {
1197 let (c, row) = float_constant(ctx, index)?;
1198 if row != 0 || c.register_count < rows_needed as u16 {
1199 return None;
1200 }
1201 let Some(t) = &c.type_info else { return None; };
1202 match t.class {
1203 TypeClass::MatrixRows | TypeClass::MatrixColumns => Some(sanitize_ident(&c.name)),
1204 _ => None,
1205 }
1206}
1207
1208fn const_row_expr(ctx: &Context<'_>, index: u16) -> String {
1209 if let Some((c, row)) = float_constant(ctx, index) {
1210 let name = sanitize_ident(&c.name);
1211 if c.register_count <= 1 {
1212 return name;
1213 }
1214 if let Some(t) = &c.type_info {
1215 match t.class {
1216 TypeClass::MatrixRows | TypeClass::MatrixColumns => return format!("{}[{}]", name, row),
1217 TypeClass::Vector | TypeClass::Scalar => return name,
1218 _ => {}
1219 }
1220 }
1221 return format!("{}[{}]", name, row);
1222 }
1223 format!("c{}", index)
1224}
1225
1226fn int_const_expr(ctx: &Context<'_>, index: u16) -> String {
1227 if let Some(ctab) = ctx.ctab {
1228 if let Some(c) = ctab.constants.iter().find(|c| c.register_set == RegisterSet::Int4 && c.register_index == index) {
1229 return sanitize_ident(&c.name);
1230 }
1231 }
1232 format!("i{}", index)
1233}
1234
1235fn bool_const_expr(ctx: &Context<'_>, index: u16) -> String {
1236 if let Some(ctab) = ctx.ctab {
1237 if let Some(c) = ctab.constants.iter().find(|c| c.register_set == RegisterSet::Bool && c.register_index == index) {
1238 return sanitize_ident(&c.name);
1239 }
1240 }
1241 format!("b{}", index)
1242}
1243
1244fn fmt_f32(v: f32) -> String {
1245 if v.is_nan() {
1246 "(0.0 / 0.0)".to_string()
1247 } else if v.is_infinite() {
1248 if v.is_sign_positive() { "(1.0 / 0.0)".to_string() } else { "(-1.0 / 0.0)".to_string() }
1249 } else {
1250 let mut s = format!("{:.9}", v);
1251 while s.contains('.') && s.ends_with('0') {
1252 s.pop();
1253 }
1254 if s.ends_with('.') {
1255 s.push('0');
1256 }
1257 if !s.contains('.') && !s.contains('e') && !s.contains('E') {
1258 s.push_str(".0");
1259 }
1260 s
1261 }
1262}
1263
1264fn sanitize_ident(s: &str) -> String {
1265 let mut out = String::new();
1266 for (i, ch) in s.chars().enumerate() {
1267 let ok = ch == '_' || ch.is_ascii_alphanumeric();
1268 if i == 0 {
1269 if ok && (ch == '_' || ch.is_ascii_alphabetic()) {
1270 out.push(ch);
1271 } else {
1272 out.push('_');
1273 if ok {
1274 out.push(ch);
1275 }
1276 }
1277 } else if ok {
1278 out.push(ch);
1279 } else {
1280 out.push('_');
1281 }
1282 }
1283 if out.is_empty() { "unnamed".to_string() } else { out }
1284}