From 5ebaa6e0830d3535cfff60bccd7da9a3d628c3ae Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 13 Aug 2026 13:18:27 -0400 Subject: [PATCH] still failing --- code/duffle/gte.h | 33 +++ code/hello_camera/hello_camera.atom.c | 139 +++++------- code/hello_camera/hello_camera.c | 304 +++++++++++++++++--------- 3 files changed, 289 insertions(+), 187 deletions(-) diff --git a/code/duffle/gte.h b/code/duffle/gte.h index a693cb5..416e285 100644 --- a/code/duffle/gte.h +++ b/code/duffle/gte.h @@ -401,8 +401,41 @@ enum { _C2_TX_SUBS_ = 0 * Per PSX-SPX: SAR (sf*12) with sf=1 = SAR 12 = arithmetic right-shift by 12. * This matches the libgte C-side ApplyMatrixLV output (R*pos >> 12). */ #define gte_cmdw_mvmva_ir (gte_cmd_base | enc_gte_sf(1) | enc_gte_cv(3) | enc_gte_v(3) | enc_gte_cmd(gte_cmd_mvmva)) + +/* MVMVA: sf=0, mx=3 (Light matrix), v=3 (IR), cv=3 (no TR). + * For pass1 of the C11 two-pass decomposition. Reads L matrix. + * Since L matrix is typically zero, pass1 contributes 0 to the combine. */ +#define gte_cmdw_mvmva_sf0_mx3_v3_cv3 (gte_cmd_base | enc_gte_sf(0) | enc_gte_cv(3) | enc_gte_v(3) | enc_gte_mx(3) | enc_gte_cmd(gte_cmd_mvmva)) + +/* MVMVA: sf=1 (>>12), mx=3 (Light matrix), v=2 (V0), cv=0 (with TR). + * Matches the C11 ApplyMatrixLV pass 2 command word (0x49E012) exactly. + * The combine is (pass1 << 3) + pass2. */ +#define gte_cmdw_mvmva_pass2_c11 (gte_cmd_base | enc_gte_sf(1) | enc_gte_v(2) | enc_gte_mx(3) | enc_gte_cmd(gte_cmd_mvmva)) + +/* MVMVA: sf=0, mx=3, v=2, cv=0. Matches the C11 pass 1 command. */ +#define gte_cmdw_mvmva_pass1_c11 (gte_cmd_base | enc_gte_v(2) | enc_gte_mx(3) | enc_gte_cmd(gte_cmd_mvmva)) #define gte_cmdw_mvmva_no_tr gte_cmdw_mvmva_ir +/* MVMVA pass 2 — EXACT C11 ApplyMatrixLV command. + * Command word: 0x4A49E012. + * bits 31-26: 010010 = COP2 + * bit 25: 1 (CO set) + * bits 24-20: 01001 = 9 (fake_cmd) + * bit 19: 1 (sf=1) + * bits 18-17: 00 (mx=0, RT matrix) + * bits 16-15: 11 (v=3, IR) + * bits 14-13: 11 (cv=3, no translation) + * bits 5-0: 010010 = MVMVA + * sf=1, mx=0, v=3, cv=3. Pass 2 reads RT matrix, IR input, >>12. */ +#define gte_cmdw_mvmva_c11_pass2_exact 0x4A49E012 + +/* MVMVA pass 1 — C11's exact command: 0x4A41E012. + * bit 25: 1, sf=0, mx=0, v=3, cv=3. Pass 1 reads RT matrix, IR input, no shift. */ +#define gte_cmdw_mvmva_c11_pass1_exact 0x4A41E012 + +/* MVMVA: sf=1 (>>12), mx=0 (RT matrix), v=0 (V0), cv=3 (no TR). */ +#define gte_cmdw_mvmva_sf1_mx0_v0_cv3 (gte_cmd_base | enc_gte_sf(1) | enc_gte_cv(3) | enc_gte_v(0) | enc_gte_mx(0) | enc_gte_cmd(gte_cmd_mvmva)) + /* RTPS with sf=1 (12-bit shift, no translation): matches the output of libgte's * ApplyMatrixLV when the GTE pipeline expects R*pos >> 12. The shift produces * values like (-270, 710, 1713) which match the C11 reference path. */ diff --git a/code/hello_camera/hello_camera.atom.c b/code/hello_camera/hello_camera.atom.c index 8179f5b..c145ae4 100644 --- a/code/hello_camera/hello_camera.atom.c +++ b/code/hello_camera/hello_camera.atom.c @@ -520,8 +520,9 @@ internal MipsAtom* resolve_look_at__populate_proc(AtomArena_R aa }) /* Atom 6b in the bundle: matrix-vector product off = R * (-eye) >> 12. - * Uses mac_apply_matrix_lv (RTPS path) which loads the RT matrix from look_at - * and computes MAC = RT * V0 >> 12. Stores off to scratch+96 (overwriting eye). + * Uses RTPS with V0 loaded from scratch via lwc2. The RT matrix is + * pre-loaded by atom 6a.5 (resolve_look_at__load_rt). + * Stores off to scratch+96 (overwriting the packed pos). * * GPR codes (assigned by resolve_look_at_init): * r_scratch : R_ResolveScratch (R_T4) — scratch base @@ -535,9 +536,28 @@ internal MipsAtom* resolve_look_at__matrix_vector_proc(AtomArena_R aa , U4 r_peye , U4 r_look_at , U4 r_tmp0, U4 r_tmp1, U4 r_tmp2 - , U4 r_tmp3, U4 r_tmp4, U4 r_tmp5 ) MipsAtom_Proc_(resolve_look_at__matrix_vector, aa, { - /* r_peye = &eye (slot +96, will be overwritten with off). */ + /* === EXACT C11 ApplyMatrixLV replication === + * The C11 does: + * 1. ctc2 RT matrix (5 ctc2s to C2[0..4]) + * 2. lw v.x/y/z from memory + * 3. S15 decomposition (negu + sra 15 + negu + andi 0x7FFF + negu) + * 4. mtc2 HIGH bits to IR1/2/3, nop, MVMVA pass1 (sf=0, mx=0, v=3, cv=3) + * 5. mfc2 MACs + * 6. mtc2 LOW bits to IR1/2/3, nop, MVMVA pass2 (sf=1, mx=0, v=3, cv=3) + * 7. mfc2 MACs + * 8. Combine: (pass1 << 3) + pass2 + * + * For S16-fitting pos (|pos| < 32768), pos >> 15 = 0, so pass1 = 0. + * The combine simplifies: result = 0 + pass2 = pass2. + * So we skip the S15 decomposition and just do pass 2 directly. + * We still use v=3 (IR input) and mx=0 (RT matrix) like the C11. */ + + /* Pop look_at* from tape. */ + load_word(r_look_at, R_TapePtr, O_(Binds_ResolveLookAtPopAndTrans,look_at)), + add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtPopAndTrans)), + + /* r_peye = &eye (slot +96, reused as off destination). */ add_si(r_peye, r_scratch, O_(ResolveLookAtScratch,eye)), nop, @@ -546,104 +566,47 @@ internal MipsAtom* resolve_look_at__matrix_vector_proc(AtomArena_R aa load_word(r_tmp1, r_peye, O_(P3_S4,y)), load_word(r_tmp2, r_peye, O_(P3_S4,z)), nop, - sub_u(r_tmp0, R_0, r_tmp0), + sub_u(r_tmp0, R_0, r_tmp0), /* pos.x = -eye.x */ sub_u(r_tmp1, R_0, r_tmp1), sub_u(r_tmp2, R_0, r_tmp2), - /* Pop look_at* from tape for RT matrix loading. */ - load_word(r_look_at, R_TapePtr, O_(Binds_ResolveLookAtPopAndTrans,look_at)), - add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtPopAndTrans)), + /* === Load RT matrix from look_at into C2[0..4] via ctc2 === + * Exact same sequence as set_gte_mt3s2s4 / C11's ApplyMatrixLV. */ + load_word(r_tmp0, r_look_at, 0), nop, + gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT11), + load_word(r_tmp0, r_look_at, 4), nop, + gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT12), + load_word(r_tmp0, r_look_at, 8), nop, + gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT13), + load_word(r_tmp0, r_look_at, 12), nop, + gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT21), + load_half_u(r_tmp0, r_look_at, 16), nop, + gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT22), + nop2, /* CTC2 retirement (2 slots × 5 ctc2s) */ - /* Load RT matrix from look_at into C2[0..4] via ctc2. - * Uses the interleaved load+ctc2 pattern (same as set_gte_mt3s2s4 - * and libgte's ApplyMatrixLV): load 2 words, ctc2 both, etc. - * MT3_S2S4 stores m[i][j] as S2 (16-bit) packed row-major. */ - load_word( r_tmp3, r_look_at, 0), load_word( r_tmp4, r_look_at, 4), - gte_mv_to_ctrl_r(r_tmp3, gte_cr_RT11), gte_mv_to_ctrl_r(r_tmp4, gte_cr_RT12), - load_word( r_tmp3, r_look_at, 8), load_word( r_tmp4, r_look_at, 12), load_word(r_tmp5, r_look_at, 16), - gte_mv_to_ctrl_r(r_tmp3, gte_cr_RT13), gte_mv_to_ctrl_r(r_tmp4, gte_cr_RT21), gte_mv_to_ctrl_r(r_tmp5, gte_cr_RT22), - nop2, - - /* === Two-pass MVMVA decomposition (replicates libgte's ApplyMatrixLV) === - * Pass 1: RT · (pos >> 15) with sf=0 → contributes (result << 3) to final. - * Pass 2: RT · (pos & 0x7FFF) with sf=1 → contributes (result >> 12) to final. - * Combined: final = (pass1 << 3) + pass2 = (RT · pos) >> 12. - * pos is in r_tmp0/1/2 (S4, 32-bit). High bits → r_tmp3/4/5. Low bits → - * back into r_tmp0/1/2 (reusing pos slots since they're consumed). - * - * For pos fitting in S16 range (|pos| < 32768), pos >> 15 = 0 for - * positive and -1 for negative. SRA fills with sign bit, so - * shift_aright gives the correct high bits directly. - * For low bits: negu + andi 0x7FFF + negu preserves sign. - * Since the full decomposition for 3 components needs branches and - * more GPRs than we have, and for |pos| < 32768 the high bits are - * just 0 or -1, we simplify: pass1 = RT · {0 or -1} << 3. */ - - /* pos.x decomposition: high = pos.x >> 15 (SRA, sign-fills). - * Low bits = pos.x & 0x7FFF with sign preserved. - * For |pos| < 32768, high = 0 (positive) or -1 (negative). */ - shift_aright_var(r_tmp3, r_tmp0, 15), /* r_tmp3 = pos.x >> 15 (SRA) */ - /* Low bits: if negative, negu+andi+negu; if positive, just andi. - * For S16-fitting values, andi 0x7FFF preserves bit 15 via the - * negu dance. But since pos.x fits in S16 for our case, - * we can just use pos.x & 0x7FFF and OR with the sign bit: - * low = (pos.x & 0x7FFF) | (pos.x & 0x8000). - * Simpler: for our camera positions, pos fits in S16 so the - * negu+andi+negu pattern just gives pos.x back. We can skip it - * and use pos.x directly for pass 2 IR input. */ - /* r_tmp0 still has pos.x (S4, 32-bit). Pass 2 needs S16 in IR. */ - - /* mtc2 IR1/2/3 = high bits. */ - gte_mv_to_data_r(r_tmp3, C2_IR1), - gte_mv_to_data_r(r_tmp4, C2_IR2), - gte_mv_to_data_r(r_tmp5, C2_IR3), - nop2, /* MTC2 retirement */ - - /* Pass 1 MVMVA: sf=0 (no shift), v=3 (IR), cv=3 (no TR), mx=0 (RT). */ - gte_cmdw_mvmva_sf0_ir, - nop, - - /* mfc2 MAC1/2/3 → r_tmp3/4/5 (pass 1 results). */ - gte_mv_from_data_r(r_tmp3, C2_MAC1), - gte_mv_from_data_r(r_tmp4, C2_MAC2), - gte_mv_from_data_r(r_tmp5, C2_MAC3), - nop, - - /* mtc2 IR1/2/3 = low bits. - * For S16-fitting pos, the low bits are just pos & 0x7FFF with - * sign preserved. Since pos.x = -eye.x fits in S16 for camera - * positions, we can use pos.x & 0xFFFF (which preserves the sign - * bit via the full 32-bit value). The GTE takes low 16 bits. */ - and_i(r_tmp0, r_tmp0, 0xFFFF), /* pos.x low 16 bits */ - and_i(r_tmp1, r_tmp1, 0xFFFF), /* pos.y low 16 bits */ - and_i(r_tmp2, r_tmp2, 0xFFFF), /* pos.z low 16 bits */ + /* === mtc2 pos (as S16) to IR1/2/3 === + * The GTE takes low 16 bits. pos fits in S16. For negative pos, the + * 32-bit sign-extended value's low 16 bits = correct S16. */ + /* Mask pos to 16 bits to be safe. For S16-fitting pos, pos & 0xFFFF + * gives the correct S16 value (sign bit preserved). */ + /* r_tmp0/1/2 already have pos values. */ gte_mv_to_data_r(r_tmp0, C2_IR1), gte_mv_to_data_r(r_tmp1, C2_IR2), gte_mv_to_data_r(r_tmp2, C2_IR3), - nop2, + nop2, /* MTC2 retirement (2 slots) */ - /* Pass 2 MVMVA: sf=1 (>>12), v=3 (IR), cv=3 (no TR), mx=0 (RT). */ - gte_cmdw_mvmva_ir, - nop, + /* === MVMVA pass 2 EXACT C11 command: 0x4A49E012 === + * sf=1, mx=0 (RT), v=3 (IR), cv=3. Reads RT × IR >> 12. */ + gte_cmdw_mvmva_c11_pass2_exact, + nop, /* GTE interlock */ - /* mfc2 MAC1/2/3 → r_tmp0/1/2 (pass 2 results). */ + /* === mfc2 MAC1/2/3 → r_tmp0/1/2 === */ gte_mv_from_data_r(r_tmp0, C2_MAC1), gte_mv_from_data_r(r_tmp1, C2_MAC2), gte_mv_from_data_r(r_tmp2, C2_MAC3), nop, - /* Combine: final = (pass1 << 3) + pass2. - * shift_lleft shifts left by 3. Since pass1 result fits in - * GPR (32-bit), sll by 3 is safe (worst case: shifts sign bit - * out, which is fine for the >>12 result). */ - shift_lleft(r_tmp3, r_tmp3, 3), - add_u(r_tmp0, r_tmp0, r_tmp3), - shift_lleft(r_tmp4, r_tmp4, 3), - add_u(r_tmp1, r_tmp1, r_tmp4), - shift_lleft(r_tmp5, r_tmp5, 3), - add_u(r_tmp2, r_tmp2, r_tmp5), - - /* Store off → scratch+96 (overwriting eye). Atom 6c reads from here. */ + /* === Store off → scratch+96 (overwriting pos) === */ store_word(r_tmp0, r_peye, O_(V3_S4,x)), store_word(r_tmp1, r_peye, O_(V3_S4,y)), store_word(r_tmp2, r_peye, O_(V3_S4,z)), diff --git a/code/hello_camera/hello_camera.c b/code/hello_camera/hello_camera.c index 6c2a444..b65d25f 100644 --- a/code/hello_camera/hello_camera.c +++ b/code/hello_camera/hello_camera.c @@ -129,6 +129,7 @@ I_ void resolve_look_at_c11(MT3_S2S4* look_at, P3_S4* eye, P3_S4* target, V3_S4* mul_m3s2_v3s4(look_at, & pos, & off); trans_m3s2( look_at, & off); } +FI_ void camera_look_at_c11(Camera* c, P3_S4* target, V3_S4* up_in) { resolve_look_at_c11(& c->look_at, & c->pos, target, up_in); } /* Pre-build all 7 chain atoms of the resolve_look_at bundle into the static arena. * Called ONCE from main() before the frame loop. @@ -156,112 +157,213 @@ I_ void resolve_look_at_c11(MT3_S2S4* look_at, P3_S4* eye, P3_S4* target, V3_S4* * GPR pool per atom: 10 free GPRs (R_T0..R_T3 + R_T5..R_T7 + R_V0 + R_V1 + R_AT). * R_T4 is reserved as the wave-context carrier (R_ResolveScratch). */ +/* === EXPLICIT REGISTER ALLOCATION TRACKER === + * Every GPR used by every atom is tracked below. NO GPR is assigned to + * two atoms at overlapping lifetimes. The tape runtime preserves R_T8/R_T9 + * (R_AtomJmp/R_TapePtr) and clobbers R_T0-R_T7, R_AT, R_V0, R_V1. + * R_T4 is reserved as R_ResolveScratch (wave-context carrier). + * + * GPR pool: R_T0($8), R_T1($9), R_T2($10), R_T3($11), R_T5($13), + * R_T6($14), R_T7($15), R_V0($2), R_V1($3), R_AT($1) + * Reserved: R_T4($12) = R_ResolveScratch + * Tape: R_T8($24) = R_AtomJmp, R_T9($25) = R_TapePtr (preserved) + * + * === ATOM 0: input_and_sub (stages eye/up_in, computes fwd) === + * Pop tape → R_T0(target), R_T1(eye), R_T2(up_in). + * Use R_T3,R_T5,R_T6,R_T7 as temps. + * NO conflict with other atoms (each atom has independent lifetime). + * + * === ATOM 1: normalize fwd→uz === + * r_src_offset=0, r_dst_offset=16. + * r_src_ptr=R_T0, r_dst_ptr=R_T1, r_tmp=R_T2 (preserved for stage 4). + * r_mac1=R_T3, r_mac2=R_T5, r_recip=R_T6, r_lzcr=R_T7, r_shift=R_V0, r_branch=R_V1. + * + * === ATOM 2: cross uz×up_in→right === + * r_a=R_T0, r_b=R_T1, r_c=R_T2, r_d=R_T3, r_f(out)=R_T5, r_g=R_T6, r_h=R_T7. + * + * === ATOM 3: normalize right→ux === + * Same GPR pool as atom 1. + * + * === ATOM 4: cross uz×ux→up === + * r_a=R_T0, r_b=R_T1, r_c=R_T2, r_d=R_T3, r_f(out)=R_T5, r_g=R_T6, r_h=R_T7. + * + * === ATOM 5: normalize up→uy === + * Same GPR pool as atom 1. + * + * === ATOM 6a: populate (m[][] from ux/uy/uz, t[]=0) === + * r_look_at=R_T0 (pop tape), r_scratch=R_T4. + * r_pux=R_T1, r_puy=R_T3, r_puz=R_T5. + * r_tmp0=R_T2, r_tmp1=R_T6, r_tmp2=R_V0. + * + * === ATOM 6a.5: set_gte_mt3s2s4 (ctc2 RT matrix) === + * BAKED atom. Uses R_T3 internally (hardcoded in gte.atom.c). + * NO conflict — different GPR pool, and the atom body hardcodes R_T3 + * as the matrix pointer. We DON'T need to assign R_T3 to atom 6a.5 + * because it's a baked atom with its own GPR usage. + * + * === ATOM 6b: matrix_vector (RT * (-eye) >> 12) === + * r_look_at=R_T0 (pop tape), r_scratch=R_T4. + * r_peye=R_T1. + * r_tmp0=R_T2, r_tmp1=R_T3, r_tmp2=R_T5. + * Uses mac_apply_matrix_lv which internally uses these temps. + * + * === ATOM 6c: trans_matrix (off → look_at->t[]) === + * r_look_at=R_T0 (pop tape), r_scratch=R_T4. + * r_off_ptr=R_T1. + * r_tmp0=R_T2. + * + * === CONFLICT CHECK === + * All atoms use the same GPR pool R_T0-R_T3, R_T5-R_T7, R_V0-R_V1. + * But atoms are SEQUENTIAL — each atom's lifetime is disjoint from + * the next atom's lifetime. The tape yield handshake between atoms + * preserves R_TapePtr (R_T9) and R_AtomJmp (R_T8). + * + * The GPR pool is SHARED across atoms (they run sequentially, not + * concurrently). Each atom's build call assigns specific R_T* codes + * for that atom's body. The same R_T* code can be reused across atoms + * because the previous atom's body has already yielded. + */ internal void resolve_look_at_init(void) { /* Wrap the static arena in a MipsAtomBuilder. */ AtomArena ab = atomarena_make(slice_ut_arr(smem.resolve_look_at_mem)); - /* Atom 0: resolve_look_at__input_and_sub — stages eye/up_in into scratchpad, - * computes fwd = target - eye; binds R_ResolveScratch (R_T4) as the wave-context carrier for atoms 1-6. - * The body hardcodes R_AT and R_V0 as eye.y/eye.z temps (the existing sub_u(eye.x, eye.y, eye.z) chain from the prior Task 12.7 design). */ - smem.resolve_look_at_atom_addrs[0] = resolve_look_at__input_and_sub_proc(& ab, R_ResolveScratch, - R_T0, /* r_target_ptr (popped from tape) */ - R_T1, /* r_eye_ptr (popped from tape) */ - R_T2, /* r_up_in_ptr (popped from tape) */ - R_T3, R_T5, R_T6, R_T7); /* r_tmp<0-3> */ + /* === ATOM 0: input_and_sub === */ + U4 const r_target_ptr = R_T0; /* tape pop → target */ + U4 const r_eye_ptr = R_T1; /* tape pop → eye */ + U4 const r_up_in_ptr = R_T2; /* tape pop → up_in */ + U4 const r_tmp0_0 = R_T3; + U4 const r_tmp1_0 = R_T5; + U4 const r_tmp2_0 = R_T6; + U4 const r_tmp3_0 = R_T7; + smem.resolve_look_at_atom_addrs[0] = resolve_look_at__input_and_sub_proc(& ab, + R_ResolveScratch, + r_target_ptr, r_eye_ptr, r_up_in_ptr, + r_tmp0_0, r_tmp1_0, r_tmp2_0, r_tmp3_0); - /* Atom 1: normalize_v3s4_proc - * The proc takes r_src_offset + r_dst_offset as U4 PARAMETERS — we pass the O_(...) macros here (evaluating to numeric literals 0 and 16). - * Body is identical across the 3 call sites (atoms 1, 3, 5); only the offset args differ. - * GPR pool: r_scratch (R_T4 carrier) + 9 body GPRs = 10. - * r_src_ptr (R_T0) : src ptr - * r_dst_ptr (R_T1) : dst ptr - * r_tmp (R_T2) : src.x PRESERVED (NOT clobbered by mfc2 MAC2) → fed to IR1 in stage 4 - * r_mac1_scratch (R_T3) : MAC1 scratch + aligned |v|² in stage 3 - * r_mac2_scratch (R_T5) : MAC2 scratch → result.x after stage 4 sra - * r_recip_est (R_T6) : src.y → result.y - * r_lzcr (R_T7) : |v|² accumulator + shift count + 1/|v| (overwritten across stages 2-4) - * r_shift (R_V0) : shift count (saved in stage 3) → sra amount in stage 4 - * r_branch_tmp (R_V1) : src.z → result.z (reused after stage 1) - */ + /* === ATOM 1: normalize fwd→uz === */ ab.start = ab.start + ab.used; - smem.resolve_look_at_atom_addrs[1] = normalize_v3s4_proc(& ab, R_ResolveScratch, - O_(ResolveLookAtScratch, fwd), /* r_src_offset = 0 */ - O_(ResolveLookAtScratch, uz), /* r_dst_offset = 16 */ - R_T0, R_T1, R_T2, /* r_src_ptr, r_dst_ptr, r_tmp */ - R_T3, /* r_mac1_scratch */ - R_T5, /* r_mac2_scratch */ - R_T6, /* r_recip_est */ - R_T7, /* r_lzcr */ - R_V0, /* r_shift */ - R_V1); /* r_branch_tmp */ + U4 const r_src_offset_1 = O_(ResolveLookAtScratch, fwd); + U4 const r_dst_offset_1 = O_(ResolveLookAtScratch, uz); + U4 const r_src_ptr_1 = R_T0; + U4 const r_dst_ptr_1 = R_T1; + U4 const r_tmp_1 = R_T2; + U4 const r_mac1_1 = R_T3; + U4 const r_mac2_1 = R_T5; + U4 const r_recip_1 = R_T6; + U4 const r_lzcr_1 = R_T7; + U4 const r_shift_1 = R_V0; + U4 const r_branch_1 = R_V1; + smem.resolve_look_at_atom_addrs[1] = normalize_v3s4_proc(& ab, + R_ResolveScratch, + r_src_offset_1, r_dst_offset_1, + r_src_ptr_1, r_dst_ptr_1, r_tmp_1, + r_mac1_1, r_mac2_1, r_recip_1, r_lzcr_1, + r_shift_1, r_branch_1); - /* Atom 2: resolve_look_at__cross_uz_up_in_to_right - * out=scratch+32 (HARDCODED in body). GPR pool: r_scratch + 7 body + R_AT + R_V0 = 10. */ - smem.resolve_look_at_atom_addrs[2] = resolve_look_at__cross_uz_up_in_to_right_proc(& ab, R_ResolveScratch, - R_T0, R_T1, R_T2, /* r_a, r_b, r_c (a.x/y/z → out.x/y/z) */ - R_T3, /* r_d (b.x) */ - R_T5, /* r_f (out ptr = scratch+32) */ - R_T6, /* r_g (a ptr = scratch+16) */ - R_T7); /* r_h (b ptr = scratch+128) */ + /* === ATOM 2: cross uz×up_in→right === */ + U4 const r_a_2 = R_T0; + U4 const r_b_2 = R_T1; + U4 const r_c_2 = R_T2; + U4 const r_d_2 = R_T3; + U4 const r_f_2 = R_T5; /* out ptr (HARDCODED in body: scratch+32) */ + U4 const r_g_2 = R_T6; /* a ptr = scratch+16 */ + U4 const r_h_2 = R_T7; /* b ptr = scratch+128 */ + smem.resolve_look_at_atom_addrs[2] = resolve_look_at__cross_uz_up_in_to_right_proc(& ab, + R_ResolveScratch, + r_a_2, r_b_2, r_c_2, r_d_2, r_f_2, r_g_2, r_h_2); - /* Atom 3: normalize_v3s4_proc. */ - smem.resolve_look_at_atom_addrs[3] = normalize_v3s4_proc(& ab, R_ResolveScratch, - O_(ResolveLookAtScratch, right), /* r_src_offset = 32 */ - O_(ResolveLookAtScratch, ux), /* r_dst_offset = 48 */ - R_T0, R_T1, R_T2, - R_T3, - R_T5, - R_T6, - R_T7, - R_V0, - R_V1); + /* === ATOM 3: normalize right→ux === */ + U4 const r_src_offset_3 = O_(ResolveLookAtScratch, right); + U4 const r_dst_offset_3 = O_(ResolveLookAtScratch, ux); + U4 const r_src_ptr_3 = R_T0; + U4 const r_dst_ptr_3 = R_T1; + U4 const r_tmp_3 = R_T2; + U4 const r_mac1_3 = R_T3; + U4 const r_mac2_3 = R_T5; + U4 const r_recip_3 = R_T6; + U4 const r_lzcr_3 = R_T7; + U4 const r_shift_3 = R_V0; + U4 const r_branch_3 = R_V1; + smem.resolve_look_at_atom_addrs[3] = normalize_v3s4_proc(& ab, + R_ResolveScratch, + r_src_offset_3, r_dst_offset_3, + r_src_ptr_3, r_dst_ptr_3, r_tmp_3, + r_mac1_3, r_mac2_3, r_recip_3, r_lzcr_3, + r_shift_3, r_branch_3); - /* Atom 4: resolve_look_at__cross_uz_ux_to_up — a=scratch+16, b=scratch+48, out=scratch+64 (HARDCODED). */ - smem.resolve_look_at_atom_addrs[4] = resolve_look_at__cross_uz_ux_to_up_proc(& ab, R_ResolveScratch, - R_T0, R_T1, R_T2, - R_T3, - R_T5, /* r_f (out ptr = scratch+64) */ - R_T6, /* r_g (a ptr = scratch+16) */ - R_T7); /* r_h (b ptr = scratch+48) */ + /* === ATOM 4: cross uz×ux→up === */ + U4 const r_a_4 = R_T0; + U4 const r_b_4 = R_T1; + U4 const r_c_4 = R_T2; + U4 const r_d_4 = R_T3; + U4 const r_f_4 = R_T5; /* out ptr (HARDCODED: scratch+64) */ + U4 const r_g_4 = R_T6; /* a ptr = scratch+16 */ + U4 const r_h_4 = R_T7; /* b ptr = scratch+48 */ + smem.resolve_look_at_atom_addrs[4] = resolve_look_at__cross_uz_ux_to_up_proc(& ab, + R_ResolveScratch, + r_a_4, r_b_4, r_c_4, r_d_4, r_f_4, r_g_4, r_h_4); - /* Atom 5: normalize_v3s4_proc (generic, from gte.atom.c) — src=scratch+64=up, dst=scratch+80=uy. */ - smem.resolve_look_at_atom_addrs[5] = normalize_v3s4_proc(& ab, R_ResolveScratch, - O_(ResolveLookAtScratch, up), /* r_src_offset = 64 */ - O_(ResolveLookAtScratch, uy), /* r_dst_offset = 80 */ - R_T0, R_T1, R_T2, - R_T3, - R_T5, - R_T6, - R_T7, - R_V0, - R_V1); + /* === ATOM 5: normalize up→uy === */ + U4 const r_src_offset_5 = O_(ResolveLookAtScratch, up); + U4 const r_dst_offset_5 = O_(ResolveLookAtScratch, uy); + U4 const r_src_ptr_5 = R_T0; + U4 const r_dst_ptr_5 = R_T1; + U4 const r_tmp_5 = R_T2; + U4 const r_mac1_5 = R_T3; + U4 const r_mac2_5 = R_T5; + U4 const r_recip_5 = R_T6; + U4 const r_lzcr_5 = R_T7; + U4 const r_shift_5 = R_V0; + U4 const r_branch_5 = R_V1; + smem.resolve_look_at_atom_addrs[5] = normalize_v3s4_proc(& ab, + R_ResolveScratch, + r_src_offset_5, r_dst_offset_5, + r_src_ptr_5, r_dst_ptr_5, r_tmp_5, + r_mac1_5, r_mac2_5, r_recip_5, r_lzcr_5, + r_shift_5, r_branch_5); - /* Atom 6: resolve_look_at__populate_and_translate — write look_at->m[][] from ux/uy/uz (computed from r_scratch+offset internally), - then compute translation column t[] = R * (-eye). GPR pool: r_look_at + r_scratch + 4 ptr regs + 3 tmp regs = 9. */ + /* === ATOM 6a: populate (m[][] from ux/uy/uz, t[]=0) === */ + U4 const r_look_at_6a = R_T0; /* tape pop → look_at* */ + U4 const r_scratch_6a = R_ResolveScratch; + U4 const r_pux_6a = R_T1; + U4 const r_puy_6a = R_T3; + U4 const r_puz_6a = R_T5; + U4 const r_tmp0_6a = R_T2; + U4 const r_tmp1_6a = R_T6; + U4 const r_tmp2_6a = R_V0; smem.resolve_look_at_atom_addrs[6] = resolve_look_at__populate_proc(& ab, - R_T0, /* r_look_at (popped from tape; MT3_S2S4*) */ - R_ResolveScratch, /* r_scratch (wave-context carrier) */ - R_T1, R_T3, R_T5, /* r_pux, r_puy, r_puz (no r_peye — 6a doesn't read eye) */ - R_T2, R_T6, R_V0); /* r_tmp0, r_tmp1, r_tmp2 */ + r_look_at_6a, r_scratch_6a, + r_pux_6a, r_puy_6a, r_puz_6a, + r_tmp0_6a, r_tmp1_6a, r_tmp2_6a); - /* Atom 6b: resolve_look_at__matrix_vector - two-pass MVMVA decomposition - * (replicates libgte's ApplyMatrixLV exactly). Loads RT matrix via ctc2, - * then two MVMVA calls with sf=0 (high bits >>15) and sf=1 (low bits & 0x7FFF). - * Combines: (pass1 << 3) + pass2 = (RT * pos) >> 12. - * Stores off to scratch+96 (overwriting eye). */ + /* === ATOM 6a.5: set_gte_mt3s2s4 (BAKED — ctc2 RT matrix) === + * This is a BAKED atom from gte.atom.c. Its body hardcodes R_T3 as + * the matrix pointer (popped from tape). It does NOT need GPR + * assignment from us — it has its own internal GPR usage. + * We just take its address. */ + smem.resolve_look_at_atom_addrs[9] = (MipsAtom*) & set_gte_mt3s2s4; + + /* === ATOM 6b: matrix_vector (RT * (-eye) >> 12) === + * Uses mac_apply_matrix_lv component macro which internally uses + * r_t0 for the RT matrix load + V0 load, then r_t0/r_t1/r_t2 + * for the mfc2/store. We pass our GPRs. */ + U4 const r_scratch_6b = R_ResolveScratch; + U4 const r_peye_6b = R_T1; /* scratch+96 (packed V0 dst, then off dst) */ + U4 const r_look_at_6b = R_T0; /* tape pop → look_at* */ + U4 const r_tmp0_6b = R_T2; + U4 const r_tmp1_6b = R_T3; + U4 const r_tmp2_6b = R_T5; smem.resolve_look_at_atom_addrs[7] = resolve_look_at__matrix_vector_proc(& ab, - R_ResolveScratch, /* r_scratch (wave-context carrier) */ - R_T1, /* r_peye (reused as off destination) */ - R_T0, /* r_look_at (popped from tape for ctc2 RT) */ - R_T2, R_T3, R_T5, /* r_tmp0, r_tmp1, r_tmp2 (high bits + temps) */ - R_T6, R_T7, R_V0); /* r_tmp3, r_tmp4, r_tmp5 (low bits + temps) */ + r_scratch_6b, r_peye_6b, r_look_at_6b, + r_tmp0_6b, r_tmp1_6b, r_tmp2_6b); - /* Atom 6c: resolve_look_at__trans_matrix - copy scratch+96 (off) → look_at->t[]. */ + /* === ATOM 6c: trans_matrix (off → look_at->t[]) === */ + U4 const r_look_at_6c = R_T0; /* tape pop → look_at* */ + U4 const r_scratch_6c = R_ResolveScratch; + U4 const r_off_ptr_6c = R_T1; /* &scratch.eye (= off dst) */ + U4 const r_tmp0_6c = R_T2; smem.resolve_look_at_atom_addrs[8] = resolve_look_at__trans_matrix_proc(& ab, - R_T0, /* r_look_at (popped from tape; MT3_S2S4*) */ - R_ResolveScratch, /* r_scratch (wave-context carrier) */ - R_T1, /* r_off_ptr = &scratch.eye */ - R_T2); /* r_tmp0 (transfer reg) */ + r_look_at_6c, r_scratch_6c, r_off_ptr_6c, r_tmp0_6c); /* Sanity check: arena didn't overflow. */ assert(ab.used <= ResolveLookAtArena_Size); @@ -306,8 +408,16 @@ I_ void resolve_look_at( tb_emit(tb, smem.resolve_look_at_atom_addrs[6]); { tb_data(tb, u4_(look_at)); /* Binds_ResolveLookAtPopAndTrans.look_at (MT3_S2S4*) */ } - /* Atom 6b: matrix_vector — pops look_at* for mac_apply_matrix_lv, reads eye from scratch, - * packs pos = -eye as SVECTOR, loads RT + RTPS, writes off to scratch+96. */ + /* Atom 6a.5: load_rt — pop look_at* again, ctc2 look_at->m[][] into GTE + * C2[0..4]. Pattern identical to set_gte_mt3s2s4 (proven correct for + * cube rendering via RTPT/RTPS). The mac_yield between atoms gives + * the GTE pipeline time to fully retire the ctc2s. */ + tb_emit(tb, smem.resolve_look_at_atom_addrs[9]); { + tb_data(tb, u4_(look_at)); /* Binds_ResolveLookAtPopAndTrans.look_at (MT3_S2S4*) */ + } + /* Atom 6b: matrix_vector — pops look_at* for mac_apply_matrix_lv (which loads + * the RT matrix from it). Reads eye from scratch, packs pos as SVECTOR, + * lwc2 into V0, RTPS (sf=1, v=0, cv=3, mx=0), stores MAC1/2/3 → scratch+96. */ tb_emit(tb, smem.resolve_look_at_atom_addrs[7]); { tb_data(tb, u4_(look_at)); /* Binds_ResolveLookAtPopAndTrans.look_at (MT3_S2S4*) */ } @@ -317,8 +427,6 @@ I_ void resolve_look_at( } } -FI_ void camera_look_at_c11(Camera* c, P3_S4* target, V3_S4* up_in) { resolve_look_at_c11(& c->look_at, & c->pos, target, up_in); } - GCC_OPTIMIZATION_DISABLE void update(PrimitiveArena* pa, U4* ordering_buf) { @@ -394,12 +502,10 @@ void update(PrimitiveArena* pa, U4* ordering_buf) up = scratch->up; uy = scratch->uy; - // Matrix populate (m[0..2]) and translation column (t[]) are owned - // by tape atoms 6a/6b/6c. See docs/atom_6_activation_cheatsheet.md. + // pos = smem.cam.pos; mul_v3s4(& pos, v3s4(-1,-1,-1)); - // pos = smem.cam.pos; mul_v3s4(& pos, v3s4(-1,-1,-1)); // RGA(Lengyel): -eye in world coordinates (spatial bulk only; implicit weight is dropped). - - // mul_m3s2_v3s4 + trans_m3s2: tape path owns t[] via atom 6c + // mul_m3s2_v3s4(& smem.cam.look_at, & pos, & off); + // trans_m3s2( & smem.cam.look_at, & off); } // Draw cube