Skip to main content

siglus_cfx_decompiler/
hlsl.rs

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(&reg) {
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(&reg) {
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(&reg) {
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}