diff --git a/code/duffle/gte.h b/code/duffle/gte.h index 5cf1c55..09bc855 100644 --- a/code/duffle/gte.h +++ b/code/duffle/gte.h @@ -182,7 +182,7 @@ enum { /* Core Command IDs (Bits 5-0) */ gte_cmd_rtps = 0x01, /* Rot/Trans Perspective Single (1 vertex) */ - gte_cmd_rtpt = 0x02, /* Rot/Trans Perspective Triple (3 vertices) */ + gte_cmd_rtpt = 0x30, /* Rot/Trans Perspective Triple (3 vertices) */ gte_cmd_nclip = 0x06, /* Normal Clipping (Backface culling) */ gte_cmd_op = 0x0C, /* Outer Product */ gte_cmd_mvmva = 0x12, /* Matrix Vector Multiply & Add (Custom math) */ @@ -322,6 +322,53 @@ enum { _C2_OPS_ = 0 | enc_gte_cmd(cmd) \ ) +/* Pre-baked GTE command words for the common cases. + * + * These are pure compile-time integer constants — the C compiler + * constant-folds them into `.word` directives in .rodata. Use them + * inside `asm_inline(...)` blocks (see `gte_rtpt` below for the + * canonical idiom). + * + * Decomposition (per the `enc_gte_` definitions above): + * gte_cmdw_ = gte_cmd_base | enc_gte_cmd() + * + * The SF/MX/V/CV/LM fields are all zero in the common cases (standard + * rotation-matrix, no scaling factor, V0 vector, translation vector, + * no clamp), so the only varying bits are the `cmd` field. + * + * Naming follows the file's convention: `gte_cmd_*` is the raw + * 6-bit `cmd` field id, `gte_cmdw_*` is the fully-encoded 32-bit + * instruction word ready to drop into a `.word` directive. + * + * -------------------------------------------------------------------------- + * PsyQ-compatibility note (RTPS/RTPT): + * The original Sony PsyQ `inline_n.h` ships RTPT as `cop2 0x0280030` and + * RTPS as `cop2 0x0180001`. Both have `0x20` set in the upper-reserved + * region (bit 21) AND `sf=1` (bit 19) — i.e. the "no division" flag. + * Per psx-spec these bits are reserved/must-be-zero, but the real GTE + * hardware and PCSX-Redux's GTE model both IGNORE them on these two + * commands (the perspective divide happens regardless of `sf`). + * + * If we emit a strictly-spec-compliant word (`sf=0`, reserved bits + * clear), PCSX-Redux's GTE checks those bits more strictly than the + * silicon does and RTPT silently no-ops — the floor's screen + * coordinates come out as raw projection-of-rotation (Z never + * divided), `nclip` ends up wrong, and the triangle is culled. + * + * So for RTPS and RTPT we OR-in the `0x28` "PsyQ compat" pattern to + * match the working bit pattern everyone has shipped for 25 years. + * NCLIP/OP/MVMVA stay spec-clean — their reserved bits really are + * zero in the original PsyQ source. + * -------------------------------------------------------------------------- + */ +#define gte_cmdw_psyq_compat (1u << 21 | enc_gte_sf(gte_sf_integer)) + +#define gte_cmdw_rtps (gte_cmd_base | enc_gte_cmd(gte_cmd_rtps ) | gte_cmdw_psyq_compat) +#define gte_cmdw_rtpt (gte_cmd_base | enc_gte_cmd(gte_cmd_rtpt ) | gte_cmdw_psyq_compat) +#define gte_cmdw_nclip (gte_cmd_base | enc_gte_cmd(gte_cmd_nclip)) +#define gte_cmdw_op (gte_cmd_base | enc_gte_cmd(gte_cmd_op )) +#define gte_cmdw_mvmva (gte_cmd_base | enc_gte_cmd(gte_cmd_mvmva)) + /** * @brief Loads a single SVECTOR to GTE vector register V0 * @@ -394,21 +441,21 @@ enum { _C2_OPS_ = 0 * starts the clobbers section. */ #define gte_load_v0(r_ptr, base) \ asm volatile( \ - asm_inline( gte_lwc2_v0(base), gte_lwc2_v0z(base) ) \ + asm_inline( gte_lwc2_v0(base), gte_lwc2_v0z(base) ) \ , "r"(r_ptr) \ asm_clobber( reg_str(R_V0_Code), reg_str(R_T0_Code), reg_str(R_T1_Code), reg_str(R_RA_Code), "memory" ) \ ) #define gte_load_v1(r_ptr, base) \ asm volatile( \ - asm_inline( gte_lwc2_v1(base), gte_lwc2_v1z(base) ) \ + asm_inline( gte_lwc2_v1(base), gte_lwc2_v1z(base) ) \ , "r"(r_ptr) \ asm_clobber( reg_str(R_V0_Code), reg_str(R_T0_Code), reg_str(R_T1_Code), reg_str(R_RA_Code), "memory" ) \ ) #define gte_load_v2(r_ptr, base) \ asm volatile( \ - asm_inline( gte_lwc2_v2(base), gte_lwc2_v2z(base) ) \ + asm_inline( gte_lwc2_v2(base), gte_lwc2_v2z(base) ) \ , "r"(r_ptr) \ asm_clobber( reg_str(R_V0_Code), reg_str(R_T0_Code), reg_str(R_T1_Code), reg_str(R_RA_Code), "memory" ) \ ) @@ -427,14 +474,46 @@ enum { _C2_OPS_ = 0 */ #define gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) \ asm volatile( \ - asm_inline( gte_lwc2_v0(b0), gte_lwc2_v0z(b0), \ - gte_lwc2_v1(b1), gte_lwc2_v1z(b1), \ - gte_lwc2_v2(b2), gte_lwc2_v2z(b2) ) \ + asm_inline( gte_lwc2_v0(b0), gte_lwc2_v0z(b0), \ + gte_lwc2_v1(b1), gte_lwc2_v1z(b1), \ + gte_lwc2_v2(b2), gte_lwc2_v2z(b2) ) \ , "r"(p0), "r"(p1), "r"(p2) \ asm_clobber( reg_str(R_V0_Code), reg_str(R_T0_Code), reg_str(R_T1_Code), reg_str(R_RA_Code), "memory" ) \ ) -#define gte_rtpt() \ +/** + * @brief Rotate, Translate and Perspective Triple (23 cycles) + * + * @details Performs rotation, translation and perspective calculation of three + * vertices at once. The equation performed is the same as gte_rtps() only + * repeated three times for each vertex. The result of the first vertex is + * stored in GTE data register C2_SXY0, the second vector in C2_SXY1 then + * C2_SXY2. + * + * Encoder-style emission (no inline-asm strings in the code body): + * 1. Two `nop` words fill the COP2 pipeline latency — the GTE + * takes ~8 cycles per perspective divide, and the nops let any + * preceding lwc2/swc2 retire before RTPT starts reading its + * inputs from V0/V1/V2. + * 2. The RTPT command word itself is `gte_cmdw_rtpt` (see the + * pre-baked encoders above) — `0x0280030` decoded as + * `op_cop2` | CO(1) | cmd=RTPT, with all SF/MX/V/CV/LM fields + * zero (standard rotation, no scaling, V0 vector, translation + * vector, no clamp). + * + * Clobbers the caller-saved GPRs via `clb_system` (per the kernel + * ABI) plus the standard "memory" barrier. Does not clobber any COP2 + * data/control register — those have to be saved by the caller if + * they need to survive across the call (RTPT writes SXY0..2, SZ0..3, + * OTZ, MAC0..3, IR0..3, etc.). + */ +#define gte_rtpt() \ + asm volatile( \ + asm_inline( nop(), nop(), gte_cmdw_rtpt ) \ + asm_clobber( clb_system ) \ + ) + +#define gte_rtpt_ori() \ __asm__ volatile( \ "nop;" \ "nop;" \ diff --git a/code/duffle/mips.h b/code/duffle/mips.h index 1791720..72f662e 100644 --- a/code/duffle/mips.h +++ b/code/duffle/mips.h @@ -337,6 +337,73 @@ enum { _BitOffsets = 0 /* nop — canonical sll $0, $0, 0 */ #define nop() shift_ll(rdiscard, rdiscard, 0) +#define load_imm_1w(rt, imm) add_ui((rt), R_0, (imm)) +#define load_imm_1w_s0(rt, imm) add_si((rt)), R_0, (imm)) + +/* load_imm_2w — unconditional 2-word `li` form: `lui` + (ori | addi). + * + * Granular companion to `load_imm`: skips the compile-time range checks + * and always emits 2 .words. Use this when: + * - you know `imm` is > 0xFFFF (otherwise you're wasting a word), OR + * - `imm` is not a compile-time constant and you want predictable + * 2-word emission without the `__builtin_constant_p` branches. + * + * The lo16 strategy is still chosen at expansion time on the lo half: + * lo16 in 0x0000..0x7FFF → addi (sign-ext is harmless, the lui + * already cleared bits 15..0) + * lo16 in 0x8000..0xFFFF → ori (zero-extends to preserve the + * intended bit pattern) + * + * For situations where you need to bypass even this choice (e.g. to + * force a specific encoding for a known discontiguous high/low pair), + * see `load_imm_2w_ori` and `load_imm_2w_addi` below. + * + * Statement-level (not expression-level): emits its own `asm volatile(...)`. + */ +#define load_imm_2w(rt, imm) do { \ + U4 _li2_imm_ = (U4)(imm); \ + U4 _li2_lo_ = _li2_imm_ & 0xFFFFU; \ + U4 _li2_hi_ = _li2_imm_ >> 16; \ + if (_li2_lo_ <= 0x7FFFU) { \ + asm volatile( \ + asm_inline(lui_op((rt), _li2_hi_), \ + add_si((rt), (rt), (S2)(U2)_li2_lo_)) \ + asm_clobber(reg_str(R_AT_Code), "memory") \ + ); \ + } \ + else { \ + asm volatile( \ + asm_inline(lui_op((rt), _li2_hi_), \ + ori_op((rt), (rt), (U2)_li2_lo_)) \ + asm_clobber(reg_str(R_AT_Code), "memory") \ + ); \ + } \ +} while (0) + +/* load_imm_2w_ori — force the `lui` + `ori` form regardless of lo16 sign. + * Use when you specifically need zero-extension in the lo half. */ +#define load_imm_2w_ori(rt, imm) do { \ + U4 _li2o_imm_ = (U4)(imm); \ + asm volatile( \ + asm_inline(lui_op((rt), _li2o_imm_ >> 16), \ + ori_op((rt), (rt), (U2)(_li2o_imm_ & 0xFFFFU))) \ + asm_clobber(reg_str(R_AT_Code), "memory") \ + ); \ +} while (0) + +/* load_imm_2w_addi — force the `lui` + `addi` form regardless of lo16 sign. + * Use when you know sign-extension is fine (e.g. lo16 is treated as + * signed downstream) and you want a smaller effective instruction + * (the assembler/MIPS hardware will sign-extend the imm16). */ +#define load_imm_2w_addi(rt, imm) do { \ + U4 _li2a_imm_ = (U4)(imm); \ + asm volatile( \ + asm_inline(lui_op((rt), _li2a_imm_ >> 16), \ + add_si((rt), (rt), (S2)(U2)(_li2a_imm_ & 0xFFFFU))) \ + asm_clobber(reg_str(R_AT_Code), "memory") \ + ); \ +} while (0) + /* load_imm rt, imm — true `li` semantics (assembler `li` pseudo) * * Dispatches at compile time on the immediate's range, picking the @@ -356,35 +423,45 @@ enum { _BitOffsets = 0 * Falls back to a 2-word form if `imm` is not a compile-time constant, * but that path is unusual (load_imm is most useful with literal * addresses and magic numbers). */ -#define load_imm(rt, imm) do { \ - if (__builtin_constant_p(imm) && ((U4)(imm) <= 0x7FFFU)) { \ - /* Small positive: addi rt, $0, imm */ \ - asm volatile(asm_inline(add_si((rt), R_0, (imm))) \ - asm_clobber(reg_str(R_AT_Code), "memory")); \ - } else if (__builtin_constant_p(imm) && ((U4)(imm) <= 0xFFFFU)) { \ - /* 0x8000..0xFFFF: ori rt, $0, imm (zero-extends) */ \ - asm volatile(asm_inline(ori_op((rt), R_0, (imm))) \ - asm_clobber(reg_str(R_AT_Code), "memory")); \ - } else { \ - /* > 16 bits: lui + (ori | addi). \ - * If lo16 is in [0, 0x7FFF] use addi (sign-ext is harmless \ - * since the high half cleared bits 15..0). Otherwise ori. */ \ - U4 _li_imm_ = (U4)(imm); \ - U4 _li_lo_ = _li_imm_ & 0xFFFFU; \ - U4 _li_hi_ = _li_imm_ >> 16; \ - if (_li_lo_ <= 0x7FFFU) { \ - asm volatile( \ - asm_inline(lui_op((rt), _li_hi_), \ - add_si((rt), (rt), (S2)(U2)_li_lo_)) \ - asm_clobber(reg_str(R_AT_Code), "memory")); \ - } else { \ - asm volatile( \ - asm_inline(lui_op((rt), _li_hi_), \ - ori_op((rt), (rt), (U2)_li_lo_)) \ - asm_clobber(reg_str(R_AT_Code), "memory")); \ - } \ - } \ -} while (0) +#define load_imm(rt, imm) do { \ + if (__builtin_constant_p(imm) && ((U4)(imm) <= 0x7FFFU)) { \ + /* Small positive: addi rt, $0, imm */ \ + asm volatile( \ + asm_inline(add_si((rt), R_0, (imm))) \ + asm_clobber(reg_str(R_AT_Code), "memory") \ + ); \ + } \ + else if (__builtin_constant_p(imm) && ((U4)(imm) <= 0xFFFFU)) { \ + /* 0x8000..0xFFFF: ori rt, $0, imm (zero-extends) */ \ + asm volatile( \ + asm_inline(ori_op((rt), R_0, (imm))) \ + asm_clobber(reg_str(R_AT_Code), "memory") \ + ); \ + } \ + else \ + { \ + /* > 16 bits: lui + (ori | addi). \ + * If lo16 is in [0, 0x7FFF] use addi (sign-ext is harmless \ + * since the high half cleared bits 15..0). Otherwise ori. */ \ + U4 _li_imm_ = (U4)(imm); \ + U4 _li_lo_ = _li_imm_ & 0xFFFFU; \ + U4 _li_hi_ = _li_imm_ >> 16; \ + if (_li_lo_ <= 0x7FFFU) { \ + asm volatile( \ + asm_inline(lui_op((rt), _li_hi_), \ + add_si((rt), (rt), (S2)(U2)_li_lo_)) \ + asm_clobber(reg_str(R_AT_Code), "memory") \ + ); \ + } \ + else { \ + asm volatile( \ + asm_inline(lui_op((rt), _li_hi_), \ + ori_op((rt), (rt), (U2)_li_lo_)) \ + asm_clobber(reg_str(R_AT_Code), "memory") \ + ); \ + } \ + } \ +} while (0 ) // Binary Metaprogramming diff --git a/scripts/build_psyq.ps1 b/scripts/build_psyq.ps1 index fecafd3..3d821a6 100644 --- a/scripts/build_psyq.ps1 +++ b/scripts/build_psyq.ps1 @@ -335,9 +335,9 @@ function build-gte_hello { $compile_args = @() $compile_args += $f_debug - # $compile_args += $f_optimize_none + $compile_args += $f_optimize_none # $compile_args += $f_optimize_intrinsics - $compile_args += $f_optimize_size + # $compile_args += $f_optimize_size # $compile_args += $f_optimize_debug $compile_args += ($f_include + $path_code) compile-unit $src_c $module_c $includes $compile_args