diff --git a/crates/xenia-gpu/src/translator.rs b/crates/xenia-gpu/src/translator.rs index 5befe1d..16e7ecb 100644 --- a/crates/xenia-gpu/src/translator.rs +++ b/crates/xenia-gpu/src/translator.rs @@ -472,28 +472,37 @@ impl EmitCtx { } } - // Scalar pipe. Binary ops use (src_a.x, src_b.x); ps-variants use - // src_a.x + running ps. `scl_src_a` mirrors the interpreter's - // `scalar_src_is_ps` selector. - // - // NOTE: the scalar-constant family (MULSC/ADDSC/SUBSC, 42..=47) really - // reads a SPECIAL operand pair — a float constant at src3's index - // (W-swizzled) and a temp register at a bit-packed index (X-swizzled), - // per canary ucode.h:1302 / shader_translator.cc:1419. A first attempt - // at that addressing rendered the intro video BLACK (the operands - // resolved to zero), so it's reverted here to the plain (src_a.x, - // src_b.x) read: the YUV→RGB result is then visible but MAGENTA-tinted - // (wrong chroma coefficients). Correct SC-operand addressing is a - // follow-up. See project memory STEP 92. - let scl_src_a = if alu.scalar_src_is_ps { - "ps".to_string() + // Scalar pipe. Most scalar ops use (src_a.x, src_b.x); ps-variants use + // the running `ps`. The scalar-constant family (MULSC/ADDSC/SUBSC, + // 42..=47) is SPECIAL: it reads one temp register and one float + // constant, both addressed through src3 (canary ucode.h + // `scalar_const_reg_op_src_temp_reg`): + // temp reg = (src3_swiz & 0x3C) | (scalar_opc & 1), component = src3_swiz & 3 + // const idx = src3_reg (+256 for the PS constant bank), component = .w + // The op is then temp const. Feeding it the plain (src_a.x, + // src_b.x) instead computed e.g. r0.x*r0.x (Yb²) for the YUV→RGB luma + // scale — a squared luma that crushes shadows so dark regions render as + // near-pure chroma (the intro's dark background read purple). The + // earlier attempt at this addressing failed only because the constant + // bank was still mis-indexed (pre-STEP-95): the coefficients read zero. + let (scl_src_a, scl_src_b) = if (42..=47).contains(&alu.scalar_opcode) { + let temp_reg = ((alu.src_c_swiz & 0x3C) | (alu.scalar_opcode & 1)) as u32; + let temp_comp = ['x', 'y', 'z', 'w'][(alu.src_c_swiz & 0x3) as usize]; + let const_idx = alu.src_c as u32 + const_base; + ( + format!("r[{temp_reg}u].{temp_comp}"), + format!("xenos_consts.alu[{const_idx}u].w"), + ) + } else if alu.scalar_src_is_ps { + ("ps".to_string(), format!("{}.x", b)) } else { - format!("{}.x", a) + (format!("{}.x", a), format!("{}.x", b)) }; - let scl_src_b = format!("{}.x", b); let expr = match scalar_expr(alu.scalar_opcode, &scl_src_a, &scl_src_b, "ps") { Some(e) => e, - None => return Err(reject::SCL_OP_UNSUPPORTED), + None => { + return Err(reject::SCL_OP_UNSUPPORTED); + } }; self.push(&format!("ps = {expr};")); if alu.scalar_write_mask != 0 {