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