diff --git a/code/duffle/gen/macs.h b/code/duffle/gen/macs.h index b15e20e..cfdfa9e 100644 --- a/code/duffle/gen/macs.h +++ b/code/duffle/gen/macs.h @@ -156,29 +156,41 @@ WORD_COUNT(mac_format_f3_color, 3) , mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c3), 0, r3,g3,b3) WORD_COUNT(mac_format_g4_color, 12) -#define mac_insert_ot_tag_f3(r_ot_base, r_prim_cursor) \ +#define mac_insert_ot_tag(r_ot_base, r_prim_cursor, poly_size) \ shift_lleft( R_T1, R_T1, S_(U4)/2) /* T1 = otz * S_(U4) (otz arg is implicit R_T1) */ \ , add_u_self( R_T1, r_ot_base) /* T1 = & OrderingTable[OTZ] */ \ , load_word( R_AT, R_T1, O_(PolyTag,code)) /* AT = old_ot_head */ \ -, load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) /* V0 = (5 - 1) << 24 = 4 << 24 */ \ +, load_upper_i(R_V0, (poly_size/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) \ , mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \ , or_u( R_AT, R_AT, R_V0) /* Merge length */ \ , store_word( R_AT, r_prim_cursor, O_(PolyTag,code)) /* prim->tag = packed(prim_length, old_addr) */ \ , shift_lleft( R_AT, r_prim_cursor, S_(PolyTag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \ , shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)) \ , store_word( R_AT, R_T1, O_(PolyTag,code)) /* OrderingTable[OTZ] = PrimCursor */ -WORD_COUNT(mac_insert_ot_tag_f3, 11) +WORD_COUNT(mac_insert_ot_tag, 11) -#define mac_insert_ot_tag_g4(r_ot_base, r_prim_cursor) \ - shift_lleft( R_T1, R_T1, S_(U4)/2) /* T1 = otz * S_(U4) (otz arg is implicit R_T1) */ \ -, add_u_self( R_T1, r_ot_base) /* T1 = & OrderingTable[OTZ] */ \ -, load_word( R_AT, R_T1, O_(PolyTag,code)) /* AT = old_ot_head */ \ -, load_upper_i(R_V0, (S_(Poly_G4)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) /* V0 = (9 - 1) << 24 = 8 << 24 */ \ -, mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \ -, or_u( R_AT, R_AT, R_V0) /* Merge length */ \ -, store_word( R_AT, r_prim_cursor, O_(PolyTag,code)) /* prim->tag = packed(prim_length, old_addr) */ \ -, shift_lleft( R_AT, r_prim_cursor, S_(PolyTag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \ -, shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)) \ -, store_word( R_AT, R_T1, O_(PolyTag,code)) /* OrderingTable[OTZ] = PrimCursor */ -WORD_COUNT(mac_insert_ot_tag_g4, 11) +/* atom_dbg_skip */ +#define mac_pad_set_centered_axes(r_state, r_scratch) \ + load_upper_i(r_scratch, (PadAxis_Centered_Word >> 16) & 0xFFFF) \ +, or_i_self( r_scratch, PadAxis_Centered_Word & 0xFFFF) \ +, store_word( r_scratch, r_state, O_(PadState,axes)) +WORD_COUNT(mac_pad_set_centered_axes, 3) + +/* atom_dbg_skip */ +#define mac_pad_set_id_byte(r_state, r_id, id_value) \ + add_ui( r_id, R_0, id_value) \ +, store_byte(r_id, r_state, O_(PadState,id)) +WORD_COUNT(mac_pad_set_id_byte, 2) + +/* atom_dbg_skip */ +#define mac_pad_set_status(r_tmp, r_state, pad_status) \ + add_ui( r_tmp, R_0, pad_status) \ +, store_word(r_tmp, r_state, O_(PadState,status)) +WORD_COUNT(mac_pad_set_status, 2) + +/* atom_dbg_skip */ +#define mac_pad_store_inverted_buttons(r_buttons, r_pad_state) \ + nor_u( r_buttons, r_buttons, R_0) \ +, store_half( r_buttons, r_pad_state, O_(PadState, buttons)) +WORD_COUNT(mac_pad_store_inverted_buttons, 2) diff --git a/code/duffle/gen/offsets.h b/code/duffle/gen/offsets.h index 0f7d193..991cddf 100644 --- a/code/duffle/gen/offsets.h +++ b/code/duffle/gen/offsets.h @@ -23,17 +23,17 @@ #pragma region duffle -// --- atom: pad_bios_snapshot (78 words) --- +// --- atom: pad_bios_snapshot (84 words) --- -#define _atom_offset_snap_root_skip_disconnected 8 -#define _atom_offset_disconnected_snap_end 61 -#define _atom_offset_case_2_id_dispatch 8 -#define _atom_offset_pending_snap_end 51 -#define _atom_offset_id_dispatch_try_analog_stick 11 -#define _atom_offset_id_dispatch_snap_end 38 -#define _atom_offset_try_analog_stick_try_analog_pad 12 -#define _atom_offset_analog_stick_snap_end 24 -#define _atom_offset_try_analog_pad_try_unsupported 11 +#define _atom_offset_snap_root_skip_disconnected 10 +#define _atom_offset_disconnected_snap_end 65 +#define _atom_offset_case_2_id_dispatch 9 +#define _atom_offset_pending_snap_end 54 +#define _atom_offset_id_dispatch_try_analog_stick 12 +#define _atom_offset_id_dispatch_snap_end 40 +#define _atom_offset_try_analog_stick_try_analog_pad 13 +#define _atom_offset_analog_stick_snap_end 25 +#define _atom_offset_try_analog_pad_try_unsupported 12 #define _atom_offset_analog_pad_snap_end 10 enum { diff --git a/code/duffle/gp.atom.c b/code/duffle/gp.atom.c index 91892e4..ce1acdc 100644 --- a/code/duffle/gp.atom.c +++ b/code/duffle/gp.atom.c @@ -49,13 +49,12 @@ MipsAtomComp_Proc_(ac_format_g4_color, { mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c3), 0, r3,g3,b3), }) -/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list. - * Hardcoded for Poly_F3 (5 words). For Poly_G4, use ac_insert_ot_tag_g4. */ -I_ Slice_MipsCode ac_insert_ot_tag_f3(U4 r_ot_base, U4 r_prim_cursor) MipsAtomComp_Proc_(ac_insert_ot_tag_f3, { +/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list. */ +I_ Slice_MipsCode ac_insert_ot_tag(U4 r_ot_base, U4 r_prim_cursor, U4 poly_size) MipsAtomComp_Proc_(ac_insert_ot_tag, { shift_lleft( R_T1, R_T1, S_(U4)/2), // T1 = otz * S_(U4) (otz arg is implicit R_T1) add_u_self( R_T1, r_ot_base), // T1 = & OrderingTable[OTZ] load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head - load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), // V0 = (5 - 1) << 24 = 4 << 24 + load_upper_i(R_V0, (poly_size/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)), // Strip upper 8 bits (length from prev cell) → keep only low 24 or_u( R_AT, R_AT, R_V0), // Merge length store_word( R_AT, r_prim_cursor, O_(PolyTag,code)), // prim->tag = packed(prim_length, old_addr) @@ -64,19 +63,4 @@ I_ Slice_MipsCode ac_insert_ot_tag_f3(U4 r_ot_base, U4 r_prim_cursor) MipsAtomCo store_word( R_AT, R_T1, O_(PolyTag,code)), // OrderingTable[OTZ] = PrimCursor }) -/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list. - * Hardcoded for Poly_G4 (9 words). For Poly_F3, use ac_insert_ot_tag_f3. */ -I_ Slice_MipsCode ac_insert_ot_tag_g4(U4 r_ot_base, U4 r_prim_cursor) MipsAtomComp_Proc_(ac_insert_ot_tag_g4, { - shift_lleft( R_T1, R_T1, S_(U4)/2), // T1 = otz * S_(U4) (otz arg is implicit R_T1) - add_u_self( R_T1, r_ot_base), // T1 = & OrderingTable[OTZ] - load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head - load_upper_i(R_V0, (S_(Poly_G4)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), // V0 = (9 - 1) << 24 = 8 << 24 - mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)), // Strip upper 8 bits (length from prev cell) → keep only low 24 - or_u( R_AT, R_AT, R_V0), // Merge length - store_word( R_AT, r_prim_cursor, O_(PolyTag,code)), // prim->tag = packed(prim_length, old_addr) - shift_lleft( R_AT, r_prim_cursor, S_(PolyTag_len_bits)), // AT = (prim_length << 24) | old_addr - shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)), - store_word( R_AT, R_T1, O_(PolyTag,code)), // OrderingTable[OTZ] = PrimCursor -}) - #pragma endregion MACs (Mips Atom Components) diff --git a/code/duffle/lottes_tape.h b/code/duffle/lottes_tape.h index c28ded6..0faf298 100644 --- a/code/duffle/lottes_tape.h +++ b/code/duffle/lottes_tape.h @@ -12,70 +12,57 @@ #endif #pragma region Tape Drive -/* ----------------------------------------------------------------------------- +/* ----------------------------------------------------------------------------------------------------------- * TAPE DRIVE ABI - * ----------------------------------------------------------------------------- - * Note(Ed): One of the main purposes of this codebase is to help me - * learn this, as such the information below may be entirely realized - * or finalized conceptually. - * ----------------------------------------------------------------------------- - * This ABI and its associated legos were directly inspired by researching - * the work of Timothy Lottes and Onat Türkçüoğlu; along with many others. - * It's the simplest bootstrap of a a directly executed chain of assemby - * arrays (Atoms) that terminate with a yield sequence to the next atom. - * These eventually lead to a terminal atom for the tape which is defined - * below as "tape_exit". + * ----------------------------------------------------------------------------------------------------------- + * Note(Ed): One of the main purposes of this codebase is to help me learn this, + * as such the information below may not* be entirely realized or finalized conceptually. + * ----------------------------------------------------------------------------------------------------------- + * This ABI and its associated legos were directly inspired by researching the work of + * Timothy Lottes and Onat Türkçüoğlu; along with many others. It's the simplest bootstrap of a + * directly executed chain of assemby arrays (Atoms) that terminate with a yield sequence to the next atom. + * These eventually lead to a terminal atom for the tape which is defined below as "tape_exit". * - * This behaves as one of the simplest runtime harnesses ontop of a - * host-enviornment's execution engine to author and compose programs with. - * From here various conventions can be further applied. - * To make things easier to understand it may be better to focus on what this - * ABI does not have. It does not have have any branching within the tape but - * relative branches within atoms or between atoms. + * This behaves as one of the simplest runtime harnesses ontop of a host-enviornment's execution engine + * to author and compose programs with. From here various conventions can be further applied. + * To make things easier to understand it may be better to focus on what this ABI does not have. + * It does not have have any branching within the tape but relative branches within atoms or between atoms. * Branching nearly is always downstream. Stack usage is non-existent. * Push/Pop, FIFO, or Arena/Bump data structures are used by atoms explicitly. - * In it's current form withe C11 macro dsl, the user also has to do manual register - * allocation per atom. + * In it's current form with the C11 macro dsl, the user also has fullfill manual register allocation per atom. * - * One of the remarkable things about utilizing this abi is its essentially - * interopable with CPUs, GPUs, FPGA, or, basically anything - * from the 5th generation consoles and onward. - * The ABI directly reflects how all computational hardware must be architected - * in order to execute digital logic effectively on current era tech. - * On the PS1 we don't have access to a few features like multi-threading, - * speculative execution, or L3 cache; but, we can set the foundation for legoing - * whats required for eventually expanding this ABI's paradigm and core atoms - * to take those newer hardware features into account. For example, you can easily - * expand this to support wave-based execution model on a PS2 or PS3. - * Not having a stack or automatic register allocation means the user can't ignore - * excessive argument shuffle across workload or waves and thier phases. - * Crossing ABI boundaries to other runtimes that do has obviouss penalties. + * One of the remarkable things about utilizing this ABI is its essentially interopable with CPUs, GPUs, FPGA, + * or, basically anything from the 5th generation consoles and onward. + * The ABI directly reflects how all computational hardware must be architected in order to execute + * digital logic effectively on current era tech. + * On the PS1 we don't have access to a few features like multi-threading, speculative execution, or L3 cache; + * but, we can set the foundation for legoing whats required for eventually expanding this ABI's paradigm + * and core atoms to take those newer hardware features into account. For example, you can easily expand + * this to support wave-based execution model on a PS2 or PS3. Not having a stack or + * automatic register allocation means the user cannott ignore excessive argument shuffle across workload or + * waves and thier phases. Crossing ABI boundaries to other runtimes that do has obviouss penalties. * - * Learning data-oreinted code becomes a natural progression. Your not fighting - * a stack-based procedural paradigm that wants to argument shuffle on by lack of - * constraints on how the user may "call" a procedure. The user doesn't - * have to hammer down "rules" or patterns to know how to massage the compiler - * to dissolve those call frames to get the asesmbly into its desired form. - * The form is obvious, and once the user gets to author these compoonents + * Learning data-oreinted code becomes a natural progression. Your not fighting a stack-based procedural + * paradigm that wants to argument shuffle. There is no ambiguity due to the lack of constraints, for example, + * on how the user may "call" a procedure in traditional random dispatch runtimes. The user does have to + * hammer down "rules" or patterns for massaging the compiler to dissolve those call frames; just to get + * the asesmbly into its desired form. The form is obvious, and once the user gets to author these compoonents * it becomes a game of tetris. * - * Another feature is this ABI is very compatible with bootstrapping and developing - * simple toolchains built off of bit-packed annotated command streams the user can - * directly author, maintatain, and immediately execute. That being like a color forth. - * This can make the tetris less of a chore with some helpful policy generation for - * allocation of registers, helping to choose resuable components, designing DSL on - * the fly, etc. - * ----------------------------------------------------------------------------- + * Another feature is this ABI is very compatible with bootstrapping and developing simple toolchains built off + * of bit-packed annotated command streams the user can directly author, maintatain, and immediately execute. + * That being like a color forth, or maybe something more familar like an immediate mode library + * for various systems such as GUIs. This can make the tetris less of a chore with some helpful policy + * generation for allocation of registers, helping to choose resuable components, designing DSL on the fly, etc. + * ----------------------------------------------------------------------------------------------------------- * TODO(Ed): We need pretty ascii diagrams and proper guides, articles, etc. - * ----------------------------------------------------------------------------- - * For now this ideation is just started functioning. I'm abusing C11 & a lua metaprogram - * to help establish a hybrid toolchain to ideate on a traditional text-based - * authoring UX for this paradigm. - * If pcsx-redux provides viable hot-reload and persistent data storage beyond - * save-states (just copying ram to filesystem), I can author a color forth to - * mess around with. With either an editor in-emulator or on the actual machine itself. - * Assembly is tedius, but I think this codebase most likely has a pretty ergonomic - * flavor worst case... + * ----------------------------------------------------------------------------------------------------------- + * For now this ideation has just started functioning. I'm abusing C11 & a lua metaprogram to help establish + * a hybrid toolchain to ideate on a traditional text-based authoring UX for this paradigm. + * If pcsx-redux provides viable hot-reload and persistent data storage beyond save-states + * (just copying ram to filesystem), I can author a color forth to mess around with. + * With either an editor in-emulator or on the actual machine itself. Assembly is tedius, + * but I think this codebase most likely has a pretty ergonomic flavor worst case... * */ /* Register Allocation Info */ enum { diff --git a/code/duffle/math.h b/code/duffle/math.h index 09fff5e..b1c8656 100644 --- a/code/duffle/math.h +++ b/code/duffle/math.h @@ -39,6 +39,7 @@ typedef Struct_(Rect_S4) { S4 x; S4 y; S4 width; S4 height; }; typedef Struct_(M3_S2) { A3x3_S2 m; A3_S4 t; }; +typedef Array_(V2_U1, 2); typedef Array_(V2_S2, 2); typedef Array_(V2_S2, 3); typedef Array_(V2_S2, 4); diff --git a/code/duffle/pad.atom.c b/code/duffle/pad.atom.c index 1a0396c..e698eb3 100644 --- a/code/duffle/pad.atom.c +++ b/code/duffle/pad.atom.c @@ -9,6 +9,34 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(pad_atom_c); +#pragma region MACs (Mips Atom Components) + +FI_ Slice_MipsCode ac_pad_set_centered_axes(U4 r_state, U4 r_scratch) atom_dbg_skip MipsAtomComp_Proc_(ac_pad_set_centered_axes, { + load_upper_i(r_scratch, (PadAxis_Centered_Word >> 16) & 0xFFFF), + or_i_self( r_scratch, PadAxis_Centered_Word & 0xFFFF), + store_word( r_scratch, r_state, O_(PadState,axes)), +}) + +FI_ Slice_MipsCode ac_pad_set_id_byte(U1 r_state, U1 r_id, U1 id_value) atom_dbg_skip MipsAtomComp_Proc_(ac_pad_set_id_byte, { + add_ui( r_id, R_0, id_value), + store_byte(r_id, r_state, O_(PadState,id)), +}) + +FI_ Slice_MipsCode ac_pad_set_status(U4 r_tmp, U1 r_state, U4 pad_status) atom_dbg_skip MipsAtomComp_Proc_(ac_pad_set_status, { + add_ui( r_tmp, R_0, pad_status), + store_word(r_tmp, r_state, O_(PadState,status)), +}) + +/* Invert r_buttons (active-low → active-high) and store to PadState.buttons. + * r_buttons must already be loaded (the caller is responsible for filling the load-delay slot of + * the preceding load_half_u with an instruction that doesn't read r_buttons). */ +FI_ Slice_MipsCode ac_pad_store_inverted_buttons(U1 r_buttons, U1 r_pad_state) atom_dbg_skip MipsAtomComp_Proc_(ac_pad_store_inverted_buttons, { + nor_u( r_buttons, r_buttons, R_0), + store_half( r_buttons, r_pad_state, O_(PadState, buttons)), +}) + +#pragma endregion MACs (Mips Atom Components) + #pragma region Baked Atoms /* ----- pad_bios_snapshot ----- @@ -35,7 +63,7 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(pad_atom_c); */ enum { R_PadRaw = R_T0 atom_reg atom_type(U1), - R_PadState = R_T1 atom_reg, + R_PadState = R_T1 atom_reg atom_type(PadState*), R_RawStatus = R_T2 atom_reg, R_RawId = R_T3 atom_reg, }; @@ -44,8 +72,8 @@ typedef Struct_(Binds_PadBiosSnapshot) { PadState* state; }; internal MipsAtom_(pad_bios_snapshot) atom_info(atom_bind(Binds_PadBiosSnapshot) -, atom_reads( R_PadRaw, R_PadState, R_RawStatus, R_RawId, R_T4, R_T5, R_TapePtr) -, atom_writes(R_PadRaw, R_PadState, R_RawStatus, R_RawId, R_T4, R_T5, R_TapePtr) +, atom_reads( R_PadRaw, R_PadState, R_RawStatus, R_RawId) +, atom_writes(R_PadRaw, R_PadState, R_RawStatus, R_RawId) ) { /* === Bind consumption: T0 = raw, T1 = state, advance R_TapePtr by 8. */ load_word(R_PadRaw, R_TapePtr, O_(Binds_PadBiosSnapshot,raw)), @@ -53,111 +81,97 @@ internal MipsAtom_(pad_bios_snapshot) atom_info(atom_bind(Binds_PadBiosSnapshot) add_ui_self( R_TapePtr, S_(Binds_PadBiosSnapshot)), /* === Read raw[0] (status) + raw[1] (id) */ - load_byte_u(R_RawStatus, R_PadRaw, 0), - load_byte_u(R_RawId, R_PadRaw, 1), + load_byte_u(R_RawStatus, R_PadRaw, O_(PadBiosRaw,status)), + load_byte_u(R_RawId, R_PadRaw, O_(PadBiosRaw,id)), atom_label(snap_root) /* === Case 1: Disconnected (status == 0xFF). */ - add_ui(R_T4, R_0, 0xFF), branch_ne(R_RawStatus, R_T4, atom_offset(snap_root, skip_disconnected)), + add_ui(R_T4, R_0, PadRawStatus_Timeout), branch_ne(R_RawStatus, R_T4, atom_offset(snap_root, skip_disconnected)), /* BD-slot: pre-compute PadStatus_Disconnected. Branch reads R_T4=0xFF in EX before this WB completes. * If branch NOT taken (fall through to pending/id_dispatch), R_T4 is overwritten by the next case body's add_ui — harmless. */ atom_label(disconnected) /* === Disconnected body. */ - /* R_T4 = PadStatus_Disconnected from snap_root BD-slot. */ - store_word(R_T4, R_PadState, O_(PadState,status)), - store_half(R_0, R_PadState, O_(PadState,buttons)), - /* axes = 0x80808080 (centered) — single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y). */ - load_upper_i(R_T4, 0x8080), or_i_self(R_T4, 0x8080), - store_word( R_T4, R_PadState, O_(PadState,left_x)), - store_byte( R_RawId, R_PadState, O_(PadState,id)), + mac_pad_set_status(R_T4, R_PadState, PadStatus_Disconnected), + store_half( R_0, R_PadState, O_(PadState,buttons)), + mac_pad_set_centered_axes(R_PadState, R_T4), + mac_pad_set_id_byte( R_PadState, R_RawId, PadRawStatus_Timeout), jump_rel(atom_offset(disconnected, snap_end)), /* BD-slot: load next atom's entry point (replaces the nop). - * The unconditional branch always jumps to snap_end, where mac_yield_tail() - * transfers control to R_AtomJmp without re-loading it. */ + * Always jumps to snap_end, where mac_yield_tail() transfers control to R_AtomJmp without re-loading it. */ mac_yield_load(), atom_label(skip_disconnected) /* === Case 2: Pending (status == 0 && id == 0) - * Combined check: if (status | id) != 0 then skip to id_dispatch. - * Falls through to the Pending case only when both are zero. */ + * Combined check: if (status | id) != 0 then skip to id_dispatch. Falls through to the Pending case only when both are zero. */ or_u_self(R_RawStatus, R_RawId), branch_ne(R_RawStatus, R_0, atom_offset(case_2, id_dispatch)), /* BD-slot: pre-compute PadStatus_Pending. Branch reads R_RawStatus in EX before this WB completes. - * If branch NOT taken (fall through to id_dispatch), R_T4 is overwritten by the digital/analog body add_ui — harmless. */ + * If branch NOT taken (fall through to id_dispatch), R_T4 is overwritten by the digital/analog body add_ui - harmless. */ -atom_label(pending) /* === Pending body */ - /* R_T4 = PadStatus_Pending from case_2 BD-slot. */ - store_word(R_T4, R_PadState, O_(PadState,status)), - store_half(R_0, R_PadState, O_(PadState,buttons)), - /* axes = 0x80808080 (centered) — single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y). */ - load_upper_i(R_T4, 0x8080), or_i_self(R_T4, 0x8080), - store_word( R_T4, R_PadState, O_(PadState,left_x)), - store_byte( R_RawId, R_PadState, O_(PadState,id)), - jump_rel(atom_offset(pending, snap_end)), +atom_label(pending) /* === Pending body (status=0, id=0 — pre-IRQ-empty buffer). */ + mac_pad_set_status(R_T4, R_PadState, PadStatus_Pending), + store_half( R_0, R_PadState, O_(PadState,buttons)), + mac_pad_set_centered_axes(R_PadState, R_T4), + store_byte(R_RawId, R_PadState, O_(PadState,id)), + jump_rel(atom_offset(pending, snap_end)), mac_yield_load(), atom_label(id_dispatch) /* === Case 3-6: ID dispatch */ - add_ui(R_T4, R_0, 0x41), branch_ne(R_RawId, R_T4, atom_offset(id_dispatch, try_analog_stick)), + add_ui(R_T4, R_0, PadRawId_Digital), branch_ne(R_RawId, R_T4, atom_offset(id_dispatch, try_analog_stick)), /* BD-slot: pre-compute PadStatus_Digital. Branch reads R_RawId in EX before this WB completes. * If branch NOT taken (fall through to try_analog_stick), R_T4 is overwritten by the analog body add_ui. */ - /* === Digital body (status, buttons normalize, axes=0x80, id, branch. */ - /* R_T4 = PadStatus_Digital from id_dispatch BD-slot. */ - store_word( R_T4, R_PadState, O_(PadState,status)), - load_half_u(R_T4, R_PadRaw, 2 * S_(U1)), - /* Fill R_T4's load-delay slot with the 0x80808080 axes constant into R_T5 - * (R_T5 is dead on this path; it's only consumed at the analog_pad range check). */ - load_upper_i(R_T5, 0x8080), or_i_self(R_T5, 0x8080), - nor_u( R_T4, R_T4, R_0), /* raw_buttons is already in host bit order; no swap needed */ - store_half( R_T4, R_PadState, O_(PadState,buttons)), - - /* axes = 0x80808080 (centered) — single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y). */ - store_word( R_T5, R_PadState, O_(PadState,left_x)), - add_ui( R_T4, R_0, 0x41), - store_byte( R_T4, R_PadState, O_(PadState,id)), + /* === Digital body (status, buttons normalize, axes=0x80, id, branch. + * R_T5 holds the 0x80808080 axes constant (loaded into the load-delay slot of the buttons-load). + * R_T5 is then "dead" — only consumed at the analog_pad range check downstream. */ + mac_pad_set_status(R_T4, R_PadState, PadStatus_Digital), + load_half_u( R_T4, R_PadRaw, O_(PadBiosRaw, buttons)), /* R_T4 = raw_buttons; */ + load_upper_i(R_T5, PadAxis_Centered_Hi), or_i_self(R_T5, PadAxis_Centered_Lo), /* fills the buttons-load's delay slot (doesn't read R_T4) */ + mac_pad_store_inverted_buttons(R_T4, R_PadState), /* R_T4 settled: nor + sh writes ~raw_buttons to state.buttons */ + store_word(R_T5, R_PadState, O_(PadState, axes)), /* single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y) */ + mac_pad_set_id_byte(R_PadState, R_T4, PadRawId_Digital), jump_rel(atom_offset(id_dispatch, snap_end)), mac_yield_load(), atom_label(try_analog_stick) /* === Case 4: AnalogStick (id == 0x53)*/ - add_ui(R_T4, R_0, 0x53), branch_ne(R_RawId, R_T4, atom_offset(try_analog_stick, try_analog_pad)), + add_ui(R_T4, R_0, PadRawId_AnalogStick), branch_ne(R_RawId, R_T4, atom_offset(try_analog_stick, try_analog_pad)), /* BD-slot: pre-compute PadStatus_AnalogStick. Branch reads R_RawId in EX before this WB completes. * If branch NOT taken (fall through to try_analog_pad), R_T4 is overwritten by the analog_pad body add_ui. */ atom_label(analog_stick) /* === AnalogStick body - * Axes are loaded as two halfwords: raw[6..7] → left_xy (sh at offset 8), raw[4..5] → right_xy (sh at offset 10). - * R_T5 holds left_xy / id-value in turn (it's dead on this path — only consumed at the analog_pad range check). */ - /* R_T4 = PadStatus_AnalogStick from try_analog_stick BD-slot. */ - store_word( R_T4, R_PadState, O_(PadState,status)), - load_half_u( R_T4, R_PadRaw, 2 * S_(U1)), /* R_T4 = raw_buttons */ - load_half_u( R_T5, R_PadRaw, 6 * S_(U1)), /* R_T5 = left_xy; fills R_T4's load-delay slot (doesn't read R_T4) */ - nor_u( R_T4, R_T4, R_0), /* R_T4 = ~raw_buttons */ - store_half( R_T4, R_PadState, O_(PadState,buttons)), - load_half_u( R_T4, R_PadRaw, 4 * S_(U1)), /* R_T4 = right_xy; fills R_T5's load-delay slot */ - store_half( R_T5, R_PadState, O_(PadState,left_x)), /* R_T5 settled, store left_xy */ - store_half( R_T4, R_PadState, O_(PadState,right_x)), - add_ui( R_T5, R_0, 0x53), /* R_T5 = id value (clobbers left_xy, already stored) */ - store_byte( R_T5, R_PadState, O_(PadState,id)), + * R_T5 holds left_xy (loaded into the load-delay slot of the buttons-load via the left-axis load_half_u). + * R_T4 holds right_xy (loaded into the load-delay slot of the left-load). + * R_T5 is then "dead" — reused for the id-byte value load in mac_pad_write_id_byte. + * The buttons invert+store happens BEFORE R_T4 is overwritten by the right_xy load. */ + mac_pad_set_status(R_T4, R_PadState, PadStatus_AnalogStick), + load_half_u( R_T4, R_PadRaw, O_(PadBiosRaw,buttons)), /* R_T4 = raw_buttons; delay slot at the next instruction */ + load_half_u( R_T5, R_PadRaw, O_(PadBiosRaw,left)), /* fills the buttons-load's delay slot (doesn't read R_T4) */ + mac_pad_store_inverted_buttons(R_T4, R_PadState), /* R_T4 settled: nor + sh writes ~raw_buttons to state.buttons */ + load_half_u( R_T4, R_PadRaw, O_(PadBiosRaw,right)), /* fills R_T5's load-delay slot (doesn't read R_T5); overwrites R_T4 (was buttons) with right_xy */ + store_half( R_T5, R_PadState, O_(PadState, left)), + store_half( R_T4, R_PadState, O_(PadState, right)), + mac_pad_set_id_byte(R_PadState, R_T5, PadRawId_AnalogStick), jump_rel(atom_offset(analog_stick, snap_end)), mac_yield_load(), atom_label(try_analog_pad) /* === Case 5-6: AnalogPad (id & 0xF0 == 0x70) */ - and_i( R_T4, R_RawId, 0xF0), - add_ui( R_T5, R_0, 0x70), + and_i( R_T4, R_RawId, PadRawId_AnalogPadMask), + add_ui( R_T5, R_0, PadRawId_AnalogPadValue), branch_ne(R_T4, R_T5, atom_offset(try_analog_pad, try_unsupported)), /* BD-slot: pre-compute PadStatus_AnalogPad. Branch reads R_T4 in EX before this WB completes. * If branch NOT taken (fall through to try_unsupported), R_T4 is overwritten by the unsupported body add_ui. */ atom_label(analog_pad) /* === AnalogPad body - * Same shape as AnalogStick with AnalogPad status. R_T5 holds left_xy (it's dead on this path). */ - /* R_T4 = PadStatus_AnalogPad from try_analog_pad BD-slot. */ - store_word( R_T4, R_PadState, O_(PadState,status)), - load_half_u(R_T4, R_PadRaw, 2 * S_(U1)), /* R_T4 = raw_buttons */ - load_half_u(R_T5, R_PadRaw, 6 * S_(U1)), /* R_T5 = left_xy; fills R_T4's load-delay slot */ - nor_u( R_T4, R_T4, R_0), /* R_T4 = ~raw_buttons */ - store_half( R_T4, R_PadState, O_(PadState,buttons)), - load_half_u(R_T4, R_PadRaw, 4 * S_(U1)), /* R_T4 = right_xy; fills R_T5's load-delay slot */ - store_half( R_T5, R_PadState, O_(PadState,left_x)), /* R_T5 settled, store left_xy */ - store_half( R_T4, R_PadState, O_(PadState,right_x)), - store_byte( R_RawId, R_PadState, O_(PadState,id)), + * Same shape as AnalogStick with AnalogPad status. R_T5 holds left_xy (it's dead on this path). + * The id byte is raw id from the BIOS buffer (R_RawId already holds raw[1]). + * Buttons invert + store happens before R_T4 is overwritten by the right_xy load. */ + mac_pad_set_status(R_T4, R_PadState, PadStatus_AnalogPad), + load_half_u( R_T4, R_PadRaw, O_(PadBiosRaw,buttons)), /* R_T4 = raw_buttons; delay slot at the next instruction */ + load_half_u( R_T5, R_PadRaw, O_(PadBiosRaw,left)), /* fills the buttons-load's delay slot (doesn't read R_T4) */ + mac_pad_store_inverted_buttons(R_T4, R_PadState), /* R_T4 settled: nor + sh writes ~raw_buttons to state.buttons */ + load_half_u(R_T4, R_PadRaw, O_(PadBiosRaw,right)), /* fills R_T5's load-delay slot (doesn't read R_T5); overwrites R_T4 with right_xy */ + store_half( R_T5, R_PadState, O_(PadState, left)), + store_half( R_T4, R_PadState, O_(PadState, right)), + store_byte( R_RawId, R_PadState, O_(PadState, id)), jump_rel(atom_offset(analog_pad, snap_end)), mac_yield_load(), @@ -166,11 +180,8 @@ atom_label(try_unsupported) /* === Case 7: Unsupported — fall through from the add_ui( R_T4, R_0, PadStatus_Unsupported), store_word(R_T4, R_PadState, O_(PadState,status)), store_half(R_0, R_PadState, O_(PadState,buttons)), - /* axes = 0x80808080 (centered) — single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y). */ - load_upper_i(R_T4, 0x8080), or_i_self(R_T4, 0x8080), - store_word( R_T4, R_PadState, O_(PadState,left_x)), - add_ui( R_T4, R_0, 0xFF), /* 0xFF sentinel: "unknown id" */ - store_byte( R_T4, R_PadState, O_(PadState,id)), + mac_pad_set_centered_axes(R_PadState, R_T4), + mac_pad_set_id_byte( R_PadState, R_RawId, PadUnknownId_Sentinel), /* Fall through to snap_end. */ atom_label(no_jump_fallthrough) diff --git a/code/duffle/pad.h b/code/duffle/pad.h index 0086edc..7d1da32 100644 --- a/code/duffle/pad.h +++ b/code/duffle/pad.h @@ -5,24 +5,25 @@ /* PSX button bit positions — 1:1 with PSX-SPX docs at docs/psx-spx/docs/controllersandmemorycards.md:405-421. * Wire is active-low (0 = pressed). - * The decoder atom computes buttons = (~raw_buttons) & 0xFFFF; the active-low-to-active-high inversion is applied bit-by-bit. */ -enum { - Bit_(Pad_Select, 0), - Bit_(Pad_L3, 1), - Bit_(Pad_R3, 2), - Bit_(Pad_Start, 3), - Bit_(Pad_Up, 4), - Bit_(Pad_Right, 5), - Bit_(Pad_Down, 6), - Bit_(Pad_Left, 7), - Bit_(Pad_L2, 8), - Bit_(Pad_R2, 9), - Bit_(Pad_L1, 10), - Bit_(Pad_R1, 11), + * The decoder atom computes buttons = (~raw_buttons) & 0xFFFF; + * active-low-to-active-high inversion is applied bit-by-bit. */ +typedef Enum_(U2, PadBtns) { + Bit_(Pad_Select, 0), + Bit_(Pad_L3, 1), + Bit_(Pad_R3, 2), + Bit_(Pad_Start, 3), + Bit_(Pad_Up, 4), + Bit_(Pad_Right, 5), + Bit_(Pad_Down, 6), + Bit_(Pad_Left, 7), + Bit_(Pad_L2, 8), + Bit_(Pad_R2, 9), + Bit_(Pad_L1, 10), + Bit_(Pad_R1, 11), Bit_(Pad_Triangle, 12), - Bit_(Pad_Circle, 13), - Bit_(Pad_Cross, 14), - Bit_(Pad_Square, 15), + Bit_(Pad_Circle, 13), + Bit_(Pad_Cross, 14), + Bit_(Pad_Square, 15), }; enum { @@ -35,15 +36,22 @@ enum { #define pad0_(btn_id) (btn_id << Pad0) #define pad1_(btn_id) (btn_id << Pad1) -/* ============================================================ +/* ============================================================================= * BIOS pad-buffer subsystem: docs/psx-spx/docs/kernelbios.md (B(12h) + B(13h)) - * ============================================================ */ + * ============================================================================= */ enum { PAD_BIOS_RAW_SIZE = 0x22, }; +// BIOS pad buffer layout (docs/psx-spx/docs/kernelbios.md (InitPAD2 returns 0x22 = 34 bytes per port)). +// Bytes 0..7 are the named snapshot region; bytes 8..33 are reserved (the BIOS writes the buffer raw; we only read bytes 0..7 via O_(PadBiosRaw, ...)). typedef Struct_(PadBiosRaw) { - U1 bytes[PAD_BIOS_RAW_SIZE]; + U1 status; /* offset 0 (PadRawStatus_Ok / PadRawStatus_Timeout) */ + U1 id; /* offset 1 (PadRawId_Digital / PadRawId_AnalogStick / 0x7x AnalogPad) */ + U2 buttons; /* offset 2-3 (active-low 16-bit button map) */ + V2_U1 right; /* offset 4-5 (right stick x, y) */ + V2_U1 left; /* offset 6-7 (left stick x, y) */ + U1 reserved[PAD_BIOS_RAW_SIZE - 8]; /* offset 8..33 */ }; typedef Enum_(U4, PadStatus) { @@ -56,18 +64,52 @@ typedef Enum_(U4, PadStatus) { PadStatus_Invalid, }; -/* PadState — per-port normalized runtime state. - * Field order is chosen so that the 4 axes (left_x, left_y, right_x, right_y) - * form a contiguous 4-byte block at offset 8, allowing a single `store_word` to clear-or-write all 4 axes in one MIPS instruction. - * The struct size stays 12 bytes (unchanged from the prior order, - * which left the C compiler to insert 1 byte of trailing pad to reach the 4-byte struct alignment). */ +/* Distinct from the game-facing PadStatus enum: PadRawStatus_Ok and PadRawStatus_Timeout are raw BIOS values; + * PadStatus_* are game-facing post-decode states. PadUnknownId_Sentinel is written by the decoder + * when the controller id does not match any known controller type. + * PadAxisCentered_Word: Four-byte 0x80 pattern used to clear / center + * four byte axes at PadState.left_x through PadState.right_y. */ +typedef Enum_(U1, PadRawStatus) { + PadRawStatus_Ok = 0x00, + PadRawStatus_Timeout = 0xFF, +}; +typedef Enum_(U1, PadRawId) { + PadRawId_Digital = 0x41, + PadRawId_AnalogStick = 0x53, + PadRawId_AnalogPadMask = 0xF0, + PadRawId_AnalogPadValue = 0x70, +}; +typedef Enum_(U1, PadUnknownId) { + PadUnknownId_Sentinel = 0xFF, +}; +typedef Enum_(U4, PadAxisCentered) { + PadAxis_Centered_Hi = 0x8080, + PadAxis_Centered_Lo = 0x8080, + PadAxis_Centered_Word = 0x80808080U, +}; +typedef Enum_(U1, PadDeadZone) { + PadDeadZone_LowBound = 0x70, /* left_x < LowBound → active; delta = 0x80 - left_x > 0 (rightward pull) */ + PadDeadZone_Center = 0x80, /* analog rest position; left_x == Center → delta = 0 (no rotation) */ + PadDeadZone_HighBound = 0x90, /* left_x > HighBound → active; delta = 0x80 - left_x < 0 (leftward pull) */ +}; + + +typedef Struct_(PadAxes) { + V2_U1 left; /* offset 8-9 */ + V2_U1 right; /* offset 10-11 */ +}; +// Field order is chosen so that the 4 axes (left_x, left_y, right_x, right_y) +// form a contiguous 4-byte block at offset 8, allowing a single `store_word` to clear-or-write all 4 axes in one MIPS instruction. typedef Struct_(PadState) { - PadStatus status; /* offset 0, size 4 (U4) */ - U2 buttons; /* offset 4, size 2 */ - U1 id; /* offset 6, size 1 */ - U1 pad; /* offset 7, size 1 — explicit pad to align the axes block */ - U1 left_x; /* offset 8, size 1 — store_word target (4-byte aligned) */ - U1 left_y; /* offset 9, size 1 */ - U1 right_x; /* offset 10, size 1 */ - U1 right_y; /* offset 11, size 1 */ + PadStatus status; /* offset 0, (U4) */ + PadBtns buttons; /* offset 4, */ + U1 id; /* offset 6, */ + byte_pad(1); /* offset 7, explicit pad to align the axes block */ + union { + A2_V2_U1 axes; /* offset 8-11 store_target (4-byte aligned)*/ + struct { + V2_U1 left; /* offset 8-9 */ + V2_U1 right; /* offset 10-11 */ + }; + }; }; diff --git a/code/hello_camera/hello_camera.atom.c b/code/hello_camera/hello_camera.atom.c index 29b0ad6..ce64f55 100644 --- a/code/hello_camera/hello_camera.atom.c +++ b/code/hello_camera/hello_camera.atom.c @@ -246,21 +246,21 @@ internal MipsAtom_(pad_apply_input) atom_info(atom_bind(Binds_PadApplyInput) /* Analog left-stick X: dead zone 0x70..0x90. * Cube delta = (0x80 - left_x) >> 2; floor delta = (0x80 - left_x) >> 5. */ - load_byte_u(R_T3, R_PadStateT5, O_(PadState,left_x)), + load_byte_u(R_T3, R_PadStateT5, O_(PadState,left.x)), /* Dead-zone check: skip analog if left_x in [0x70, 0x90] inclusive. Outside dead zone on LOW side: left_x < 0x70 (strictly). * set_lt_u(R_T4, R_T3, R_T4=0x70) → R_T4 = (left_x < 0x70) ? 1 : 0. */ - add_ui(R_T4, R_0, 0x70), set_lt_u(R_T4, R_T3, R_T4), branch_ne(R_T4, R_0, atom_offset(dead_zone_low_check, dead_low_active)), - add_ui(R_T4, R_0, 0x80), /* BD-slot: pre-load 0x80 for dead_low_active */ + add_ui(R_T4, R_0, PadDeadZone_HighBound), set_lt_u(R_T4, R_T3, R_T4), branch_ne(R_T4, R_0, atom_offset(dead_zone_low_check, dead_low_active)), + add_ui(R_T4, R_0, PadDeadZone_Center), /* BD-slot: pre-load 0x80 for dead_low_active */ atom_label(dead_check_upper) /* left_x >= 0x70 → check upper bound. */ - load_byte_u(R_T3, R_PadStateT5, O_(PadState,left_x)), /* reload */ - add_ui( R_T4, R_0, 0x90), + load_byte_u(R_T3, R_PadStateT5, O_(PadState,left.x)), /* reload */ + add_ui( R_T4, R_0, PadDeadZone_HighBound), /* R_T4 = (0x90 < left_x) ? 1 : 0 → (left_x > 0x90) ? 1 : 0 */ set_lt_u(R_T4, R_T4, R_T3), branch_ne(R_T4, R_0, atom_offset(dead_zone_high_check, dead_high_active)), - add_ui( R_T4, R_0, 0x80), /* BD-slot: pre-load 0x80 for dead_high_active */ + add_ui( R_T4, R_0, PadDeadZone_Center), /* BD-slot: pre-load 0x80 for dead_high_active */ jump_rel(atom_offset(dead_zone_skip, exit_stick)), mac_yield_load(), @@ -273,8 +273,7 @@ atom_label(dead_low_active) /* R_T4 = cube_delta */ shift_aright(R_T4, R_T3, 2), - load_half( R_T0, R_CubeRot, O_(V3_S2,y)), - nop, + load_half( R_T0, R_CubeRot, O_(V3_S2,y)), nop, add_u( R_T0, R_T0, R_T4), store_half( R_T0, R_CubeRot, O_(V3_S2,y)), /* R_T4 = floor_delta — moved into the load-delay slot of the floor load below (fills the 1-instruction gap; @@ -295,8 +294,7 @@ atom_label(dead_high_active) /* delta = 0x80 - left_x (signed negative). */ shift_aright(R_T4, R_T3, 2), /* R_T4 = cube_delta (signed) */ - load_half( R_T0, R_CubeRot, O_(V3_S2,y)), - nop, + load_half( R_T0, R_CubeRot, O_(V3_S2,y)), nop, add_u( R_T0, R_T0, R_T4), store_half( R_T0, R_CubeRot, O_(V3_S2,y)), @@ -344,7 +342,7 @@ internal MipsAtom_(rbind_cube_g4_face) atom_info(atom_bind(Binds_CubeTri), atom_ mac_yield() }; - // cube_g4_face — Draw one cube face (Gouraud-shaded quad) via the GTE tape pipeline +// cube_g4_face — Draw one cube face (Gouraud-shaded quad) via the GTE tape pipeline internal MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4), atom_reads( R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase), @@ -379,7 +377,7 @@ MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4), set_lt_u( R_AT, R_T1, R_AT), branch_equal(R_AT, R_0, atom_offset(bounds_chk, cube_g4_face_exit)), nop, - mac_insert_ot_tag_g4(R_OtBase, R_PrimCursor), + mac_insert_ot_tag(R_OtBase, R_PrimCursor, S_(Poly_G4)), mac_format_g4_color(R_PrimCursor, /* c0 magenta */ 0xFF, 0x00, 0xFF, /* c1 yellow */ 0xFF, 0xFF, 0x00, @@ -439,7 +437,7 @@ MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3) set_lt_u( R_AT, R_T1, R_AT), branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop, mac_format_f3_color(R_PrimCursor, 0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white) - mac_insert_ot_tag_f3(R_OtBase, R_PrimCursor), /* Insert into Ordering Table Linked List */ + mac_insert_ot_tag(R_OtBase, R_PrimCursor, S_(Poly_F3)), /* Insert into Ordering Table Linked List */ add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */ // Note(Ed): No bounds checking, should be checked before atom runs. // end: branch(bounds_chk) diff --git a/code/hello_camera/hello_camera.c b/code/hello_camera/hello_camera.c index 4fa7021..ebbdaa0 100644 --- a/code/hello_camera/hello_camera.c +++ b/code/hello_camera/hello_camera.c @@ -163,14 +163,14 @@ void update(PrimitiveArena* pa, U4* ordering_buf) if (1) // Pad Input { tb.used = 0; tb_scope_run(& tb) { - /* BIOS-owned polling: per-frame snapshot of both ports. */ + // Grab latest state from bios. tb_emit_(pad_bios_snapshot); - tb_data_(raw, & smem.pad_raw[0]); + tb_data_(raw, & smem.pad_raw[0]); tb_data_(state, & smem.pad[0]); tb_emit_(pad_bios_snapshot); tb_data_(raw, & smem.pad_raw[1]); tb_data_(state, & smem.pad[1]); - /* Per-frame rotation apply: consume pad[0].buttons + pad[0].left_x */ + // Demo input tb_emit_(pad_apply_input); tb_data_(state, & smem.pad[0]); tb_data_(cube_rot, & smem.cube.rot); @@ -253,7 +253,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf) tb_data(& tb, u4_(& smem.tform_world)); tb_emit(& tb, rbind_floor_f3_face); - // TODO(Ed): Just use a single context struct ref + // TODO(Ed): Just use a single context struct ref? tb_data(& tb, prim_cursor); tb_data(& tb, u4_(smem.floor.faces)); tb_data(& tb, u4_(smem.floor.verts));