#ifdef INTELLISENSE_DIRECTIVES # pragma once # include "dsl.h" # include "gcc_asm.h" # include "mips.h" # include "gte.h" # include "memory.h" #endif /* --------------------------------------------------------------------------- * TAPE DRIVE ABI & REGISTER ALIASES * --------------------------------------------------------------------------- * We map the MIPS temporary registers to a persistent global workspace. * The C compiler is completely unaware of these bindings. * ---------------------------------------------------------------------------*/ enum { R_TapePtr = R_T8, /* The Instruction Stream Pointer */ R_PrimCur = R_T7, /* VRAM output cursor (primitive buffer) */ R_FaceCur = R_T4, /* Input data cursor (indices/faces) */ R_InCursor = R_T4, /* Input data cursor (indices/faces) */ R_VertBase = R_T5, /* Base address of the vertex array */ R_OtBase = R_T6, /* Base address of the Ordering Table */ /* Stringification codes for the GCC inline assembler clobber lists */ #define R_TapePtr_Code R_T8_Code #define R_PrimCur_Code R_T7_Code #define R_FaceCur_Code R_T4_Code #define R_InCursor_Code R_T4_Code #define R_VertBase_Code R_T5_Code #define R_OtBase_Code R_T6_Code }; /* The 'Yield' sequence for Tape Atoms. * Loads the next pointer from the tape, advances the tape, and jumps. * Cost: ~ 4 cycles */ #define mips_yield() \ load_word(R_T9, R_TapePtr, 0) \ , add_ui_1( R_TapePtr, 4) \ , jump_reg( R_T9) \ , nop /* The 'Exit' Atom */ MipsAtom_(tape_exit) { jump_reg(rret_addr), nop }; typedef Slice_(U4); /* Generalized Tape Engine Runner */ FI_ void tape_run(Slice_U4 tape) { register U4* tp rgcc(R_TapePtr) = tape.ptr; asm volatile( asm_words( add_ui( R_SP, R_SP, -8) /* Allocate stack space */ , store_word(R_RA, R_SP, 0) /* Safely backup $ra to the stack */ , load_word( R_T9, R_TapePtr, 0) /* Bootstrap the first jump */ , add_ui_1( R_TapePtr, 4) /* Advance tape */ , jump_nreg(R_T9) /* jalr $t9 */ , nop /* Branch delay slot */ , load_word(R_RA, R_SP, 0) /* Restore $ra from stack */ , add_ui_1( R_SP, 8) /* Deallocate stack space */ ) asm_rpins, r_use(tp) asm_clobber: rlit(R_AT) , rlit(R_V0), rlit(R_V1) , rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3) /* Tell GCC the tape engine owns and destroys the workspace registers */ , rlit(R_PrimCur), rlit(R_FaceCur), rlit(R_VertBase), rlit(R_OtBase) , rlit(R_T9) , clb_mem_drain ); } typedef Struct_(TapeBuilder) { U4 ptr; U4 count; }; FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start; tb->count = 0; } FI_ TapeBuilder tb_make( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; } FI_ void tb_emit(TapeBuilder* tb, MipsCode* atom) { u4_r(tb->ptr)[tb->count] = u4_(atom); ++ tb->count; } FI_ void tb_data(TapeBuilder* tb, U4 data) { u4_r(tb->ptr)[tb->count] = u4_(data); ++ tb->count; } FI_ Slice_U4 tb_end (TapeBuilder* tb) { tb_emit(tb,code_tape_exit); return (Slice_U4){ C_(U4*,tb->ptr), tb->count }; } FI_ Slice_U4 tb_slice(TapeBuilder tb) { return (Slice_U4){ C_(U4*,tb.ptr), tb.count }; } #define tb_scope(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_emit(tb,code_tape_exit)) /* --------------------------------------------------------------------------- * MACRO ATOM Components (Reusable Assembly Components) * These do NOT yield. They are expanded inline inside Tape Atoms. * ---------------------------------------------------------------------------*/ /* Loads 3 16-bit indices from the face array */ #define mac_load_tri_indices(rId_0, rId_1, rId_2) \ load_half_u(rId_0, R_FaceCur, 0) \ , load_half_u(rId_1, R_FaceCur, 2) \ , load_half_u(rId_2, R_FaceCur, 4) /* Translates indices to vertex addresses and pushes them to GTE */ #define mac_load_tri_verts(rId_0, rId_1, rId_2) \ shift_ll(R_AT, rId_0, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY0), gte_mt(R_V1, C2_VZ0) \ , shift_ll(R_AT, rId_1, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY1), gte_mt(R_V1, C2_VZ1) \ , shift_ll(R_AT, rId_2, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY2), gte_mt(R_V1, C2_VZ2) /* Formats the primitive memory layout (Tag + Color + Coordinates) */ #define mac_format_prim_f3(color_hi, color_lo) \ store_word(R_0, R_PrimCur, 0) \ , load_ui(R_AT, color_hi), or_i(R_AT, R_AT, color_lo) \ , store_word(R_AT, R_PrimCur, 4) \ , gte_sw(C2_SXY0, R_PrimCur, 8) \ , gte_sw(C2_SXY1, R_PrimCur, 12) \ , gte_sw(C2_SXY2, R_PrimCur, 16) /* Correctly inserts a primitive into the Ordering Table linked list */ #define mac_insert_ot_tag(r_otz, prim_length) \ shift_ll( R_T1, r_otz, 2) \ , add_u( R_T1, R_T1, R_OtBase) /* T1 = &OrderingTable[OTZ] */ \ , load_word( R_AT, R_T1, 0) /* AT = old_ot_head */ \ , load_ui( R_V0, prim_length) /* V0 = Length << 24 */ \ , shift_ll( R_AT, R_AT, 8) /* Strip upper 8 bits from old_ot */ \ , shift_lr( R_AT, R_AT, 8) \ , or_u( R_AT, R_AT, R_V0) /* Merge length */ \ , store_word(R_AT, R_PrimCur, 0) /* prim->tag = old_ot_head */ \ , shift_ll( R_AT, R_PrimCur, 8) /* AT = PrimCur & 0x00FFFFFF */ \ , shift_lr( R_AT, R_AT, 8) \ , store_word(R_AT, R_T1, 0) /* OrderingTable[OTZ] = PrimCur */ internal MipsAtom_(bind_workspace) { /* Pop 4 arguments from the tape directly into the workspace registers */ load_word(R_PrimCur, R_TapePtr, 0), load_word(R_FaceCur, R_TapePtr, 4), load_word(R_VertBase, R_TapePtr, 8), load_word(R_OtBase, R_TapePtr, 12), add_ui_1( R_TapePtr, 16), mips_yield() }; internal MipsAtom_(sync_prim_cursor) { /* Pop the C-struct address and base address from the tape */ load_word(R_AT, R_TapePtr, 0), /* AT = &pa->used */ load_word(R_T0, R_TapePtr, 4), /* T0 = prim_base */ add_ui_1( R_TapePtr, 8), /* Calculate byte offset and store directly back to RAM */ sub_u(R_T0, R_PrimCur, R_T0), store_word(R_T0, R_AT, 0), mips_yield() }; internal MipsAtom_(set_gte_world) { /* Pop matrix address from tape into R_T3 ($11) */ load_word(R_T3, R_TapePtr, 0), add_ui_1( R_TapePtr, 4), /* 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_ct( R_T0, gte_cr_RT11), gte_ct( 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_ct( R_T0, gte_cr_RT13), gte_ct( R_T1, gte_cr_RT21), gte_ct( 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_ct( R_T0, gte_cr_TRX), gte_ct( R_T1, gte_cr_TRY), gte_ct( R_T2, gte_cr_TRZ), mips_yield() }; /* ============================================================================ * cube_tri — Draw one cube face (Gouraud-shaded quad) via the GTE tape pipeline * ============================================================================ * * Reads 4 indices from R_FaceCur (V4_S2 = 8 bytes), loads 4 vertices into * the GTE, runs the PsyQ RotAverageNclip4 sequence, and renders a Poly_G4. * * PsyQ RotAverageNclip4 sequence: * 1. Load V0=p0, V1=p1, V2=p2 * 2. RTPT → SXY0=p0, SXY1=p1, SXY2=p2, SZ1,SZ2,SZ3 * 3. NCLIP → MAC0 = cross(p0,p1,p2) ← BEFORE RTPS! * 4. Store SXY0 (p0) to primitive buffer * 5. Load V3 into V0 * 6. RTPS → SXY0=p3, SZ0 * 7. Store SXY0 (p3) to primitive buffer * 8. AVSZ3 → OTZ from SZ1,SZ2,SZ3 * * PRIMITIVE FORMAT (Poly_G4 = 9 words = 36 bytes) * ------------------------------------------------ * Word 0 (offset 0): OT tag (set by mac_insert_ot_tag) * Word 1 (offset 4): c0 + code = 0x38FF00FF (magenta, opcode 0x38) * Word 2 (offset 8): p0 = SXY0 (stored BEFORE RTPS) * Word 3 (offset 12): c1 + pad = 0x0000FFFF (yellow) * Word 4 (offset 16): p1 = SXY1 * Word 5 (offset 20): c2 + pad = 0x00FFFF00 (cyan) * Word 6 (offset 24): p2 = SXY2 * Word 7 (offset 28): c3 + pad = 0x0000FF00 (green) * Word 8 (offset 32): p3 = SXY0 (stored AFTER RTPS) * * BRANCH OFFSETS * ---------------------------------------------- * Outer branch (backface cull): branch_le_zero(R_T0, 49) * → Skip 49 instructions from BD slot, land at add_ui(R_FaceCur,...) * Inner branch (OTZ bounds): branch_equal(R_AT, R_0, 13) * → Skip 13 instructions from BD slot, land at add_ui(R_FaceCur,...) * ============================================================================ */ internal MipsAtom_(cube_tri) { /* ── 1. Load 4 face indices from R_FaceCur ──────────────────────────── */ load_half_u(R_T0, R_FaceCur, 0), /* T0 = face->x (vertex 0 index) */ load_half_u(R_T1, R_FaceCur, 2), /* T1 = face->y (vertex 1 index) */ load_half_u(R_T2, R_FaceCur, 4), /* T2 = face->z (vertex 2 index) */ load_half_u(R_T3, R_FaceCur, 6), /* T3 = face->w (vertex 3 index) */ /* ── 2. Load V0, V1, V2 into GTE ────────────────────────────────────── */ /* V0 = verts[face->x] */ shift_ll(R_AT, R_T0, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY0), gte_mt(R_V1, C2_VZ0), /* V1 = verts[face->y] */ shift_ll(R_AT, R_T1, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY1), gte_mt(R_V1, C2_VZ1), /* V2 = verts[face->z] */ shift_ll(R_AT, R_T2, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY2), gte_mt(R_V1, C2_VZ2), /* ── 3. RTPT — transforms V0/V1/V2 → SXY0/SXY1/SXY2 + SZ1/SZ2/SZ3 ─── */ nop, nop, gte_cmdw_rtpt, /* ── 4. NCLIP — backface culling on SXY0/SXY1/SXY2 (p0,p1,p2) ──────── */ /* MUST be done BEFORE RTPS overwrites SXY0 with p3! */ nop, nop, gte_cmdw_nclip, nop, nop, /* ── 5. Cull check: skip format/insert if MAC0 ≤ 0 (backface) ───────── */ gte_mf(R_T0, C2_MAC0), nop, branch_le_zero(R_T0, 49), /* Skip 49 if MAC0 ≤ 0 (backface) → cull */ nop, /* BD slot */ /* ── 6. Store p0,p1,p2 to primitive buffer (BEFORE RTPS overwrites) ─── */ store_word(R_0, R_PrimCur, 0), /* Word 1: c0 (BGR) + code = 0x38FF00FF (magenta, opcode 0x38) */ load_ui(R_AT, 0x38FF), or_i(R_AT, R_AT, 0x00FF), store_word(R_AT, R_PrimCur, 4), /* Word 2: p0 = SXY0 (stored BEFORE RTPS overwrites it) */ gte_sw(C2_SXY0, R_PrimCur, 8), /* Word 3: c1 (BGR) + pad = 0x0000FFFF (yellow) */ load_ui(R_AT, 0x0000), or_i(R_AT, R_AT, 0xFFFF), store_word(R_AT, R_PrimCur, 12), /* Word 4: p1 = SXY1 */ gte_sw(C2_SXY1, R_PrimCur, 16), /* Word 5: c2 (BGR) + pad = 0x00FFFF00 (cyan) */ load_ui(R_AT, 0x00FF), or_i(R_AT, R_AT, 0xFF00), store_word(R_AT, R_PrimCur, 20), /* Word 6: p2 = SXY2 */ gte_sw(C2_SXY2, R_PrimCur, 24), /* Word 7: c3 (BGR) + pad = 0x0000FF00 (green) */ load_ui(R_AT, 0x0000), or_i(R_AT, R_AT, 0xFF00), store_word(R_AT, R_PrimCur, 28), /* ── 7. Load V3 = verts[face->w] into V0 ─────────────────────────────── */ shift_ll(R_AT, R_T3, 3), add_u(R_AT, R_AT, R_VertBase), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt(R_V0, C2_VXY0), gte_mt(R_V1, C2_VZ0), /* ── 8. RTPS — transforms V0 (now V3) → SXY0 (p3) + SZ0 ─────────────── */ nop, nop, gte_cmdw_rtps, /* Word 8: p3 = SXY0 (written AFTER RTPS with V3's screen coords) */ gte_sw(C2_SXY0, R_PrimCur, 32), /* ── 9. AVSZ4 — average Z from SZ0/SZ1/SZ2/SZ3 ────────────── */ nop, nop, gte_cmdw_avsz4, nop, nop, gte_mf(R_T1, C2_OTZ), /* ── 10. Bounds check OTZ < 2048 ─────────────────────────────────────── */ add_ui( R_AT, R_0, 2048), slt_u( R_AT, R_T1, R_AT), branch_equal(R_AT, R_0, 13), /* Skip 13 → land at add_ui(R_FaceCur,...) */ nop, /* BD slot */ /* ── 11. Insert into Ordering Table (length = 8 for Poly_G4) ─────────── */ mac_insert_ot_tag(R_T1, 0x0800), /* 0x0800 = 8 << 8 = length 8 in tag */ /* ── 12. Advance cursors & yield ─────────────────────────────────────── */ add_ui(R_PrimCur, R_PrimCur, 36), /* 9 words × 4 bytes */ add_ui(R_FaceCur, R_FaceCur, 8), /* 4 × S2 = 8 bytes */ mips_yield() }; internal MipsAtom_(floor_tri) { mac_load_tri_indices(R_T0, R_T1, R_T2), mac_load_tri_verts( R_T0, R_T1, R_T2), /* 3. Execute Math */ nop, nop, gte_cmdw_rtpt, nop, nop, gte_cmdw_nclip, nop, nop, /* 4. Culling (Branch forward 29 instructions if Backface) */ gte_mf(R_T0, C2_MAC0), nop, branch_le_zero(R_T0, 29), nop, /* 5. Format Primitive */ mac_format_prim_f3(0x20FF, 0xFFFF), /* High: 0x20/B, Low: G/R */ /* 6. Calculate Depth */ nop, nop, gte_cmdw_avsz3, nop, nop, gte_mf(R_T1, C2_OTZ), /* 7. Bounds Check OTZ < 2048 (Branch forward 13 instructions to skip insertion) */ add_ui( R_AT, R_0, 2048), slt_u( R_AT, R_T1, R_AT), branch_equal(R_AT, R_0, 13), nop, /* 8. Insert into Ordering Table Linked List */ mac_insert_ot_tag(R_T1, 0x0400), /* Length = 4 words */ add_ui(R_PrimCur, R_PrimCur, 20), /* Advance Prim Cursor (5 words) */ /* 9. Advance Input Cursor & Yield (Both branch targets land here) */ add_ui(R_FaceCur, R_FaceCur, 8), /* Advance Face Cursor (4 * S2 = 8 bytes) */ mips_yield() }; /* DIAGNOSTIC 1: Pure tape loop test */ internal MipsAtom_(diag_yield) { mips_yield() }; /* DIAGNOSTIC 2: Pure memory test (No GTE). Draws a fixed cyan triangle. */ internal MipsAtom_(diag_color) { store_word(R_0, R_T7, 0), load_ui( R_AT, 0x20FF), /* High: MipsCode 0x20 + Color B:FF */ or_i( R_AT, R_AT, 0xFF00), /* Low: Color G:FF, R:00 (Cyan) */ store_word(R_AT, R_T7, 4), /* Fake coordinates - Swapped winding order to prevent GPU culling! */ load_ui(R_AT, 0x0010), or_i(R_AT, R_AT, 0x0010), store_word(R_AT, R_T7, 8), /* (16, 16) */ load_ui(R_AT, 0x0050), or_i(R_AT, R_AT, 0x0010), store_word(R_AT, R_T7, 12), /* (80, 16) */ load_ui(R_AT, 0x0010), or_i(R_AT, R_AT, 0x0050), store_word(R_AT, R_T7, 16), /* (16, 80) */ add_ui( R_T1, R_0, 10), shift_ll(R_T1, R_T1, 2), add_u( R_T1, R_T1, R_T6), load_word( R_AT, R_T1, 0), load_ui( R_V0, 0x0400), // <--- Fills load delay slot! store_word(R_AT, R_T7, 0), shift_ll( R_AT, R_T7, 8), shift_lr(R_AT, R_AT, 8), or_u( R_AT, R_AT, R_V0), store_word(R_AT, R_T1, 0), add_ui(R_T7, R_T7, 20), mips_yield() }; /* DIAGNOSTIC 3: Pure GTE test (No Memory Writes) */ internal MipsAtom_(diag_gte) { /* Load 3 indices */ load_half_u(R_T0, R_T4, 0), load_half_u(R_T1, R_T4, 2), load_half_u(R_T2, R_T4, 4), /* Load Vertices into GTE */ shift_ll( R_AT, R_T0, 3), add_u( R_AT, R_AT, R_T5), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt( R_V0, C2_VXY0), gte_mt( R_V1, C2_VZ0), shift_ll( R_AT, R_T1, 3), add_u( R_AT, R_AT, R_T5), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt( R_V0, C2_VXY1), gte_mt( R_V1, C2_VZ1), shift_ll( R_AT, R_T2, 3), add_u( R_AT, R_AT, R_T5), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mt( R_V0, C2_VXY2), gte_mt( R_V1, C2_VZ2), /* Run Math */ nop, nop, gte_cmdw_rtpt, nop, nop, gte_cmdw_nclip, nop, nop, /* Advance Face Cursor and Yield */ add_ui(R_T4, R_T4, 8), mips_yield() };