From 5a4bfb12244a59e9ebc046823b0dab00f680db9b Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 17 Aug 2026 18:18:18 -0400 Subject: [PATCH] Collapse of atom 6-9 into a single atom (finaly). Generalized cross product atom proc and atom component. Still working on normalize_v3s4. --- code/duffle/gen/macs.h | 12 +- code/duffle/gen/offsets.h | 2 +- code/duffle/gte.atom.c | 93 +++++++----- code/duffle/lottes_tape.h | 22 +-- code/duffle/memory.h | 10 ++ code/hello_camera/hello_camera.atom.c | 183 +++++++++++----------- code/hello_camera/hello_camera.c | 209 +++++++++++--------------- 7 files changed, 260 insertions(+), 271 deletions(-) diff --git a/code/duffle/gen/macs.h b/code/duffle/gen/macs.h index 034cc21..5a86e1d 100644 --- a/code/duffle/gen/macs.h +++ b/code/duffle/gen/macs.h @@ -35,15 +35,11 @@ * These do NOT yield. They are expanded inline inside Tape Atoms. * ---------------------------------------------------------------------------*/ // The 'Yield' sequence for Tape Atoms (mac_yield). -// - mac_yield() is the safe default for atom-endings: 4 words, BD-slot of jr is mandatory nop. -// - mac_yield_load() + mac_yield_tail(): -// - unconditional branch: mac_yield_load fills the branch's BD-slot (replaces a nop); -// - mac_yield_tail runs at the branch target (does NOT re-load R_AtomJmp). #define mac_yield(...) \ load_word(R_AtomJmp, R_TapePtr, 0) \ , add_ui_self( R_TapePtr, S_(MipsCode)) \ , jump_reg( R_AtomJmp) \ -, nop +, BdSlot_ nop WORD_COUNT(mac_yield, 4) /* atom_dbg_skip */ @@ -54,8 +50,8 @@ WORD_COUNT(mac_yield_load, 1) /* atom_dbg_skip */ #define mac_yield_tail(...) \ add_ui_self(R_TapePtr, S_(MipsCode)) \ -, jump_reg( R_AtomJmp) \ -, nop +, jump_reg( R_AtomJmp) \ +, BdSlot_ nop WORD_COUNT(mac_yield_tail, 3) /* atom_dbg_skip */ @@ -217,7 +213,7 @@ WORD_COUNT(mac_gte_ld_ir123_v3s4, 3) 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) */ \ +, 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) */ diff --git a/code/duffle/gen/offsets.h b/code/duffle/gen/offsets.h index c55fcdf..45b603e 100644 --- a/code/duffle/gen/offsets.h +++ b/code/duffle/gen/offsets.h @@ -33,7 +33,7 @@ enum { atom_offset_example_atom_proc_skip = _atom_offset_example_atom_proc_skip, }; -// --- atom: build_normalize_v3s4 (61 words) --- +// --- atom: build_normalize_v3s4 (67 words) --- #define _atom_offset_aligned_done_srav_path 3 #define _atom_offset_srav_path_aligned_done 4 diff --git a/code/duffle/gte.atom.c b/code/duffle/gte.atom.c index 81e2dc6..ea1923f 100644 --- a/code/duffle/gte.atom.c +++ b/code/duffle/gte.atom.c @@ -30,10 +30,26 @@ FI_ Slice_MipsCode ac_gte_ld_ir123_v3s4(AtomBuilder_R ab, Reg_(V3_S4) v) MipsAto 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) */ + 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) */ }) @@ -226,7 +242,8 @@ FI_ Slice_MipsCode ac_gte_mv_from_mac123_v3s4(AtomBuilder_R ab, Reg_(V3_S4) v) M * 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). */ + * Reproduced verbatim from libgte (verified against libpsn00b/psxgte/vector.s:100-123 — 24 rows × 8 halfwords, last 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, @@ -254,39 +271,40 @@ internal S2 const gte_normalize_sqr_tbl[192] align_(2) = { 0x0820, 0x081c, 0x0818, 0x0814, 0x0810, 0x080c, 0x0808, 0x0804, }; -typedef Struct_(Binds_build_normalize_v3s4) { - U4 scratch; - U2 src_offset; - U2 dst_offset; +typedef Struct_(Binds_NormalizeV3S4) { + U2 src_offset; /* offset of src V3_S4 within the BIOS scratchpad */ + U2 dst_offset; /* offset of dst V3_S4 within the BIOS scratchpad */ }; typedef Struct_(RegUse_build_normalize_v3s4) { - Reg scratch; + Reg scratch; /* scratchpad base; loaded via load_word_imm below. */ Reg src_ptr; Reg dst_ptr; - Reg recip_est; // |v|² sum + shift-input + sqrtbl[index] + Reg recip_est; /* |v|² sum + shift-input + sqrtbl[index] */ Reg norm; Reg shift; Reg src_x; - union { Reg mac1_scratch; } t3; + union { Reg mac1_scratch, dst_offset; } t3; union { Reg mac2_scratch; } t4; - union { Reg btarget, shift_count, lookup_addr, src_z; } t5; + union { Reg btarget, shift_count, lookup_addr, src_z, src_offset; } t5; }; /* ─── Full normalize (all 4 stages inline) ─── - * Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav). - * - * 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. - */ -internal MipsAtom* build_normalize_v3s4(AtomArena_R aa, U2 src_offset, U2 dst_offset, RegUse_build_normalize_v3s4 r) + * Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav). */ +internal MipsAtom* build_normalize_v3s4(AtomArena_R aa, RegUse_build_normalize_v3s4 r) MipsAtom_Proc_(aa, { - // load_word(r.scratch, R_TapePtr, O_(Binds_build_normalize_v3s4,scratch)), - // add_ui_self(R_TapePtr, S_(Binds_build_normalize_v3s4)), - - add_si(r.src_ptr, r.scratch, src_offset), /* r_src_ptr = &src */ + /* Load scratch base via immediate (always Scratchpad_Loc = 0x1F800000 — the BIOS + * scratchpad, aliased by every consumer's ResolveLookAtScratch struct). */ + mac_load_word_imm(r.scratch, Scratchpad_Loc), + /* Tape pop: src_offset, dst_offset = 4 bytes (packed into 1 U4: low16=src, high16=dst). + * Loads back-to-back fill each other's load-delay slots; the subsequent add_u + * (2 cycles after the matching load) sees a valid value. */ + load_half(r.t5.src_offset, R_TapePtr, O_(Binds_NormalizeV3S4, src_offset)), + load_half(r.t3.dst_offset, R_TapePtr, O_(Binds_NormalizeV3S4, dst_offset)), + LdSlot_ add_u(r.src_ptr, r.scratch, r.t5.src_offset), + LdSlot_ add_u(r.dst_ptr, r.scratch, r.t3.dst_offset), + LdSlot_ add_ui_self(R_TapePtr, S_(Binds_NormalizeV3S4)), /* Load src.x/y/z from r_src_ptr (caller-determined address) into r_tmp/r_recip_est/r_branch_tmp. - * r.rt1_src_x holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below). */ + * r.rt1_src_x holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below). + * t5.src_offset/dst_offset are dead by here; t5 is reused for src.z in the mac_load_word_v3 below. */ mac_load_word_v3(r.src_x, r.recip_est, r.t5.src_z, r.src_ptr, 0), /* Stage 1: mtc2 src → IR1/2/3, SQR fires. */ @@ -326,12 +344,12 @@ MipsAtom_Proc_(aa, { /* Stage 4: GPF + srav finalize (r_shift = shift count, r_norm = 1/|v|). */ LdSlot_ mac_gte_general_purpose_interopolation( - r.norm, + r.norm, r.src_x, /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */ - r.recip_est, + r.recip_est, r.t5.src_z, /* IR3 = src.z (reloaded) */ r.t4.mac2_scratch, r.recip_est, r.t5.src_z, - GteDelay_ add_si(r.dst_ptr, r.scratch, dst_offset), // pre-laoding destination to register here. + GteDelay_ nop, GteDelay_ nop ), /* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */ @@ -342,25 +360,28 @@ MipsAtom_Proc_(aa, { mac_yield() }) +/* ─── 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; - union { Reg t0, out; }; - union { Reg t1, src_a, rt11; }; - union { Reg t2, src_b, rt22; }; + union { Reg out, t0; } x; + union { Reg src_a, t1, rt11; } y; + union { Reg src_b, t2, rt22; } z; }; 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)), + load_word(r.y.src_a, R_TapePtr, O_(Binds_gte_cross_v3s4,src_a)), + load_word(r.z.src_b, R_TapePtr, O_(Binds_gte_cross_v3s4,src_b)), + load_word(r.x.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 (one component call). */ - mac_store_v3s4(r.a, r.out, 0), + mac_load_v3s4(r.a, r.y.src_a, 0), LdSlot_ + mac_load_v3s4(r.b, r.z.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.x.out, 0), mac_yield() }) diff --git a/code/duffle/lottes_tape.h b/code/duffle/lottes_tape.h index 01ddd6f..ac56ded 100644 --- a/code/duffle/lottes_tape.h +++ b/code/duffle/lottes_tape.h @@ -82,7 +82,10 @@ enum { // - R_T8: Will be used as the atom jump register. // All allocatable registers for mips atoms: + + // TODO(Ed): Make this the R_AtomJmp register since its better to clobber across atoms. R_TScratchVolatile = R_AT, // This one is reserved for psuedo instructions, but you can technically use it. + R_TScratch0 = R_T0, R_TScratch1 = R_T1, R_TScratch2 = R_T2, @@ -91,7 +94,7 @@ enum { R_TScratch5 = R_T5, R_TScratch6 = R_T6, R_TScratch7 = R_T7, - R_TScratch8 = R_T8, + R_TScratch8 = R_T8, // Clobbered by the yield on a per-atom boundary. R_TScratch10 = R_V0, // Tend to be used with gte DMAs R_TScratch11 = R_V1, // Tend to be used with gte DMAs // Note(Ed): We can technically clobber these, but don't unless we hit a bottleneck. @@ -167,8 +170,8 @@ FI_ void tape_run(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape asm_words( load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ , add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */ - , call_reg( R_AtomJmp) /* jalr $t9 */ - , nop /* Branch delay slot */ + , call_reg( R_AtomJmp) /* jalr $t8 */ + , BdSlot_ nop /* Branch delay slot */ ) asm_rpins, r_use(tape_ptr) asm_clobber: @@ -184,8 +187,8 @@ FI_ void tape_run_a02_s07(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u asm_words( load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ , add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */ - , call_reg( R_AtomJmp) /* jalr $t9 */ - , nop /* Branch delay slot */ + , call_reg( R_AtomJmp) /* jalr $t8 */ + , BdSlot_ nop /* Branch delay slot */ ) asm_rpins, r_use(tape_ptr) asm_clobber: @@ -226,15 +229,11 @@ FI_ void tb_scope_run_end(TapeBuilder* tb) { tb_emit(tb,tape_exit); tape_run(tb_ * ---------------------------------------------------------------------------*/ // The 'Yield' sequence for Tape Atoms (mac_yield). -// - mac_yield() is the safe default for atom-endings: 4 words, BD-slot of jr is mandatory nop. -// - mac_yield_load() + mac_yield_tail(): -// - unconditional branch: mac_yield_load fills the branch's BD-slot (replaces a nop); -// - mac_yield_tail runs at the branch target (does NOT re-load R_AtomJmp). atom_dbg_skip MipsAtomComp_(ac_yield) { load_word(R_AtomJmp, R_TapePtr, 0), add_ui_self( R_TapePtr, S_(MipsCode)), - jump_reg( R_AtomJmp), nop, + jump_reg( R_AtomJmp), BdSlot_ nop, }; atom_dbg_skip MipsAtomComp_(ac_yield_load) { @@ -243,7 +242,8 @@ atom_dbg_skip MipsAtomComp_(ac_yield_load) { atom_dbg_skip MipsAtomComp_(ac_yield_tail) { add_ui_self(R_TapePtr, S_(MipsCode)), - jump_reg( R_AtomJmp), nop, + jump_reg( R_AtomJmp), + BdSlot_ nop, }; #pragma endregion Macro Atom Components diff --git a/code/duffle/memory.h b/code/duffle/memory.h index 023bb82..badd008 100644 --- a/code/duffle/memory.h +++ b/code/duffle/memory.h @@ -131,3 +131,13 @@ FI_ U4 farena_unused_start(FArena arena) { return arena.start + arena.used; } #define farena_push_array(arena, type, amount, ...) (tmpl(Slice,type)){ C_(type*, farena_push((arena), (amount), opt_(farena, .type_width=S_(type), __VA_ARGS__)).ptr), (amount) } #pragma endregion FArena + +#pragma region BIOS Scratchpad +/* BIOS scratchpad location. 1 KB at 0x1F800000. + * The PS1 BIOS A-functions use this region for inter-call communication and as a temp storage area. + * The Tape runtime uses scratch region along with explicit data structures instead of the stack-based scratch from the C-Runtime. */ +enum { + Scratchpad_Loc = 0x1F800000, +}; +#define C_scratch(type) C_(type, Scratchpad_Loc) +#pragma endregion BIOS Scratchpad diff --git a/code/hello_camera/hello_camera.atom.c b/code/hello_camera/hello_camera.atom.c index 9e021da..882a3d3 100644 --- a/code/hello_camera/hello_camera.atom.c +++ b/code/hello_camera/hello_camera.atom.c @@ -108,10 +108,7 @@ typedef AtomBundle_(resolve_look_at) { MipsAtom* normalize_right_ux, cross_to_up, normalize_up_uy, - populate, - set_gte_mt3s2s4, - matrix_vector, - trans_matrix; + pop_mv_trans; }; enum { @@ -179,108 +176,114 @@ atom_info(atom_bind(Binds_ResolveLookAtSub)) MipsAtom_Proc_(aa, { }) -typedef Struct_(Binds_ResolveLookAtPopAndTrans) { - U4 look_at; /* U4 (MT3_S2S4* — destination matrix address) */ +typedef Struct_(Binds_ResolveLookAtPopMvTrans) { + U4 look_at; /* MT3_S2S4* — destination matrix address */ }; -typedef Struct_(RegUse_resolve_look_at__populate_proc) { - Reg const scratch; +typedef Struct_(RegUse_resolve_look_at__pop_mv_trans) { + Reg scratch; /* loaded via load_word_imm below — can't rely on + * R_T4 surviving across the tape_run boundary */ Reg look_at; - Reg_(V3_S4) row; /* one matrix row, reused */ - Reg ux; - Reg uy; - Reg uz; + Reg_(V3_S4) row; /* populate phase: load ux/uy/uz */ + union { Reg ux, v_x; } t6; /* populate addr (canonical) → matrix_vector v_x */ + union { Reg uy, v_y; } t7; /* populate uy → matrix_vector v_y */ + union { Reg uz, v_z; } t8; /* populate uz → matrix_vector v_z */ + Reg eye; /* matrix_vector phase: load -eye */ }; -/* Atom 6a: write look_at->m[][] from ux/uy/uz as S2. Zero t[]. +/* Atom 6 (fused): write look_at->m[][] from ux/uy/uz as packed S2 (populate), + * ctc2 RT chain into C2[0..4] (matrix_vector), MVMVA RT*(-eye)>>12, store off + * directly to look_at->t[] (trans_matrix). Replaces the previous 3 separate atoms + * (populate + matrix_vector + trans_matrix). + * * MT3_S2S4 { A3x3_S2 m; A3_S4 t; } * m[][] is S2 packed (9 × 2 = 18 bytes at offset 0) - * t[0/1/2] is S4 (3 × 4 = 12 bytes at offset 18) - */ -internal MipsAtom* resolve_look_at__populate_proc(AtomArena_R aa, - RegUse_resolve_look_at__populate_proc r -) MipsAtom_Proc_(aa, { - load_word(r.look_at, R_TapePtr, O_(Binds_ResolveLookAtPopAndTrans,look_at)), - LdSlot_ add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtPopAndTrans)), - - add_si(r.ux, r.scratch, O_(ResolveLookAtScratch,ux)), /* r.ux = &ux */ LdSlot_ - add_si(r.uy, r.scratch, O_(ResolveLookAtScratch,uy)), /* r.uy = &uy */ LdSlot_ - add_si(r.uz, r.scratch, O_(ResolveLookAtScratch,uz)), /* r.uz = &uz */ LdSlot_ - - mac_load_v3s4(r.row, r.ux, 0), LdSlot_ mac_store_v3s2(r.row, r.look_at, O_(MT3_S2S4,m[0])), - mac_load_v3s4(r.row, r.uy, 0), LdSlot_ mac_store_v3s2(r.row, r.look_at, O_(MT3_S2S4,m[1])), - mac_load_v3s4(r.row, r.uz, 0), LdSlot_ mac_store_v3s2(r.row, r.look_at, O_(MT3_S2S4,m[2])), - - /* Zero t[0..2] — atom 6c writes the final values here. */ - mac_store_v3s4(v3s4_R_0(), r.look_at, O_(MT3_S2S4,t)), - mac_yield() -}) - -typedef Struct_(RegUse_resolve_look_at__matrix_vector_proc) { - Reg const scratch; - Reg look_at; - Reg eye; /* &scratch.eye; store dest for off */ - Reg_(V3_S4) v; /* RT words, then -eye, then off */ -}; -/* Atom 6b: off = look_at.m * (-eye) >> 12. Stores off over scratch.eye. + * t[0..2] is S4 (3 × 4 = 12 bytes at offset 18) * - * C11 ApplyMatrixLV: + * C11 ApplyMatrixLV semantics (gte.atom.c ac_apply_matrix_lv; libgte reference): * 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 + * 2. lw -eye from memory + * 3. S15 decomposition (eliminated here — the fused body takes the >>12 path + * directly via mtc2 IR + MVMVA pass2, matching the libgte canonical output) + * 4. mtc2 to IR1/2/3, nop2, MVMVA pass2 (sf=1, mx=0, v=3, cv=3) + * 5. mfc2 MACs → off + * 6. store off to look_at->t[] (skip scratch.eye intermediate) + * + * GPR codes (assigned by resolve_look_at_init): + * r_scratch : R_ResolveScratch (R_T4 carrier) + * r_look_at : ralloc() — also serves as the off-dst in the trans_matrix phase + * r_row : V3_S4, reused for ux/uy/uz loads in populate phase + * r_eye : ralloc() — &scratch.eye, used for -eye load in matrix_vector phase + * r_v_x/v_y/v_z : ralloc() — populate scratch addrs (ux/uy/uz), reused as + * ctc2 transfer + MVMVA -eye temp in matrix_vector phase + * (v_x/v_y/v_z alias ux/uy/uz via the union; lifetime ends for ux/uy/uz after + * populate's mac_load_v3s4, so reusing for v.x/v.y/v.z is safe) + * Pool cost: 1 carrier + 1 look_at + 3 row + 1 eye + 3 aliased = 9 GPRs + * + * Net word savings vs the previous 3-atom flow: ~15 words + 2 mac_yields + 1 tape pop. + * - 2 mac_yields (trans_matrix's + matrix_vector's) → fused into one yield + * - 1 redundant tb_data (look_at was pushed 2x; now once) + * - mac_trans_mt3s3s4 (6 words) → replaced by direct mac_store_v3s4 + * - mac_store_v3s4 to scratch.eye (3 words intermediate) → eliminated + * - add_si for r_off_ptr (2 words) → eliminated + * - mac_store_v3s4 zero-store of t[] (3 words) → eliminated (matrix_vector writes + * off directly; no consumer needed the zero first) + * - 1 set_gte_mt3s2s4 ctc2 chain (13 baked words) → eliminated (matrix_vector + * has its own ctc2 RT chain; cube rendering atoms reload C2 state themselves) */ -internal MipsAtom* resolve_look_at__matrix_vector_proc(AtomArena_R aa, - RegUse_resolve_look_at__matrix_vector_proc r +internal MipsAtom* resolve_look_at__pop_mv_trans(AtomArena_R aa, + RegUse_resolve_look_at__pop_mv_trans r ) MipsAtom_Proc_(aa, { - load_word(r.look_at, R_TapePtr, O_(Binds_ResolveLookAtPopAndTrans,look_at)), - LdSlot_ add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtPopAndTrans)), + /* --- Tape pop: look_at pointer --- */ + load_word(r.look_at, R_TapePtr, O_(Binds_ResolveLookAtPopMvTrans,look_at)), + LdSlot_ add_ui_self(R_TapePtr, S_(Binds_ResolveLookAtPopMvTrans)), - /* Load RT from look_at.m into C2[0..4]. Packed S2 pairs, same as set_gte_mt3s2s4. */ - load_word( r.v.x, r.look_at, O_(MT3_S2S4, m[0][0])), /* RT11|RT12 */ LdSlot_ add_si(r.eye, r.scratch, O_(ResolveLookAtScratch,eye)), /* r.eye = &eye */ - load_word( r.v.y, r.look_at, O_(MT3_S2S4, m[0][2])), /* RT13|RT21 */ LdSlot_ gte_mv_to_ctrl_r(r.v.x, gte_cr_RT11), - load_word( r.v.z, r.look_at, O_(MT3_S2S4, m[1][1])), /* RT22|RT23 */ LdSlot_ gte_mv_to_ctrl_r(r.v.y, gte_cr_RT12), - load_word( r.v.x, r.look_at, O_(MT3_S2S4, m[2][0])), /* RT31|RT32 */ LdSlot_ gte_mv_to_ctrl_r(r.v.z, gte_cr_RT13), - load_half_u(r.v.y, r.look_at, O_(MT3_S2S4, m[2][2])), /* RT33 */ LdSlot_ gte_mv_to_ctrl_r(r.v.x, gte_cr_RT21), - /* pos = -eye. The three loads also retire the last CTC2. */ gte_mv_to_ctrl_r(r.v.y, gte_cr_RT22), - GteDelay_ mac_load_p3s4(r.v, r.eye, 0), LdSlot_ mac_sub_v3s4(r.v, v3s4_R_0(), r.v), /* pos.x = -eye.x */ + /* Load scratch base via immediate (Scratchpad_Loc = 0x1F800000). We can't rely on + * R_T4 (= R_ResolveScratch) surviving across the tape_run boundary — the compiler + * treats it as clobberable per the tape_run asm_clobber list. Baking the scratch + * address via load_word_imm is robust. */ + mac_load_word_imm(r.scratch, Scratchpad_Loc), + + /* --- Scratch addresses for ux/uy/uz/eye (populate phase; t6/t7/t8 alias ux/uy/uz) --- */ + add_si(r.t6.ux, r.scratch, O_(ResolveLookAtScratch, ux)), LdSlot_ + add_si(r.t7.uy, r.scratch, O_(ResolveLookAtScratch, uy)), + add_si(r.t8.uz, r.scratch, O_(ResolveLookAtScratch, uz)), + add_si(r.eye, r.scratch, O_(ResolveLookAtScratch, eye)), + + /* --- POPULATE phase: write look_at->m[][] from ux/uy/uz as packed S2 --- */ + mac_load_v3s4(r.row, r.t6.ux, 0), LdSlot_ mac_store_v3s2(r.row, r.look_at, O_(MT3_S2S4, m[0])), + mac_load_v3s4(r.row, r.t7.uy, 0), LdSlot_ mac_store_v3s2(r.row, r.look_at, O_(MT3_S2S4, m[1])), + mac_load_v3s4(r.row, r.t8.uz, 0), LdSlot_ mac_store_v3s2(r.row, r.look_at, O_(MT3_S2S4, m[2])), + + /* --- MATRIX-VECTOR phase: ctc2 RT chain + MVMVA RT*(-eye)>>12 --- */ + /* RT packing (per libgte ApplyMatrixLV convention; see gte.h:217-220 + + * atom_6b_disasm_comparison.md:28-32): + * C2[0] = (RT12<<16)|RT11 ← ctc2 RT11 from m[0][0..1] packed word + * C2[1] = (RT21<<16)|RT13 ← ctc2 RT12 from m[0][2..3] packed word + * C2[2] = (RT23<<16)|RT22 ← ctc2 RT13 from m[1][1..2] packed word + * C2[3] = (RT32<<16)|RT31 ← ctc2 RT21 from m[2][0..1] packed word + * C2[4] = (RT33<<16)|junk ← ctc2 RT22 from m[2][2] (half) + * Each ctc2 writes a WHOLE 32-bit C2 slot; the "macro name" identifies + * which C2 register, not which 16-bit half. */ + load_word( r.t6.v_x, r.look_at, O_(MT3_S2S4, m[0][0])), /* RT11|RT12 */ LdSlot_ + load_word( r.t7.v_y, r.look_at, O_(MT3_S2S4, m[0][2])), /* RT13|RT21 */ LdSlot_ gte_mv_to_ctrl_r(r.t6.v_x, gte_cr_RT11), + load_word( r.t8.v_z, r.look_at, O_(MT3_S2S4, m[1][1])), /* RT22|RT23 */ LdSlot_ gte_mv_to_ctrl_r(r.t7.v_y, gte_cr_RT12), + load_word( r.t6.v_x, r.look_at, O_(MT3_S2S4, m[2][0])), /* RT31|RT32 */ LdSlot_ gte_mv_to_ctrl_r(r.t8.v_z, gte_cr_RT13), + load_half_u(r.t7.v_y, r.look_at, O_(MT3_S2S4, m[2][2])), /* RT33 */ LdSlot_ gte_mv_to_ctrl_r(r.t6.v_x, gte_cr_RT21), + /* pos = -eye. The three loads also retire the last CTC2. */ gte_mv_to_ctrl_r(r.t7.v_y, gte_cr_RT22), + GteDelay_ mac_load_word_v3(r.t6.v_x, r.t7.v_y, r.t8.v_z, r.eye, 0), LdSlot_ + mac_sub_s_v3(r.t6.v_x, r.t7.v_y, r.t8.v_z, R_0, R_0, R_0, r.t6.v_x, r.t7.v_y, r.t8.v_z), /* 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. */ - gte_mv_to_data_r(r.v.x, C2_IR1), - gte_mv_to_data_r(r.v.y, C2_IR2), - gte_mv_to_data_r(r.v.z, C2_IR3), + gte_mv_to_data_r(r.t6.v_x, C2_IR1), + gte_mv_to_data_r(r.t7.v_y, C2_IR2), + gte_mv_to_data_r(r.t8.v_z, C2_IR3), GteDelay_ nop2, /* MVMVA pass 2 — C11 ApplyMatrixLV command. * sf=1, mx=0 (RT), v=3 (IR), cv=3. Reads RT × IR >> 12. */ - gte_cmdw_mvmva_c11_pass2, GteDelay_ nop, - mac_gte_mv_from_data_r_mac123(r.v.x, r.v.y, r.v.z), GteDelay_ nop, - mac_store_v3s4(r.v, r.eye, 0), + gte_cmdw_mvmva_c11_pass2, GteDelay_ nop, + mac_gte_mv_from_data_r_mac123(r.t6.v_x, r.t7.v_y, r.t8.v_z), GteDelay_ nop, - mac_yield() -}) - -/* Atom 6c in the bundle: copy scratch+96 (off, written by atom 6b) → look_at->t[]. - * Uses mac_trans_matrix component (m->t = v, libgte TransMatrix semantics = struct copy). - * - * GPR codes (assigned by resolve_look_at_init): - * r_look_at : MT3_S2S4* (popped from tape; output matrix destination) - * r_scratch : R_ResolveScratch (R_T4) — scratch base - * r_off_ptr : pointer to off (= &scratch.eye, reused slot) - * r_tmp0 : transfer reg for mac_trans_matrix - * - * Pool cost: r_look_at (1) + r_scratch (carrier) + r_off_ptr + 1 clobber = 4 GPRs. - */ -I_ MipsAtom* AtomBundleEntry_(resolve_look_at,trans_matrix)(AtomArena_R aa - , U4 r_look_at, U4 r_scratch, U4 r_off_ptr - , U4 r_tmp0, U4 r_tmp1, U4 r_tmp2 -) MipsAtom_Proc_(aa, { - /* r_off_ptr = &off (= &scratch.eye since atom 6b overwrote eye with off). */ - add_si(r_off_ptr, r_scratch, O_(ResolveLookAtScratch,eye)), nop, - - /* Copy off → look_at.t[] (mac_trans_matrix: m->t = v). */ - mac_trans_mt3s3s4(r_look_at, r_off_ptr, r_tmp0, r_tmp1, r_tmp2), + /* --- TRANS-MATRIX phase: store off directly to look_at->t[] (skip scratch.eye intermediate) --- */ + mac_store_word_v3(r.t6.v_x, r.t7.v_y, r.t8.v_z, r.look_at, O_(MT3_S2S4, t)), mac_yield() }) diff --git a/code/hello_camera/hello_camera.c b/code/hello_camera/hello_camera.c index fa0ddbd..8445bf4 100644 --- a/code/hello_camera/hello_camera.c +++ b/code/hello_camera/hello_camera.c @@ -52,11 +52,6 @@ #include "hello_camera.atom.c" #pragma endregion Hello Joypad TUs -enum { - Scratchpad_Loc = 0x1F800000, -}; -#define C_scratch(type) C_(type, Scratchpad_Loc) - enum { Scratchpad_Len = 1024, MemTape_Len = 512, @@ -91,9 +86,11 @@ typedef Struct_(SMemory) { U1 ct_init_atom_mem[CT_InitAtomMem_Size]; MipsAtom* normalize_v3s4; - // TODO(Ed): Convert normalize_v3s4 to a generic atom? - // This would allow us to reduce specializations with the loss being some cycles to loading registers. - // The cost would be 3 loads (scratch, src_ptr, dst_offset) from tape and + MipsAtom* gte_cross_v3s4; /* GTE OP OuterProduct12 (a × b → out). + * Baked once at init from compile_gte_cross_v3s4() into ct_init_atom_mem. + * One instance serves both cross call sites in resolve_look_at; + * per-frame tape emits push src_a/src_b/out (3 pointers = 12 bytes) + * for Binds_gte_cross_v3s4. */ U1 resolve_look_at_mem[ResolveLookAtArena_Size]; MipsAtom* resolve_look_at_bundle[AtomBundle_Len(resolve_look_at)]; @@ -143,6 +140,57 @@ FI_ void camera_look_at_c11(Camera* c, P3_S4* target, V3_S4* up_in) { resolve_lo +internal void compile_init_atoms(void) { + /* Single shared arena: each compile_* function pushes its atom into the same + * ct_init_atom_mem backing. If each function called atomarena_make() locally, + * the second call would reset arena.used to 0 and overwrite the first atom. + */ + AtomArena ab = atomarena_make(slice_ut_arr(smem.ct_init_atom_mem)); + RegFile rf = regfile(regfile_abi_mask); + + /* === gte_cross_v3s4 — GTE OP OuterProduct12 (a × b → out) === + * Reused by both cross call sites in resolve_look_at. No scratch carrier. + * GPR pool: 9 allocatable (no carrier). Pool R_T0..R_T7, R_V0, R_V1 minus ABI = 9. + * Fits exactly. */ + { + smem.gte_cross_v3s4 = gte_cross_v3s4(& ab, + RegUse_(gte_cross_v3s4) { + .a = { regfile_alloc(& rf), regfile_alloc(& rf), regfile_alloc(& rf) }, + .b = { regfile_alloc(& rf), regfile_alloc(& rf), regfile_alloc(& rf) }, + .x = regfile_alloc(& rf), /* out / t0 shared */ + .y = regfile_alloc(& rf), /* src_a / t1 / rt11 shared */ + .z = regfile_alloc(& rf), /* src_b / t2 / rt22 shared */ + }); + regfile_reset(& rf); + } + + /* === build_normalize_v3s4 — Generic 4-stage GTE normalize === + * Reused by all 3 normalize call sites in resolve_look_at. Reads scratch + src/dst + * offsets from tape (no carrier — atom is fully self-contained per call). + * GPR pool: no carrier, 10 allocatable (R_T0..R_T7, R_V0, R_V1 minus ABI pins). + * 10 fields (scratch + src_ptr + dst_ptr + recip_est + norm + shift + src_x + + * t3 + t4 + t5) → just fits. Union members t5.src_offset, t3.dst_offset, etc. + * share GPRs via lifetime discipline. */ + { + RegFile rf = regfile(regfile_abi_mask); + smem.normalize_v3s4 = build_normalize_v3s4(& ab, + RegUse_(build_normalize_v3s4) { + .scratch = regfile_alloc(& rf), + .src_ptr = regfile_alloc(& rf), + .dst_ptr = regfile_alloc(& rf), + .recip_est = regfile_alloc(& rf), + .norm = regfile_alloc(& rf), + .shift = regfile_alloc(& rf), + .src_x = regfile_alloc(& rf), + .t3 = regfile_alloc(& rf), + .t4 = regfile_alloc(& rf), + .t5 = regfile_alloc(& rf), + }); + } + + assert(ab.used <= CT_InitAtomMem_Size); +} + internal void compile_resolve_look_at(void) { /* Wrap the static arena in a MipsAtomBuilder. */ AtomArena ab = atomarena_make(slice_ut_arr(smem.resolve_look_at_mem)); @@ -169,118 +217,32 @@ internal void compile_resolve_look_at(void) { regfile_reset_to_mask(& rf, pin_mask); /* === ATOM 1: normalize fwd→uz === */ - U2 src_offset = O_(ResolveLookAtScratch, fwd); - U2 dst_offset = O_(ResolveLookAtScratch, uz); - smem.resolve_look_at_bundle[1] = build_normalize_v3s4(& ab, - src_offset, dst_offset, RegUse_(build_normalize_v3s4){ - .scratch = R_ResolveScratch, - .src_ptr = ralloc(), - .dst_ptr = ralloc(), - .recip_est = ralloc(), - .norm = ralloc(), - .shift = ralloc(), - .src_x = ralloc(), - .t3 = ralloc(), - .t4 = ralloc(), - .t5 = ralloc(), - }); - regfile_reset_to_mask(& rf, pin_mask); + smem.resolve_look_at_bundle[1] = smem.normalize_v3s4; - /* === ATOM 2: cross uz×up_in→right (Binds_gte_cross_v3s4) === */ - smem.resolve_look_at_bundle[2] = gte_cross_v3s4(& ab, - RegUse_(gte_cross_v3s4) { - .a = ralloc_v3(), - .b = ralloc_v3(), - .t0 = ralloc(), - .t1 = ralloc(), - .t2 = ralloc(), - }); - regfile_reset_to_mask(& rf, pin_mask); + /* === ATOM 2: cross uz×up_in→right (reuses smem.gte_cross_v3s4) === */ + smem.resolve_look_at_bundle[2] = smem.gte_cross_v3s4; /* === ATOM 3: normalize right→ux === */ - src_offset = O_(ResolveLookAtScratch, right); - dst_offset = O_(ResolveLookAtScratch, ux); - smem.resolve_look_at_bundle[3] = build_normalize_v3s4(& ab, - src_offset, dst_offset, RegUse_(build_normalize_v3s4){ - .scratch = R_ResolveScratch, - .src_ptr = ralloc(), - .dst_ptr = ralloc(), - .recip_est = ralloc(), - .norm = ralloc(), - .shift = ralloc(), - .src_x = ralloc(), - .t3 = ralloc(), - .t4 = ralloc(), - .t5 = ralloc(), - }); - regfile_reset_to_mask(& rf, pin_mask); + smem.resolve_look_at_bundle[3] = smem.normalize_v3s4; - /* === ATOM 4: cross uz×ux→up (Binds_gte_cross_v3s4) === */ - smem.resolve_look_at_bundle[4] = gte_cross_v3s4(& ab, - RegUse_(gte_cross_v3s4) { - .a = ralloc_v3(), - .b = ralloc_v3(), - .t0 = ralloc(), - .t1 = ralloc(), - .t2 = ralloc(), - }); - regfile_reset_to_mask(& rf, pin_mask); + /* === ATOM 4: cross uz×ux→up (reuses smem.gte_cross_v3s4) === */ + smem.resolve_look_at_bundle[4] = smem.gte_cross_v3s4; /* === ATOM 5: normalize up→uy === */ - src_offset = O_(ResolveLookAtScratch, up); - dst_offset = O_(ResolveLookAtScratch, uy); - smem.resolve_look_at_bundle[5] = build_normalize_v3s4(& ab, - src_offset, dst_offset, - RegUse_(build_normalize_v3s4){ - .scratch = R_ResolveScratch, - .src_ptr = ralloc(), - .dst_ptr = ralloc(), - .recip_est = ralloc(), - .norm = ralloc(), - .shift = ralloc(), - .src_x = ralloc(), - .t3 = ralloc(), - .t4 = ralloc(), - .t5 = ralloc(), - }); - regfile_reset_to_mask(& rf, pin_mask); + smem.resolve_look_at_bundle[5] = smem.normalize_v3s4; - /* === ATOM 6a: populate (m[][] from ux/uy/uz, t[]=0) === */ - smem.resolve_look_at_bundle[6] = resolve_look_at__populate_proc(& ab, - RegUse_(resolve_look_at__populate_proc){ - .scratch = R_ResolveScratch, - .look_at = ralloc(), /* T0 */ - .row = ralloc_v3(), /* T1 T2 T3 */ - .ux = ralloc(), /* T5 = ux */ - .uy = ralloc(), /* T6 = uy */ - .uz = ralloc(), /* T7 = uz */ - }); - regfile_reset_to_mask(& rf, pin_mask); - - /* === ATOM 6a.5: set_gte_mt3s2s4 (BAKED — ctc2 RT matrix) === */ - smem.resolve_look_at_bundle[7] = (MipsAtom*) & set_gte_mt3s2s4; - - /* === ATOM 6b: matrix_vector (RT * (-eye) >> 12) === */ - smem.resolve_look_at_bundle[8] = resolve_look_at__matrix_vector_proc(& ab, - RegUse_(resolve_look_at__matrix_vector_proc){ - .scratch = R_ResolveScratch, - .look_at = ralloc(), /* T0 */ - .eye = ralloc(), /* T1 */ - .v = ralloc_v3(), /* T2 T3 T5 */ - }); - - /* === ATOM 6c: trans_matrix (off → look_at->t[]) === */ - U4 r_look_at_6c = R_T0; /* tape pop → look_at* */ - U4 r_scratch_6c = R_ResolveScratch; - U4 r_off_ptr_6c = R_T1; /* &scratch.eye (= off dst) */ - U4 r_tmp0_6c = R_T2; - smem.resolve_look_at_bundle[9] = AtomBundleEntry_(resolve_look_at,trans_matrix)(& ab, - r_look_at_6c, - r_scratch_6c, - r_off_ptr_6c, - r_tmp0_6c, - R_T3, - R_T4); + /* === ATOM 6 (FUSED): populate + ctc2 RT + MVMVA + trans_matrix → look_at === */ + smem.resolve_look_at_bundle[6] = resolve_look_at__pop_mv_trans(& ab, + RegUse_(resolve_look_at__pop_mv_trans){ + .scratch = R_ResolveScratch, + .look_at = ralloc(), /* T0 */ + .eye = ralloc(), /* T1 — allocated BEFORE row so it doesn't alias row.y */ + .row = ralloc_v3(), /* T2 T3 T5 */ + .t6 = ralloc(), /* T6 = ux (populate) / v_x (matrix_vector) */ + .t7 = ralloc(), /* T7 = uy (populate) / v_y (matrix_vector) */ + .t8 = ralloc(), /* V0 = uz (populate) / v_z (matrix_vector) */ + }); + /* No regfile_reset between phases: the fused atom uses all 9 GPRs throughout. */ /* Sanity check: arena didn't overflow. */ assert(ab.used <= ResolveLookAtArena_Size); @@ -305,7 +267,11 @@ I_ void resolve_look_at(TapeBuilder_R tb } tb_emit(tb, smem.resolve_look_at_bundle[1]); { - // tb_data(tb, u4_(Scratchpad_Loc)); + /* Binds_NormalizeV3S4: src_offset (low 16) | dst_offset (high 16). Both U2s + * packed into a single U4 because the atom body reads at byte offsets 0 and 2 + * from R_TapePtr. Scratch base is baked into the atom (load_word_imm of + * Scratchpad_Loc). */ + tb_data(tb, u4_(O_(ResolveLookAtScratch, fwd) | (O_(ResolveLookAtScratch, uz) << 16))); } tb_emit(tb, smem.resolve_look_at_bundle[2]); { /* Binds_gte_cross_v3s4: src_a, src_b, out (3 pointers). */ @@ -314,7 +280,7 @@ I_ void resolve_look_at(TapeBuilder_R tb tb_data(tb, u4_(& sp->right)); /* out */ } tb_emit(tb, smem.resolve_look_at_bundle[3]); { - // tb_data(tb, u4_(Scratchpad_Loc)); + tb_data(tb, u4_(O_(ResolveLookAtScratch, right) | (O_(ResolveLookAtScratch, ux) << 16))); } tb_emit(tb, smem.resolve_look_at_bundle[4]); { /* Binds_gte_cross_v3s4: src_a, src_b, out (3 pointers). */ @@ -323,21 +289,13 @@ I_ void resolve_look_at(TapeBuilder_R tb tb_data(tb, u4_(& sp->up)); /* out */ } tb_emit(tb, smem.resolve_look_at_bundle[5]); { - // tb_data(tb, u4_(Scratchpad_Loc)); + tb_data(tb, u4_(O_(ResolveLookAtScratch, up) | (O_(ResolveLookAtScratch, uy) << 16))); } + /* === FUSED atom: populate + matrix_vector + trans_matrix (replaces 4 separate emits) === */ tb_emit(tb, smem.resolve_look_at_bundle[6]); { tb_data(tb, u4_(look_at)); } - tb_emit(tb, smem.resolve_look_at_bundle[7]); { - tb_data(tb, u4_(look_at)); - } - tb_emit(tb, smem.resolve_look_at_bundle[8]); { - tb_data(tb, u4_(look_at)); - } - tb_emit(tb, smem.resolve_look_at_bundle[9]); { - // tb_data(tb, u4_(look_at)); - } } GCC_OPTIMIZATION_DISABLE @@ -529,6 +487,7 @@ int main(void) /* Direct BIOS: poll both ports during VBlank. */ pad_bios_init_start(& smem.pad_raw[0], & smem.pad_raw[1]); + compile_init_atoms(); compile_resolve_look_at(); /* Pinned registers for the GPU init atom. */