#ifdef INTELLISENSE_DIRECTIVES # include "gen/macs.h" # include "gen/offsets.h" # include "gte.h" # include "gp.h" # include "lottes_tape.h" #endif ATOM_FILE_DEBUGGER_LINE_MARKER(gte_atom_c); #pragma region MACs (Mips Atom Components) /* Words: 3; Loads 3 S2 indices from the face array */ FI_ Slice_MipsCode ac_load_tri_indices(AtomBuilder_R ab, U4 r_face_cusor, U4 r_i0, U4 r_i1, U4 r_i2) atom_dbg_skip MipsAtomComp_Proc_(ac_load_tri_indices, ab, { load_half_u(r_i0, r_face_cusor, 0 * S_(S2)), load_half_u(r_i1, r_face_cusor, 1 * S_(S2)), load_half_u(r_i2, r_face_cusor, 2 * S_(S2)), }) /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3. * PIPELINE: post-RTPT (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen). */ FI_ Slice_MipsCode ac_gte_store_f3(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_f3, ab, { gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_F3,p0)), gte_sw(C2_SXY1, r_primitive_cursor, O_(Poly_F3,p1)), gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_F3,p2)), }) /* Words: 18; Translates indices to vertex addresses and pushes them to GTE */ I_ Slice_MipsCode ac_gte_load_tri_verts(AtomBuilder_R ab, U4 r_vert_base, U4 r_v0, U4 r_v1, U4 r_v2) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_load_tri_verts, ab, { shift_lleft(R_AT, r_v0, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0), shift_lleft(R_AT, r_v1, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1), shift_lleft(R_AT, r_v2, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2), }) /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the * G4 triangle portion to p0/p1/p2. * PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen). * MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 get overwritten with v3 * (RTPS writes only to SXY2, but to keep the three registers aligned with v0/v1/v2 you must store before RTPS). */ FI_ Slice_MipsCode ac_gte_store_g4_p012(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_g4_p012, ab, { gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_G4,p0)), gte_sw(C2_SXY1, r_primitive_cursor, O_(Poly_G4,p1)), gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p2)), }) /* Words: 1; Stores the V3 screen coord to the G4's p3 slot. * PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its single-vertex result to SXY2; * SXY0 still holds v0.screen from the earlier RTPT. */ FI_ Slice_MipsCode ac_gte_store_g4_p3(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_g4_p3, ab, { gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3)) }) /* ─── STAGE 1 of normalize: SQR + mfc2 MAC1/2/3 ─── * Emits squared magnitude per component (in MAC1/2/3) into caller-provided scratch regs. * Stage 2 of normalize consumes these directly. * Words: 8. Clobbers: IR1/2/3, MAC1/2/3. Uses gte_cmdw_sqr (sf=0, lm=1). */ FI_ Slice_MipsCode ac_gte_sqr_v3(AtomBuilder_R ab, U4 r_sx, U4 r_sy, U4 r_sz, U4 r_sq_x, U4 r_sq_y, U4 r_sq_z) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_sqr_v3, ab, { gte_mv_to_data_r(r_sx, C2_IR1), gte_mv_to_data_r(r_sy, C2_IR2), gte_mv_to_data_r(r_sz, C2_IR3), nop, gte_cmdw_sqr, gte_mv_from_data_r(r_sq_x, C2_MAC1), gte_mv_from_data_r(r_sq_y, C2_MAC2), gte_mv_from_data_r(r_sq_z, C2_MAC3), }) /* ─── STAGE 4 of normalize: mtc2 IR0..3 + GPF + mfc2 MAC + srav finalize ─── * Reusable standalone — given an IR0 = 1/|v| estimate (typically from a sqrtbl lookup) and a shift count * (typically (31 - LZCR)/2), multiplies IR0*IR[i] via GPF and shifts right to produce the normalized output. * Used standalone for "scale vector by scalar". * Words: 11. Clobbers: IR0..3, MAC1..3. Uses gte_cmdw_gpf (sf=0, lm=0). */ FI_ Slice_MipsCode ac_gte_gpf_scale(AtomBuilder_R ab, U4 r_sx, U4 r_sy, U4 r_sz, U4 r_recip_est, U4 r_shift, U4 r_dx, U4 r_dy, U4 r_dz) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_gpf_scale, ab, { gte_mv_to_data_r(r_recip_est, C2_IR0), gte_mv_to_data_r(r_sx, C2_IR1), gte_mv_to_data_r(r_sy, C2_IR2), gte_mv_to_data_r(r_sz, C2_IR3), nop2, /* retire IR0..IR3 → GPF input pre-fill (matches libgte 0x80016134..0x80016138) */ gte_cmdw_gpf, gte_mv_from_data_r(r_dx, C2_MAC1), gte_mv_from_data_r(r_dy, C2_MAC2), gte_mv_from_data_r(r_dz, C2_MAC3), shift_aright_var(r_dx, r_dx, r_shift), shift_aright_var(r_dy, r_dy, r_shift), shift_aright_var(r_dz, r_dz, r_shift), }) /* ─── APPLY MATRIX LV (libgte ApplyMatrixLV port) ─── * Atom component — auto-generates mac_apply_matrix_lv Mac composer macro. * Uses GTE RTPS (cv=1, sf=1, v=0) with lwc2-loaded V0/VZ0 inputs. * Per PSX-SPX `geometrytransformationenginegte.md` lines 416-418: * IR1 = MAC1 = (TRX*1000h + RT11*VX0 + RT12*VY0 + RT13*VZ0) SAR (sf*12) * IR2 = MAC2 = (TRY*1000h + RT21*VX0 + RT22*VY0 + RT23*VZ0) SAR (sf*12) * IR3 = MAC3 = (TRZ*1000h + RT31*VX0 + RT32*VY0 + RT33*VZ0) SAR (sf*12) * RTPS uses the FULL row of the rotation matrix (not just diagonal like MVMVA with mx=0). * libgte's `gte_ApplyMatrix` calls `gte_rtv0()` = RTPS cv=1 v=0 mx=0. * Per `gte.h` line 405 the body sets cv=3 (BK, zero-initialized) so no TR contribution. * * Operands: * r_mtx : MT3_S2S4* (matrix pointer) * r_vec : U4 (pointer to PACKED V0 data — (pos.y << 16) | pos.x at +0, pos.z at +4) * r_out : V3_S4* (output pointer; MAC1/2/3 stored here) * r_t0/1/2 : 3 GPR codes for matrix load + intermediate state * Words: ~26. Clobbers: r_t0, r_t1, r_t2 (C2 $0..$4, VXY0/VZ0, MAC1/2/3, SXY0/1/2). */ FI_ Slice_MipsCode ac_apply_matrix_lv(AtomBuilder_R ab , U4 r_mtx, U4 r_vec, U4 r_out , U4 r_t0, U4 r_t1, U4 r_t2 ) MipsAtomComp_Proc_(ac_apply_matrix_lv, ab, { /* Load MATRIX rows into GTE RT11..RT33 (libgte convention: ctc2 to C2 $0..$4 in order). * load_half_u zero-extends the last word so RT33 = m[2][2] and TRX = 0. */ load_word(r_t0, r_mtx, 0), nop, gte_mv_to_ctrl_r(r_t0, gte_cr_RT11_Code), load_word(r_t0, r_mtx, 4), nop, gte_mv_to_ctrl_r(r_t0, gte_cr_RT12_Code), load_word(r_t0, r_mtx, 8), nop, gte_mv_to_ctrl_r(r_t0, gte_cr_RT13_Code), load_word(r_t0, r_mtx, 12), nop, gte_mv_to_ctrl_r(r_t0, gte_cr_RT21_Code), load_half_u(r_t0, r_mtx, 16), nop, gte_mv_to_ctrl_r(r_t0, gte_cr_RT22_Code), nop2, /* Load PACKED pos into V0 (libgte SVECTOR layout). * r_vec points to atom-0-staged packed data ((pos.y << 16) | pos.x at +0, pos.z at +4). * LWC2 base register MUST be the pointer r_vec, NOT the loaded value r_t0. */ load_word(r_t0, r_vec, 0), nop, gte_lw(C2_VXY0, r_vec, 0), load_word(r_t0, r_vec, 4), nop, gte_lw(C2_VZ0, r_vec, 4), /* RTPS: cv=3 (no translation), sf=1 (no shift, integer), v=0 (V0 input), * mx=0 (rotation matrix). MAC = RT row · V0 + 0. RTPS also writes * SXY0/1/2 + SZ0..SZ3 (perspective division); ignored. */ gte_cmdw_rtps_sf1, /* Read MAC1/2/3 → out. */ gte_mv_from_data_r(r_t0, C2_MAC1), gte_mv_from_data_r(r_t1, C2_MAC2), gte_mv_from_data_r(r_t2, C2_MAC3), nop, store_word(r_t0, r_out, 0), store_word(r_t1, r_out, 4), store_word(r_t2, r_out, 8), }) /* ─── TRANS MATRIX (libgte TransMatrix port) ─── * Atom component — auto-generates mac_trans_matrix Mac composer macro. * m->t = v (struct copy; libgte's TransMatrix at 0x8001a540 is just 3 store_words, no GTE, no add). * Uses 1 GPR (r_t1 = off value) per axis; per-axis load-delay-slot pattern. * Words: 9. Clobbers: r_t1. */ FI_ Slice_MipsCode ac_trans_matrix(AtomBuilder_R ab , U4 r_mtx, U4 r_off , U4 r_t1 ) MipsAtomComp_Proc_(ac_trans_matrix, ab, { load_word(r_t1, r_off, O_(V3_S4,x)), nop, store_word(r_t1, r_mtx, O_(MT3_S2S4,t[0])), load_word(r_t1, r_off, O_(V3_S4,y)), nop, store_word(r_t1, r_mtx, O_(MT3_S2S4,t[1])), load_word(r_t1, r_off, O_(V3_S4,z)), nop, store_word(r_t1, r_mtx, O_(MT3_S2S4,t[2])), }) #pragma endregion MACs (Mips Atom Components) #pragma region Atom Procs /* ─── Local copy of PSYQ's sqrtbl (1/sqrt lookup table for VectorNormal). ─── * Source: PSYQ 4.7 libgte sqrtbl at 0x800185B4 in hello_camera.elf. * objdump -s --start-address=0x800185B4 --stop-address=0x800185F4 hello_camera.elf * → 192 entries × 16-bit signed, in 1.12 fixed-point (max value 0x1000 = 1.0). * * Data is identical to the libgte original (byte-for-byte verified). * * ─── Per-entry semantics (decoded from libgte msc02 VectorNormal) ─── * Each entry is `1/sqrt(x)` in 1.12 fixed point (value / 4096). * The 192 entries span 4 octaves of the input magnitude, with 48 entries per octave: * Octave 0 (entries 0- 47): mantissa in [0x8000, 0x10000) output ~[1.000, 0.707] * Octave 1 (entries 48- 95): mantissa in [0x10000, 0x20000) output ~[0.707, 0.500] * Octave 2 (entries 96-143): mantissa in [0x20000, 0x40000) output ~[0.500, 0.354] * Octave 3 (entries144-191): mantissa in [0x40000, 0x80000) output ~[0.354, 0.251] * Within each octave, 8 sub-entries interpolate over the 8 fractional bits of the mantissa * (the byte `(0x80 | (i mod 8))` for the lower-byte of the aligned value). * Sampling the first value of each octave: * [0] 0x1000 = 1.0000 ; 1 / sqrt(1.0000) * [48] 0x0e4f = 0.8940 ; 1 / sqrt(1.2500) * [96] 0x0d10 = 0.8164 ; 1 / sqrt(1.5000) * [144] 0x0c0a = 0.7520 ; 1 / sqrt(1.7500) * And representative sub-entries within octave 0 (mantissa in [0x8000, 0x8100)): * [0] 0x1000 = 1.0000 ; 1 / sqrt(0x8000) * [1] 0x0fe0 = 0.9922 ; 1 / sqrt(0x8100) * [2] 0x0fc1 = 0.9846 ; 1 / sqrt(0x8200) * [3] 0x0fa3 = 0.9773 ; 1 / sqrt(0x8300) * [4] 0x0f85 = 0.9700 ; 1 / sqrt(0x8400) * [5] 0x0f68 = 0.9629 ; 1 / sqrt(0x8500) * [6] 0x0f4c = 0.9561 ; 1 / sqrt(0x8600) * [7] 0x0f30 = 0.9492 ; 1 / sqrt(0x8700) * * The algorithm's `addi -64 / sll 1 / lh` selects the entry at `(aligned - 64) * 2` for the case where `aligned` has its top bit at bit 24. * After the sllv/srav pair, `aligned` always lands in `[0x80, 0x100)` * (with top bit at bit 24 → after `sub $aligned - 64`, the index sits in `[0x40, 0x80) * 2 = [0x80, 0x100)` bytes = entries [64, 128) within the sqrtbl). * The earlier 64 entries (octave 0) are reached when the magnitude after shifting puts the top bit below bit 24 (the `sllv` branch), * and the load upper_halves of the table bracket the input range. * The later 64 entries (octaves 2-3) are the `srav` branch when the magnitude's top bit is well above bit 24. * * 192-entry table is reproduced verbatim from libgte (verified against libpsn00b/psxgte/vector.s:100-123 — 24 rows × 8 halfwords, last entry 0x0804). */ internal RO_ S2 gte_normalize_sqr_tbl[192] align_(2) = { 0x1000, 0x0fe0, 0x0fc1, 0x0fa3, 0x0f85, 0x0f68, 0x0f4c, 0x0f30, 0x0f15, 0x0efb, 0x0ee1, 0x0ec7, 0x0eae, 0x0e96, 0x0e7e, 0x0e66, 0x0e4f, 0x0e38, 0x0e22, 0x0e0c, 0x0df7, 0x0de2, 0x0dcd, 0x0db9, 0x0da5, 0x0d91, 0x0d7e, 0x0d6b, 0x0d58, 0x0d45, 0x0d33, 0x0d21, 0x0d10, 0x0cff, 0x0cee, 0x0cdd, 0x0ccc, 0x0cbc, 0x0cac, 0x0c9c, 0x0c8d, 0x0c7d, 0x0c6e, 0x0c5f, 0x0c51, 0x0c42, 0x0c34, 0x0c26, 0x0c18, 0x0c0a, 0x0bfd, 0x0bef, 0x0be2, 0x0bd5, 0x0bc8, 0x0bbb, 0x0baf, 0x0ba2, 0x0b96, 0x0b8a, 0x0b7e, 0x0b72, 0x0b67, 0x0b5b, 0x0b50, 0x0b45, 0x0b39, 0x0b2e, 0x0b24, 0x0b19, 0x0b0e, 0x0b04, 0x0af9, 0x0aef, 0x0ae5, 0x0adb, 0x0ad1, 0x0ac7, 0x0abd, 0x0ab4, 0x0aaa, 0x0aa1, 0x0a97, 0x0a8e, 0x0a85, 0x0a7c, 0x0a73, 0x0a6a, 0x0a61, 0x0a59, 0x0a50, 0x0a47, 0x0a3f, 0x0a37, 0x0a2e, 0x0a26, 0x0a1e, 0x0a16, 0x0a0e, 0x0a06, 0x09fe, 0x09f6, 0x09ef, 0x09e7, 0x09e0, 0x09d8, 0x09d1, 0x09c9, 0x09c2, 0x09bb, 0x09b4, 0x09ad, 0x09a5, 0x099e, 0x0998, 0x0991, 0x098a, 0x0983, 0x097c, 0x0976, 0x096f, 0x0969, 0x0962, 0x095c, 0x0955, 0x094f, 0x0949, 0x0943, 0x093c, 0x0936, 0x0930, 0x092a, 0x0924, 0x091e, 0x0918, 0x0912, 0x090d, 0x0907, 0x0901, 0x08fb, 0x08f6, 0x08f0, 0x08eb, 0x08e5, 0x08e0, 0x08da, 0x08d5, 0x08cf, 0x08ca, 0x08c5, 0x08bf, 0x08ba, 0x08b5, 0x08b0, 0x08ab, 0x08a6, 0x08a1, 0x089c, 0x0897, 0x0892, 0x088d, 0x0888, 0x0883, 0x087e, 0x087a, 0x0875, 0x0870, 0x086b, 0x0867, 0x0862, 0x085e, 0x0859, 0x0855, 0x0850, 0x084c, 0x0847, 0x0843, 0x083e, 0x083a, 0x0836, 0x0831, 0x082d, 0x0829, 0x0824, 0x0820, 0x081c, 0x0818, 0x0814, 0x0810, 0x080c, 0x0808, 0x0804, }; /* ─── Full normalize (all 4 stages inline) ─── * Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav). * * Parameterized by caller-provided scratch base + src/dst offsets. * The caller passes r_src_offset and r_dst_offset as compile-time constants * (typically derived from O_ macros in the caller's struct schema, e.g., `O_(CallerBundleScratch, fwd)`). * * This design lets any caller (with a scratch base + struct schema) use `normalize_v3s4_proc` * without putting magic offsets in the C-side bundle helper — the offsets come from O_ macros at the call site. * * Body uses 9 GPRs (r_src_ptr..r_branch_tmp): * r_src_ptr, r_dst_ptr : src/dst pointers (computed from r_scratch + caller offsets) * r_tmp : src.x PRESERVED across stages 1-2 (NOT clobbered by mfc2 MAC2) → fed to IR1 in stage 4 * r_mac1_scratch : MAC1 result scratch (also holds aligned |v|² in stage 3) * r_mac2_scratch : MAC2 result scratch → result.x after stage 4 sra * r_recip_est : src.y PRESERVED across stages 1-2 → fed to IR2 in stage 4 → result.y * r_lzcr : |v|² sum (stage 2) → shift count (stage 3) → 1/|v| (stage 4 IR0) * r_shift : shift count SAVED in stage 3 → consumed by stage 4 srav * r_branch_tmp : src.z PRESERVED across stages 1-2 → fed to IR3 in stage 4 → result.z (also sqrtbl base addr) * * Atom_labels are srav_path / aligned_done * (NOT namespaced — they're internal to this proc; * the metaprogram's per-atom-name enum emission handles any collision across different atoms/files that share the same labels). * * Pool cost: 11 GPRs (well within the 9-10 caller-trash GPR budget when r_scratch is a wave-context carrier). * * Direct port of PSYQ libgte msc02.rel.text VectorNormal disassembly (0x800160a0..0x8001615c). * Words: ~59 (matches libgte 0x800160a0..0x8001615c at +/- 0-2 words for BD-slot reshuffling). * Sqrtbl: hardcoded to 0x800185B4 (libgte msc02.rel.data). Note: swapped to local. * Pipeline: clobbers IR0..3, MAC1..3, LZCS, LZCR. */ /* MipsAtom_Proc_ wrapper: declares the static MipsCode[] body, then calls atombuilder_unroll(ab, ...) to copy the encoded instructions into the caller's MipsAtomBuilder arena. */ internal MipsAtom* normalize_v3s4_proc(AtomArena_R aa, U4 r_scratch /* GPR code: scratch base carrier (e.g., R_T4 = R_ResolveScratch) */ , U4 r_src_offset, U4 r_dst_offset /* GPR codes: PARAMETERIZED offsets (caller passes O_ macros) */ , U4 r_src_ptr, U4 r_dst_ptr, U4 r_tmp /* GPR codes: 3 scratch regs (src/dst computed + tmp) */ , U4 r_mac1_scratch, U4 r_mac2_scratch /* GPR codes: 2 more: MAC1/MAC2 scratch */ , U4 r_recip_est /* GPR code: |v|² sum + shift-input + sqrtbl[index] */ , U4 r_lzcr, U4 r_shift /* GPR codes: lzcr + final srav amount */ , U4 r_branch_tmp /* GPR code: scratch (shift count, branch target, lookup addr) */ ) MipsAtom_Proc_(normalize_v3s4, aa, { add_si(r_src_ptr, r_scratch, r_src_offset), /* r_src_ptr = &src */ add_si(r_dst_ptr, r_scratch, r_dst_offset), /* r_dst_ptr = &dst */ nop, /* Load src.x/y/z from r_src_ptr (caller-determined address) into r_tmp/r_recip_est/r_branch_tmp. * r_tmp holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below). */ load_word(r_tmp, r_src_ptr, O_(V3_S4,x)), load_word(r_recip_est, r_src_ptr, O_(V3_S4,y)), load_word(r_branch_tmp, r_src_ptr, O_(V3_S4,z)), nop, /* load-delay */ /* Stage 1: mtc2 src → IR1/2/3, SQR fires. */ gte_mv_to_data_r(r_tmp, C2_IR1), gte_mv_to_data_r(r_recip_est, C2_IR2), gte_mv_to_data_r(r_branch_tmp, C2_IR3), nop, gte_cmdw_sqr, /* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. */ gte_mv_from_data_r(r_mac1_scratch, C2_MAC1), gte_mv_from_data_r(r_mac2_scratch, C2_MAC2), gte_mv_from_data_r(r_lzcr, C2_MAC3), nop, add_u(r_lzcr, r_lzcr, r_mac2_scratch), add_u(r_lzcr, r_lzcr, r_mac1_scratch), gte_mv_to_data_r(r_lzcr, C2_LZCS), nop2, gte_mv_from_data_r(r_shift, C2_LZCR), nop, /* Stage 3: compute srav amount (r_lzcr) + align |v|² to bit 24. * IMPORTANT: the sllv/srav below writes the aligned |v|² to r_mac1_scratch (NOT r_lzcr), * so r_lzcr retains the shift count all the way to the start of stage 4. */ and_i( r_shift, r_shift, -2), or_u(r_mac1_scratch, r_lzcr, 0), /* FIX B: save sum before clobbering r_lzcr with shift count */ li_s( r_lzcr, 31), sub_s( r_lzcr, r_lzcr, r_shift), shift_aright(r_lzcr, r_lzcr, 1), /* r_branch_tmp = LZCR - 24 (overwrites r_branch_tmp; src.z no longer needed after SQR) */ add_si( r_branch_tmp, r_shift, -24), branch_lt_zero(r_branch_tmp, atom_offset(aligned_done, srav_path)), nop, /* FIX A: bltz → srav_path (LZCR<24 path) */ jump_rel(atom_offset(srav_path, aligned_done)), /* FIX A: b → aligned_done (LZCR>=24 path) */ shift_lleft_var(r_mac1_scratch, r_mac1_scratch, r_branch_tmp), /* FIX B: src=sum (r_mac1_scratch), dst=same */ atom_label(srav_path) li_s( r_branch_tmp, 24), sub_s( r_branch_tmp, r_branch_tmp, r_shift), shift_aright_var(r_mac1_scratch, r_mac1_scratch, r_branch_tmp), /* FIX B: src=sum (r_mac1_scratch), dst=same */ atom_label(aligned_done) /* Save the shift count to r_shift before the next 5 instructions overwrite r_lzcr * (the sqrtbl lookup loads 1/|v| into r_lzcr, which becomes IR0 in stage 4). */ or_u(r_shift, r_lzcr, 0), /* r_shift ← shift count (preserved through stage 4) */ /* r_mac1_scratch holds |v|² aligned (top bit at bit 7). */ add_si( r_mac1_scratch, r_mac1_scratch, -64), shift_lleft(r_mac1_scratch, r_mac1_scratch, 1), load_upper_i(r_branch_tmp, u4_hi(& gte_normalize_sqr_tbl)), or_i_self( r_branch_tmp, u4_lo(& gte_normalize_sqr_tbl)), add_u(r_branch_tmp, r_branch_tmp, r_mac1_scratch), load_half(r_lzcr, r_branch_tmp, 0), nop, /* r_lzcr = sqrtbl[aligned-64] = 1/|v| (IR0 in stage 4) */ /* FIX bug C: r_branch_tmp held the sqrtbl base+index, NOT src.z. Reload src.z from scratch now that r_branch_tmp is free. */ load_word(r_branch_tmp, r_src_ptr, O_(V3_S4,z)), nop, /* r_branch_tmp = src.z (for IR3 in stage 4) */ /* Stage 4: GPF + srav finalize (r_shift = shift count, r_lzcr = 1/|v|). */ gte_mv_to_data_r(r_lzcr, C2_IR0), gte_mv_to_data_r(r_tmp, C2_IR1), /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */ gte_mv_to_data_r(r_recip_est, C2_IR2), gte_mv_to_data_r(r_branch_tmp, C2_IR3), /* IR3 = src.z (reloaded) */ nop2, gte_cmdw_gpf, gte_mv_from_data_r(r_mac2_scratch, C2_MAC1), gte_mv_from_data_r(r_recip_est, C2_MAC2), gte_mv_from_data_r(r_branch_tmp, C2_MAC3), shift_aright_var(r_mac2_scratch, r_mac2_scratch, r_shift), /* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */ shift_aright_var(r_recip_est, r_recip_est, r_shift), shift_aright_var(r_branch_tmp, r_branch_tmp, r_shift), /* Store result.x/y/z to r_dst_ptr (caller-determined dst address). */ store_word(r_mac2_scratch, r_dst_ptr, O_(V3_S4,x)), store_word(r_recip_est, r_dst_ptr, O_(V3_S4,y)), store_word(r_branch_tmp, r_dst_ptr, O_(V3_S4,z)), mac_yield() }) #pragma endregion Atom Procs #pragma region Baked Atoms typedef Struct_(Binds_SetGteMT3S2S4) { MT3_S2S4* transform; }; internal MipsAtom_(set_gte_mt3s2s4) atom_info( atom_bind(Binds_SetGteMT3S2S4) , atom_reads(R_TapePtr) ){ /* Pop matrix address from tape into R_T3 ($11) */ load_word(R_T3, R_TapePtr, O_(Binds_SetGteMT3S2S4,transform)), add_ui_self( R_TapePtr, S_(Binds_SetGteMT3S2S4)), /* Load 3x3 Rotation + 3x1 Translation from R_T3 into GTE CONTROL Regs (ctc2) */ load_word(R_T0, R_T3, 0), load_word(R_T1, R_T3, 4), gte_mv_to_ctrl_r(R_T0, gte_cr_RT11), gte_mv_to_ctrl_r(R_T1, gte_cr_RT12), load_word(R_T0, R_T3, 8), load_word(R_T1, R_T3, 12), load_word(R_T2, R_T3, 16), gte_mv_to_ctrl_r(R_T0, gte_cr_RT13), gte_mv_to_ctrl_r(R_T1, gte_cr_RT21), gte_mv_to_ctrl_r(R_T2, gte_cr_RT22), load_word(R_T0, R_T3, 20), load_word(R_T1, R_T3, 24), load_word(R_T2, R_T3, 28), gte_mv_to_ctrl_r(R_T0, gte_cr_TRX), gte_mv_to_ctrl_r(R_T1, gte_cr_TRY), gte_mv_to_ctrl_r(R_T2, gte_cr_TRZ), mac_yield() }; #pragma endregion Baked Atoms