#ifdef INTELLISENSE_DIRECTIVES # include "gen/macs.h" # include "gen/offsets.h" # include "gte.h" # include "gp.h" # include "tape.h" # include "math.atom.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_(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)), }) FI_ Slice_MipsCode ac_gte_mv_to_cr_diag_v3s4(AtomBuilder_R ab, Reg_(V3_S4) v) MipsAtomComp_Proc_(ab, { gte_mv_to_ctrl_r(v.y, gte_cr_RT13), gte_mv_to_ctrl_r(v.z, gte_cr_RT22), gte_mv_to_ctrl_r(v.x, gte_cr_RT11), }) FI_ Slice_MipsCode ac_gte_ld_ir123_v3s4(AtomBuilder_R ab, Reg_(V3_S4) v) MipsAtomComp_Proc_(ab, { gte_mv_to_data_r(v.x, C2_IR1), gte_mv_to_data_r(v.y, C2_IR2), gte_mv_to_data_r(v.z, C2_IR3), }) /* ─── GTE OP cross product (a × b → a) ─── * Sets up RT diagonal from a.xyz, IR1/2/3 from b.xyz, fires OP, * reads MAC1/2/3, shifts right 12 (S12.20 → S12.0 OuterProduct12), writes back to a.xyz. * Composes the three sub-primitives (RT-load, IR-load, OP, MAC-read, shift) * into one component for use by atoms that need the cross product inline. * * Output gpr (a) aliases source-A gpr; MAC read clobbers source-A's load targets, * but by that point the RT load is complete and source A is dead. * Pipeline: clobbers IR1..3, MAC1..3, RT11..33. * * The CPU→COP2 transfer chains (3 ctc2, 3 mtc2) require a 2-slot retirement gap, * and the MFC2→GPR chain (3 mfc2) requires a 1-slot retirement gap, before the GPR can be read. * The hazard nops are inlined below — same convention as ac_gte_gpf_scale — so any atom body inlining this component inherits them. * * Words: 18 (3 ctc2 + 2 nop + 3 mtc2 + 2 nop + 1 op + 3 mfc2 + 1 nop + 3 sra). */ FI_ Slice_MipsCode ac_gte_op_cross_v3s4(AtomBuilder_R ab, Reg_(V3_S4) a, Reg_(V3_S4) b) atom_dbg_skip MipsAtomComp_Proc_(ab, { mac_gte_mv_to_cr_diag_v3s4(a), GteDelay_ /* RT diagonal: D1 = a.x, D2 = a.y, D3 = a.z */ mac_gte_ld_ir123_v3s4(b), GteDelay_ /* IR: second operand (b.xyz) */ gte_cmdw_cross, /* OP: MAC1/2/3 = a × b (S12.20) */ mac_gte_mv_from_mac123_v3s4(a), GteDelay_ /* Read MAC1/2/3 → a.xyz (overwrites source-A's load targets) */ mac_shift_aright_v3s4_self(a, 12), /* Right-shift MAC by 12 (S12.20 → S12.0 OuterProduct12) */ }) /* 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_(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, Reg vbase, Reg v0, Reg v1, Reg v2) atom_dbg_skip MipsAtomComp_Proc_(ab, { shift_lleft(R_AT, v0, v3s2_byteoff), add_u_self(R_AT, vbase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), LdSlot_ gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0), shift_lleft(R_AT, v1, v3s2_byteoff), add_u_self(R_AT, vbase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), LdSlot_ gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1), shift_lleft(R_AT, v2, v3s2_byteoff), add_u_self(R_AT, vbase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), LdSlot_ 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, Reg r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(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_(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. */ 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_(ab, { mac_gte_sqr_v3s4(r_sx, r_sy, r_sz, nop), 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), }) /* ─── SQR FIRE — mtc2 3 GPRs into IR1/IR2/IR3, then fire SQR. ─── */ FI_ Slice_MipsCode ac_gte_sqr_v3s4(AtomBuilder_R ab, Reg sx, Reg sy, Reg sz, MipsCode delay_slot) atom_dbg_skip MipsAtomComp_Proc_(ab, { gte_mv_to_data_r(sx, C2_IR1), gte_mv_to_data_r(sy, C2_IR2), gte_mv_to_data_r(sz, C2_IR3), delay_slot, gte_cmdw_sqr, }) /* ─── 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_(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), GteDelay_ 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), }) /* ─── 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_mt3s3s4(AtomBuilder_R ab , U4 r_mtx, U4 r_off , U4 r_t0, U4 r_t1, U4 r_t2 ) MipsAtomComp_Proc_(ab, { load_word( r_t0, r_off, O_(V3_S4,x)), load_word( r_t1, r_off, O_(V3_S4,y)), load_word( r_t2, r_off, O_(V3_S4,z)), store_word(r_t0, r_mtx, O_(MT3_S2S4,t[0])), store_word(r_t1, r_mtx, O_(MT3_S2S4,t[1])), store_word(r_t2, r_mtx, O_(MT3_S2S4,t[2])), }) /* ─── LZCR ROUND EVEN + HALF-SHIFT ─── * Takes the raw LZCR leading-zero/ones count (from mfc2 C2_LZCR, range 1..32 per PSX-SPX cop2r31) and the |v|² sum (in r_mag_sq from the MAC1+MAC2+MAC3 add). * Produces: * r_shift ← LZCR rounded down to even (clear bit 0) * r_mag_sq_copy ← |v|² sum (moved out of r_mag_sq before it's overwritten) * r_mag_sq ← (31 - even_LZCR) / 2 = the final srav/GPF shift amount * * Rounding to even ensures (31 - LZCR) is always odd, so the >> 1 division is consistent — no 0.5 loss. * The caller branches on LZCR < 24 to decide left-shift vs right-shift of r_mag_sq_copy, then saves the shift count. * * Note: C2_LZCR (cop2r31) is a fixed read-only C2 data register — the caller must read it via mfc2 from C2_LZCR; * there is no register choice at the hardware level. Only the GPR that holds the result is caller-determined. */ FI_ Slice_MipsCode ac_lzcr_round_even_half_shift(AtomBuilder_R ab, Reg shift, Reg mag_sq, Reg mag_sq_copy) atom_dbg_skip MipsAtomComp_Proc_(ab, { and_i(shift, shift, gte_lzcr_even_mask), or_u(mag_sq_copy, mag_sq, 0), li_s( mag_sq, 31), sub_s( mag_sq, mag_sq, shift), shift_aright(mag_sq, mag_sq, 1), }) FI_ Slice_MipsCode ac_gte_general_purpose_interopolation(AtomBuilder_R ab , Reg to_ir0, Reg to_ir1, Reg to_ir2, Reg to_ir3 , Reg fr_mac1, Reg fr_mac2, Reg fr_mac3 , MipsCode nop_slot1, MipsCode nop_slot2) MipsAtomComp_Proc_(ab, { gte_mv_to_data_r(to_ir0, C2_IR0), gte_mv_to_data_r(to_ir1, 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(to_ir2, C2_IR2), gte_mv_to_data_r(to_ir3, C2_IR3), /* IR3 = src.z (reloaded) */ GteDelay_ nop_slot1, GteDelay_ nop_slot2, gte_cmdw_gpf, gte_mv_from_data_r(fr_mac1, C2_MAC1), gte_mv_from_data_r(fr_mac2, C2_MAC2), gte_mv_from_data_r(fr_mac3, C2_MAC3), }) FI_ Slice_MipsCode ac_gte_mv_from_data_r_mac123(AtomBuilder_R ab, Reg fr_mac1, Reg fr_mac2, Reg fr_mac3) MipsAtomComp_Proc_(ab, { gte_mv_from_data_r(fr_mac1, C2_MAC1), gte_mv_from_data_r(fr_mac2, C2_MAC2), gte_mv_from_data_r(fr_mac3, C2_MAC3), }) FI_ Slice_MipsCode ac_gte_mv_from_mac123_v3s4(AtomBuilder_R ab, Reg_(V3_S4) v) MipsAtomComp_ProcMap_(ab, mac_gte_mv_from_data_r_mac123(v.x, v.y, v.z)) #pragma endregion MACs (Mips Atom Components) #pragma region Atom Procs /* Normalize V3_S4 using the PSYQ/libgte reciprocal-sqrt method: * |v|² = x² + y² + z² * LZCR determines the exponent of |v|². * Round that exponent even and shift |v|² into [1, 4). * sqrtbl approximates 1/sqrt(mantissa). * GPF multiplies v by that reciprocal-sqrt mantissa. * srav_shift restores the exponent scale. * Effectively: v_normalized = v * (1 / sqrt(|v|²)). * * ─── Local port 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 x 16-bit signed, stored in 1.12 fixed point. * Data is identical to the libgte original (byte-for-byte verified). * * ─── Table semantics ─── * For table index i in [0, 192): * x = 1 + i / 64 * tbl[i] = floor(4096 / sqrt(x)) * Thus the table uniformly samples 1/sqrt(x) over: * x in [1.0, 4.0) * at steps of 1/64, with the result represented in 1.12 fixed point (0x1000 = 1.0). * * Representative entries: * [ 0] 0x1000 = 1.000000 ; 1 / sqrt(1.000000) * [ 16] 0x0e4f = 0.894287 ; 1 / sqrt(1.250000) * [ 32] 0x0d10 = 0.816406 ; 1 / sqrt(1.500000) * [ 48] 0x0c18 = 0.755859 ; 1 / sqrt(1.750000) * [ 64] 0x0b50 = 0.707031 ; 1 / sqrt(2.000000) * [128] 0x093c = 0.577148 ; 1 / sqrt(3.000000) * [191] 0x0804 = 0.500977 ; 1 / sqrt(3.984375) * * ─── How VectorNormal indexes it ─── * Let: * mag_sq = x*x + y*y + z*z * lzcr = leading-zero count of mag_sq * For a non-zero magnitude, libgte first rounds LZCR down to an even number: * lzcr_even = lzcr & ~1 * * It then shifts mag_sq so that its significant bits land in one of two * adjacent normalized ranges: * if lzcr_even >= 24: * aligned = mag_sq << (lzcr_even - 24) * else: * aligned = mag_sq >> (24 - lzcr_even) * * Because lzcr_even differs from the true LZCR by at most one bit: * raw LZCR even -> aligned in [0x80, 0x100) * raw LZCR odd -> aligned in [0x40, 0x080) * therefore: * aligned in [0x40, 0x100) * * Dividing this normalized integer by 64 gives exactly the table domain: * x = aligned / 64 * x in [1.0, 4.0) * * The lookup is therefore: * index = aligned - 0x40 * byte_offset = index * sizeof(S2) * inv_len = sqrtbl[index] * or equivalently, matching the libgte instructions: * addi aligned, -64 * sll aligned, 1 * lh inv_len, sqrtbl + aligned * * ─── Why the domain spans [1, 4) instead of [1, 2) ─── * Square-root scaling depends on the parity of the exponent. * Rounding LZCR to even absorbs exponent changes in pairs of bits, leaving the lookup mantissa normalized over a factor-of-four interval [1, 4). * * The corresponding exponent correction is retained separately as: * srav_shift = (31 - lzcr_even) >> 1 * * After GPF multiplies the original vector components by the table's reciprocal-square-root coefficient, * this shift restores the exponent scale and yields the normalized vector. * * Reproduced verbatim from libgte; also matches PSn00bSDK VectorNormalS _norm_table (24 rows x 8 halfwords, final entry 0x0804). **/ internal S2 const 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, }; typedef Struct_(Binds_normalize_v3s4) { U2 src_offset; U2 dst_offset; }; typedef Struct_(RegUse_normalize_v3s4) { union { Reg_(V3_S4) res, src; }; union { Reg r0, src_ptr, mac2; }; union { Reg r1, dst_ptr; }; union { Reg r2, dst_offset, mac1, v_sqr_aligned, sqrtbl_byte_offset; }; union { Reg r3, src_offset, align_delta, shift_count, sqrtbl_lookup; }; union { Reg r4, mac3, v_sqr_sum, srav_shift; }; union { Reg r5, lzcr_raw, lzcr_even, inv_len; }; }; /* ─── Full normalize (all 4 stages inline) ─── * Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav). */ internal MipsAtom* normalize_v3s4(AtomArena_R aa, RegUse_normalize_v3s4 r) MipsAtom_Proc_(aa, { load_half(r.src_offset, R_TapePtr, O_(Binds_normalize_v3s4, src_offset)), load_half(r.dst_offset, R_TapePtr, O_(Binds_normalize_v3s4, dst_offset)), LdSlot_ add_u(r.src_ptr, R_ScratchBase, r.src_offset), LdSlot_ add_u(r.dst_ptr, R_ScratchBase, r.dst_offset), LdSlot_ add_ui_self(R_TapePtr, S_(Binds_normalize_v3s4)), mac_load_v3s4(r.src, r.src_ptr, 0), /* Stage 1: mtc2 src → IR1/2/3, SQR fires. */ LdSlot_ mac_gte_sqr_v3s4(r.src.x, r.src.y, r.src.z, LdSlot_ nop), /* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. src_ptr is dead; reuse as mac2. */ mac_gte_mv_from_data_r_mac123(r.mac1, r.mac2, r.mac3), LdSlot_ nop, add_u_self( r.v_sqr_sum, r.mac1), add_u_self( r.v_sqr_sum, r.mac2), gte_mv_to_data_r( r.v_sqr_sum, C2_LZCS), GteDelay_ nop2, gte_mv_from_data_r(r.lzcr_raw, C2_LZCR), GteDelay_ nop, /* Stage 3: even(LZCR), half-shift, align |v|² to bit 24. */ mac_lzcr_round_even_half_shift(r.lzcr_raw, r.v_sqr_sum, r.v_sqr_aligned), add_si( r.align_delta, r.lzcr_even, -24), branch_lt_zero(r.align_delta, atom_offset(aligned_done, srav_path)), BdSlot_ nop, /* bltz → srav_path (LZCR < 24 path) */ jump_rel(atom_offset(srav_path, aligned_done)), /* b → aligned_done (LZCR >= 24 path) */ BdSlot_ shift_lleft_var(r.v_sqr_aligned, r.v_sqr_aligned, r.align_delta), atom_label(srav_path) li_s( r.shift_count, 24), sub_s(r.shift_count, r.shift_count, r.lzcr_even), shift_aright_var(r.v_sqr_aligned, r.v_sqr_aligned, r.shift_count), atom_label(aligned_done) add_si( r.sqrtbl_byte_offset, r.v_sqr_aligned, -64), shift_lleft(r.sqrtbl_byte_offset, r.sqrtbl_byte_offset, 1), mac_load_word_imm(r.sqrtbl_lookup, & gte_normalize_sqr_tbl), add_u_self(r.sqrtbl_lookup, r.sqrtbl_byte_offset), load_half(r.inv_len, r.sqrtbl_lookup, 0), LdSlot_ nop, mac_gte_general_purpose_interopolation(r.inv_len, r.src.x, r.src.y, r.src.z, r.res.x, r.res.y, r.res.z, GteDelay_ load_word(R_AtomJmp, R_TapePtr, 0), LdSlot_ // ac_yield: word 1 GteDelay_ add_ui_self( R_TapePtr, S_(MipsCode)) // ac_yield: word ), mac_shift_aright_var_v3s4_self(r.res, r.srav_shift), mac_store_v3s4(r.res, r.dst_ptr, 0), jump_reg(R_AtomJmp), BdSlot_ nop // ac_yield: word 3-4 }) /* ─── GTE OP cross product (a × b → out) ─── * Generalized V3_S4 cross product via GTE OP (OuterProduct12 libpsyx convention). * The >> 12 shift converts S12.20 → S12.0 OuterProduct12. */ typedef Struct_(Binds_gte_cross_v3s4) { V3_S4* src_a; V3_S4* src_b; V3_S4* out; }; typedef Struct_(RegUse_gte_cross_v3s4) { Reg_(V3_S4) a; Reg_(V3_S4) b; Reg out; Reg src_a; Reg src_b; }; internal MipsAtom* gte_cross_v3s4(AtomArena_R aa, RegUse_gte_cross_v3s4 r) atom_info(atom_bind(Binds_gte_cross_v3s4)) MipsAtom_Proc_(aa, { load_word(r.src_a, R_TapePtr, O_(Binds_gte_cross_v3s4,src_a)), load_word(r.src_b, R_TapePtr, O_(Binds_gte_cross_v3s4,src_b)), load_word(r.out, R_TapePtr, O_(Binds_gte_cross_v3s4,out)), LdSlot_ add_ui_self(R_TapePtr, S_(Binds_gte_cross_v3s4)), mac_load_v3s4(r.a, r.src_a, 0), LdSlot_ mac_load_v3s4(r.b, r.src_b, 0), LdSlot_ mac_gte_op_cross_v3s4(r.a, r.b), /* RT diagonal + IR + OP + MAC read + shift */ mac_store_v3s4(r.a, r.out, 0), 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