// SPDX-License-Identifier: MIT // Copyright (c) 2026 Raphael Amorim // Data register layout (32 × u32): // 0 V0 (xy packed, low half of vertex 0) // 1 V0Z (z, low 16 bits) // 2 V1 / 3 V1Z // 4 V2 / 5 V2Z // 6 RGBC - current colour + code // 7 OTZ - average Z for ordering table // 8 IR0 - interpolation parameter // 9-11 IR1, IR2, IR3 // 12-15 SXY0, SXY1, SXY2, SXYP - screen-space FIFO + push input // 16-19 SZ0, SZ1, SZ2, SZ3 - Z FIFO // 20-22 RGB0, RGB1, RGB2 // 23 RES1 - reserved // 24-27 MAC0, MAC1, MAC2, MAC3 // 28 IRGB (R), 29 ORGB (R) - packed 15-bit IR view // 30 LZCS, 31 LZCR - leading-zero count source / result // // Control register layout (32 × u32): // 0..4 rotation matrix (R11R12, R13R21, R22R23, R31R32, R33) // 5..7 translation vector (TRX, TRY, TRZ) // 8..12 light matrix (L11L12, ..., L33) // 13..15 background colour (RBK, GBK, BBK) // 16..20 light colour matrix // 21..23 far colour (RFC, GFC, BFC) // 24..25 OFX, OFY (screen offset, 16-bit fixed-point with 16 frac bits) // 26 H (projection plane distance) // 27 DQA (depth queue parameter A) // 28 DQB (depth queue parameter B) // 29 ZSF3, 30 ZSF4 // 31 FLAG (saturation/overflow bits) const { Vec } = import("std/collections"); const GTE_REGS: u32 = 144; const D_V0: u32 = 0; const D_V0Z: u32 = 1; const D_SXY0: u32 = 12; const D_SXY1: u32 = 13; const D_SXY2: u32 = 14; const D_SXYP: u32 = 15; const D_MAC0: u32 = 24; const D_MAC1: u32 = 25; const D_MAC2: u32 = 26; const D_MAC3: u32 = 27; const C_OFX: u32 = 24; const C_OFY: u32 = 25; const C_FLAG: u32 = 31; // Unsigned Newton-Raphson reciprocal table for the GTE perspective divide. const GTE_UNR_TABLE: [257]u8 = [ 0xff, 0xfd, 0xfb, 0xf9, 0xf7, 0xf5, 0xf3, 0xf1, 0xef, 0xee, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe3, 0xe1, 0xdf, 0xdd, 0xdc, 0xda, 0xd8, 0xd6, 0xd5, 0xd3, 0xd1, 0xd0, 0xce, 0xcd, 0xcb, 0xc9, 0xc8, 0xc6, 0xc5, 0xc3, 0xc1, 0xc0, 0xbe, 0xbd, 0xbb, 0xba, 0xb8, 0xb7, 0xb5, 0xb4, 0xb2, 0xb1, 0xb0, 0xae, 0xad, 0xab, 0xaa, 0xa9, 0xa7, 0xa6, 0xa4, 0xa3, 0xa2, 0xa0, 0x9f, 0x9e, 0x9c, 0x9b, 0x9a, 0x99, 0x97, 0x96, 0x95, 0x94, 0x92, 0x91, 0x90, 0x8f, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e, 0x4d, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x43, 0x42, 0x41, 0x40, 0x3f, 0x3f, 0x3e, 0x3d, 0x3c, 0x3c, 0x3b, 0x3a, 0x39, 0x39, 0x38, 0x37, 0x36, 0x36, 0x35, 0x34, 0x33, 0x33, 0x32, 0x31, 0x31, 0x30, 0x2f, 0x2e, 0x2e, 0x2d, 0x2c, 0x2c, 0x2b, 0x2a, 0x2a, 0x29, 0x28, 0x28, 0x27, 0x26, 0x26, 0x25, 0x24, 0x24, 0x23, 0x22, 0x22, 0x21, 0x20, 0x20, 0x1f, 0x1e, 0x1e, 0x1d, 0x1d, 0x1c, 0x1b, 0x1b, 0x1a, 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, 0x14, 0x13, 0x12, 0x12, 0x11, 0x11, 0x10, 0x0f, 0x0f, 0x0e, 0x0e, 0x0d, 0x0d, 0x0c, 0x0c, 0x0b, 0x0a, 0x0a, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, 0x06, 0x06, 0x05, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, ]; pub fn createGte() Vec(u32) { return Vec(u32).filled(0, GTE_REGS); } // 15 SXYP read -> mirror of SXY2; write -> push SXY FIFO // 28 IRGB write -> unpack 15-bit RGB into IR1..IR3; read -> repack // 29 ORGB read -> repack IR1..IR3 to 15-bit; write -> ignored (RO) // 30 LZCS write -> also compute LZCR (leading sign-bit count) // 31 LZCR read-only pub fn gteSext16(v: u32) u32 { if ((v & 0x8000) != 0) { return v | 0xFFFF0000; } return v & 0x0000FFFF; } pub fn gteDataRead(g: *mut[] u32, idx: u32) u32 { const i: u32 = idx & 0x1F; if (i == 15) { return g[14]; } if (i == 1 || i == 3 || i == 5 || i == 8 || i == 9 || i == 10 || i == 11) { return gteSext16(g[i]); } if (i == 28 || i == 29) { var r: i64 = s16ToI64(g[9]) >> 7; var grn: i64 = s16ToI64(g[10]) >> 7; var b: i64 = s16ToI64(g[11]) >> 7; if (r < 0) { r = 0; } if (r > 0x1F) { r = 0x1F; } if (grn < 0) { grn = 0; } if (grn > 0x1F) { grn = 0x1F; } if (b < 0) { b = 0; } if (b > 0x1F) { b = 0x1F; } return ((r as u64) | ((grn as u64) << 5) | ((b as u64) << 10)) as u32; } return g[i]; } pub fn gteDataWrite(g: *mut[] u32, idx: u32, val: u32) { const i: u32 = idx & 0x1F; if (i == 15) { // SXYP - store the value, then push SXY FIFO so the latest // entry slides into SXY2 and older entries shift down. Used by // the BIOS to pre-load screen-space vertices without RTPS. // SXYP raw g[15] = val; // SXY0 ← SXY1 g[12] = g[13]; // SXY1 ← SXY2 g[13] = g[14]; // SXY2 ← new g[14] = val; return; } if (i == 28) { const packed: u32 = val & 0x7FFF; g[28] = packed; g[9] = (packed & 0x1F) << 7; g[10] = ((packed >> 5) & 0x1F) << 7; g[11] = ((packed >> 10) & 0x1F) << 7; return; } if (i == 29) { // ORGB is read-only - writes are silently discarded. return; } if (i == 30) { g[30] = val; g[31] = gteCountLeadingSign(val); return; } g[i] = val; } pub fn gteCountLeadingSign(v: u32) u32 { if (v == 0) { return 32; } if (v == 0xFFFFFFFF) { return 32; } var x: u32 = v; if ((x & 0x80000000) != 0) { x = ~x; } var n: u32 = 0; while ((x & 0x80000000) == 0 && n < 32) { x = x << 1; n = n + 1; } return n; } pub fn gteCtrlRead(g: *mut[] u32, idx: u32) u32 { const i: u32 = idx & 0x1F; if (i == C_FLAG) { const v: u32 = g[32 + C_FLAG] & 0x7FFFF000; if ((v & 0x7F87E000) != 0) { return v | 0x80000000; } return v; } // (rt/l/lr -> idx 4/12/20), H (26), DQA (27), ZSF3 (29), ZSF4 (30). if (i == 4 || i == 12 || i == 20 || i == 26 || i == 27 || i == 29 || i == 30) { return gteSext16(g[32 + i]); } return g[32 + i]; } pub fn gteCtrlWrite(g: *mut[] u32, idx: u32, val: u32) { const i: u32 = idx & 0x1F; if (i == C_FLAG) { g[32 + C_FLAG] = val & 0x7FFFF000; return; } g[32 + i] = val; } pub fn gteExec(g: *mut[] u32, opc: u32) { // clear g[32 + C_FLAG] = 0; const op: u32 = opc & 0x3F; g[70] = g[70] + 1; if (op < 0x40) { g[80 + op] = g[80 + op] + 1; } const sf: u32 = ((opc >> 19) & 1) * 12; const lm: u32 = (opc >> 10) & 1; match (op) { 0x01 { gteRtps(g, 0, sf, lm, 1); } // RTPS 0x06 { gteNclip(g); } // NCLIP 0x0C { gteOp(g, sf, lm); } // OP 0x12 { gteMvmva(g, opc); } // MVMVA 0x13 { gteNcds(g, 0, sf, lm); } // NCDS 0x16 { // NCDT - 3 successive NCDS gteNcds(g, 0, sf, lm); gteNcds(g, 1, sf, lm); gteNcds(g, 2, sf, lm); } 0x28 { gteSqr(g, sf, lm); } // SQR 0x2D { gteAvsz(g, 3); } // AVSZ3 0x2E { gteAvsz(g, 4); } // AVSZ4 0x30 { // RTPT - 3 successive RTPS gteRtps(g, 0, sf, lm, 0); gteRtps(g, 1, sf, lm, 0); gteRtps(g, 2, sf, lm, 1); // DQ tail only on last vertex } 0x10 { gteDpcs(g, sf, lm); } // DPCS 0x11 { gteIntpl(g, sf, lm); } // INTPL 0x14 { gteCdp(g, sf, lm); } // CDP 0x1B { gteNccs(g, 0, sf, lm); } // NCCS 0x1C { gteCc(g, sf, lm); } // CC 0x1E { gteNcs(g, 0, sf, lm); } // NCS 0x20 { // NCT - 3× NCS gteNcs(g, 0, sf, lm); gteNcs(g, 1, sf, lm); gteNcs(g, 2, sf, lm); } 0x29 { gteDcpl(g, sf, lm); } // DCPL 0x2A { gteDpct(g, sf, lm); } // DPCT 0x3D { gteGpf(g, sf, lm); } // GPF 0x3E { gteGpl(g, sf, lm); } // GPL 0x3F { // NCCT - 3× NCCS gteNccs(g, 0, sf, lm); gteNccs(g, 1, sf, lm); gteNccs(g, 2, sf, lm); } _ {} } } pub fn gteOpCycles(opc: u32) u32 { const op: u32 = opc & 0x3F; match (op) { 0x01 { return 15; } // RTPS 0x06 { return 8; } // NCLIP 0x0C { return 6; } // OP 0x10 { return 8; } // DPCS 0x11 { return 8; } // INTPL 0x12 { return 8; } // MVMVA 0x13 { return 19; } // NCDS 0x14 { return 13; } // CDP 0x16 { return 44; } // NCDT 0x1B { return 17; } // NCCS 0x1C { return 11; } // CC 0x1E { return 14; } // NCS 0x20 { return 30; } // NCT 0x28 { return 5; } // SQR 0x29 { return 8; } // DCPL 0x2A { return 17; } // DPCT 0x2D { return 5; } // AVSZ3 0x2E { return 6; } // AVSZ4 0x30 { return 23; } // RTPT 0x3D { return 5; } // GPF 0x3E { return 5; } // GPL 0x3F { return 39; } // NCCT _ { return 2; } // unknown sub-op } } pub fn gtePushRgb(g: *mut[] u32, m1: i64, m2: i64, m3: i64) { g[20] = g[21]; g[21] = g[22]; const code: u32 = (g[6] >> 24) & 0xFF; const r2: u32 = gteClampRgb(g, 1, m1 >> 4); const g2: u32 = gteClampRgb(g, 2, m2 >> 4); const b2: u32 = gteClampRgb(g, 3, m3 >> 4); g[22] = r2 | (g2 << 8) | (b2 << 16) | (code << 24); } // Light-matrix transform of vertex vidx into (IR1,IR2,IR3) and (MAC1,MAC2,MAC3). pub fn gteApplyLight(g: *mut[] u32, vidx: u32, sf: u32, lm: u32) { const vSlot: u32 = vidx * 2; const vxy: u32 = g[vSlot]; const vzS: u32 = g[vSlot + 1]; const vx: i64 = s16ToI64(vxy & 0xFFFF); const vy: i64 = s16ToI64((vxy >> 16) & 0xFFFF); const vz: i64 = s16ToI64(vzS & 0xFFFF); const ll11: i64 = s16ToI64(g[32 + 8] & 0xFFFF); const ll12: i64 = s16ToI64((g[32 + 8] >> 16) & 0xFFFF); const ll13: i64 = s16ToI64(g[32 + 9] & 0xFFFF); const ll21: i64 = s16ToI64((g[32 + 9] >> 16) & 0xFFFF); const ll22: i64 = s16ToI64(g[32 + 10] & 0xFFFF); const ll23: i64 = s16ToI64((g[32 + 10] >> 16) & 0xFFFF); const ll31: i64 = s16ToI64(g[32 + 11] & 0xFFFF); const ll32: i64 = s16ToI64((g[32 + 11] >> 16) & 0xFFFF); const ll33: i64 = s16ToI64(g[32 + 12] & 0xFFFF); var m1: i64 = ll11 * vx + ll12 * vy + ll13 * vz; var m2: i64 = ll21 * vx + ll22 * vy + ll23 * vz; var m3: i64 = ll31 * vx + ll32 * vy + ll33 * vz; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1, m2, m3, lm); } // (BK<<12) + LC*IR stage - applied by CC, CDP, NCDS, NCCS, NCS after // L*V. Reads IR1..IR3 from g[9..11], writes MAC1..MAC3, then IR1..IR3 // clamped by lm. pub fn gteApplyLightColor(g: *mut[] u32, sf: u32, lm: u32) { const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); const lr1: i64 = s16ToI64(g[32 + 16] & 0xFFFF); const lr2: i64 = s16ToI64((g[32 + 16] >> 16) & 0xFFFF); const lr3: i64 = s16ToI64(g[32 + 17] & 0xFFFF); const lg1: i64 = s16ToI64((g[32 + 17] >> 16) & 0xFFFF); const lg2: i64 = s16ToI64(g[32 + 18] & 0xFFFF); const lg3: i64 = s16ToI64((g[32 + 18] >> 16) & 0xFFFF); const lb1: i64 = s16ToI64(g[32 + 19] & 0xFFFF); const lb2: i64 = s16ToI64((g[32 + 19] >> 16) & 0xFFFF); const lb3: i64 = s16ToI64(g[32 + 20] & 0xFFFF); const rbk: i64 = s32ToI64(g[32 + 13]); const gbk: i64 = s32ToI64(g[32 + 14]); const bbk: i64 = s32ToI64(g[32 + 15]); const a1a: i64 = gteCheckMac(g, 1, (rbk << 12) + lr1 * ir1); const a1b: i64 = gteCheckMac(g, 1, a1a + lr2 * ir2); const m1: i64 = gteClampMac(g, 1, a1b + lr3 * ir3, sf); const a2a: i64 = gteCheckMac(g, 2, (gbk << 12) + lg1 * ir1); const a2b: i64 = gteCheckMac(g, 2, a2a + lg2 * ir2); const m2: i64 = gteClampMac(g, 2, a2b + lg3 * ir3, sf); const a3a: i64 = gteCheckMac(g, 3, (bbk << 12) + lb1 * ir1); const a3b: i64 = gteCheckMac(g, 3, a3a + lb2 * ir2); const m3: i64 = gteClampMac(g, 3, a3b + lb3 * ir3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1, m2, m3, lm); } // Far-colour interpolation stage shared by CDP, NCDS, DCPL, DPCS: // ir' = clamp(FC<<12 - (C<<4) * IR, lm=0) // MAC = (C<<4) * IR + IR0 * ir' // Reads IR1..IR3 from g[9..11], color from g[6] RGBC (rc/gc/bc). // Writes MAC1..MAC3 and IR1..IR3 (clamped by lm). pub fn gteFarColorInterp(g: *mut[] u32, sf: u32, lm: u32) { const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); const rgbc: u32 = g[6]; const rc: i64 = (rgbc & 0xFF) as i64; const gc: i64 = ((rgbc >> 8) & 0xFF) as i64; const bc: i64 = ((rgbc >> 16) & 0xFF) as i64; const rfc: i64 = s32ToI64(g[32 + 21]); const gfc: i64 = s32ToI64(g[32 + 22]); const bfc: i64 = s32ToI64(g[32 + 23]); var fp1: i64 = (rfc << 12) - (rc << 4) * ir1; var fp2: i64 = (gfc << 12) - (gc << 4) * ir2; var fp3: i64 = (bfc << 12) - (bc << 4) * ir3; fp1 = gteClampMac(g, 1, fp1, sf); fp2 = gteClampMac(g, 2, fp2, sf); fp3 = gteClampMac(g, 3, fp3, sf); const ir1f: i64 = gteClampIr(g, 1, fp1, 0); const ir2f: i64 = gteClampIr(g, 2, fp2, 0); const ir3f: i64 = gteClampIr(g, 3, fp3, 0); const ir0: i64 = s16ToI64(g[8]); var m1: i64 = (rc << 4) * ir1 + ir0 * ir1f; var m2: i64 = (gc << 4) * ir2 + ir0 * ir2f; var m3: i64 = (bc << 4) * ir3 + ir0 * ir3f; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1, m2, m3, lm); } // NCS - normal-color single. L*V then BK+LC*IR. No final colour stage. // Push RGB FIFO with the resulting MAC. pub fn gteNcs(g: *mut[] u32, vidx: u32, sf: u32, lm: u32) { gteApplyLight(g, vidx, sf, lm); gteApplyLightColor(g, sf, lm); const m1: i64 = s32ToI64(g[D_MAC1]); const m2: i64 = s32ToI64(g[D_MAC2]); const m3: i64 = s32ToI64(g[D_MAC3]); gtePushRgb(g, m1, m2, m3); // NCS reclamps IR after the FIFO push using the *new* MAC values - // the existing MAC1..MAC3 register state. gteIrTriple(g, m1, m2, m3, lm); } // NCCS - normal-color-color single. NCS pipeline + (C * IR) << 4 final // colour stage (no FC interpolation). pub fn gteNccs(g: *mut[] u32, vidx: u32, sf: u32, lm: u32) { gteApplyLight(g, vidx, sf, lm); gteApplyLightColor(g, sf, lm); const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); const rgbc: u32 = g[6]; const rc: i64 = (rgbc & 0xFF) as i64; const gc: i64 = ((rgbc >> 8) & 0xFF) as i64; const bc: i64 = ((rgbc >> 16) & 0xFF) as i64; var m1: i64 = (rc * ir1) << 4; var m2: i64 = (gc * ir2) << 4; var m3: i64 = (bc * ir3) << 4; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gtePushRgb(g, m1, m2, m3); gteIrTriple(g, m1, m2, m3, lm); } // CC - color color. Skips L*V (uses existing IR), then BK+LC*IR, then // (C*IR)<<4 final stage. pub fn gteCc(g: *mut[] u32, sf: u32, lm: u32) { gteApplyLightColor(g, sf, lm); const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); const rgbc: u32 = g[6]; const rc: i64 = (rgbc & 0xFF) as i64; const gc: i64 = ((rgbc >> 8) & 0xFF) as i64; const bc: i64 = ((rgbc >> 16) & 0xFF) as i64; var m1: i64 = (rc * ir1) << 4; var m2: i64 = (gc * ir2) << 4; var m3: i64 = (bc * ir3) << 4; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gtePushRgb(g, m1, m2, m3); gteIrTriple(g, m1, m2, m3, lm); } // CDP - color depth-cued. Skips L*V (uses existing IR), then BK+LC*IR, // then FC interpolation. Push RGB FIFO. pub fn gteCdp(g: *mut[] u32, sf: u32, lm: u32) { gteApplyLightColor(g, sf, lm); gteFarColorInterp(g, sf, lm); const m1: i64 = s32ToI64(g[D_MAC1]); const m2: i64 = s32ToI64(g[D_MAC2]); const m3: i64 = s32ToI64(g[D_MAC3]); gtePushRgb(g, m1, m2, m3); } // DPCS - depth queue color single. FC interpolation on the RGBC color: // (RFC<<12) - (RC<<16), then (RC<<16) + IR0*ir'. The shift-by-16 // difference vs FarColorInterp's (RC<<4)*IR is because here IR=1.0 implicit. pub fn gteDpcs(g: *mut[] u32, sf: u32, lm: u32) { const rgbc: u32 = g[6]; const rc: i64 = (rgbc & 0xFF) as i64; const gc: i64 = ((rgbc >> 8) & 0xFF) as i64; const bc: i64 = ((rgbc >> 16) & 0xFF) as i64; const rfc: i64 = s32ToI64(g[32 + 21]); const gfc: i64 = s32ToI64(g[32 + 22]); const bfc: i64 = s32ToI64(g[32 + 23]); var fp1: i64 = (rfc << 12) - (rc << 16); var fp2: i64 = (gfc << 12) - (gc << 16); var fp3: i64 = (bfc << 12) - (bc << 16); fp1 = gteClampMac(g, 1, fp1, sf); fp2 = gteClampMac(g, 2, fp2, sf); fp3 = gteClampMac(g, 3, fp3, sf); const ir1f: i64 = gteClampIr(g, 1, fp1, 0); const ir2f: i64 = gteClampIr(g, 2, fp2, 0); const ir3f: i64 = gteClampIr(g, 3, fp3, 0); const ir0: i64 = s16ToI64(g[8]); var m1: i64 = (rc << 16) + ir0 * ir1f; var m2: i64 = (gc << 16) + ir0 * ir2f; var m3: i64 = (bc << 16) + ir0 * ir3f; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1, m2, m3, lm); gtePushRgb(g, m1, m2, m3); } // DPCT - depth queue color triple. Repeats DPCS three times, each time // using RGB0 (FIFO head) as the source color. The FIFO shifts each // iteration so the next "RGB0" is the previous result. pub fn gteDpct(g: *mut[] u32, sf: u32, lm: u32) { var iter: u32 = 0; while (iter < 3) { const rgb0: u32 = g[20]; const rc: i64 = (rgb0 & 0xFF) as i64; const gc: i64 = ((rgb0 >> 8) & 0xFF) as i64; const bc: i64 = ((rgb0 >> 16) & 0xFF) as i64; const rfc: i64 = s32ToI64(g[32 + 21]); const gfc: i64 = s32ToI64(g[32 + 22]); const bfc: i64 = s32ToI64(g[32 + 23]); var fp1: i64 = (rfc << 12) - (rc << 16); var fp2: i64 = (gfc << 12) - (gc << 16); var fp3: i64 = (bfc << 12) - (bc << 16); fp1 = gteClampMac(g, 1, fp1, sf); fp2 = gteClampMac(g, 2, fp2, sf); fp3 = gteClampMac(g, 3, fp3, sf); const ir1f: i64 = gteClampIr(g, 1, fp1, 0); const ir2f: i64 = gteClampIr(g, 2, fp2, 0); const ir3f: i64 = gteClampIr(g, 3, fp3, 0); const ir0: i64 = s16ToI64(g[8]); var m1: i64 = (rc << 16) + ir0 * ir1f; var m2: i64 = (gc << 16) + ir0 * ir2f; var m3: i64 = (bc << 16) + ir0 * ir3f; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1, m2, m3, lm); gtePushRgb(g, m1, m2, m3); iter = iter + 1; } } // INTPL - interpolation. FC interpolation using the *existing* IR as the // "color" input: ir' = clamp(FC<<12 - IR<<12); MAC = IR<<12 + IR0*ir'. pub fn gteIntpl(g: *mut[] u32, sf: u32, lm: u32) { const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); const rfc: i64 = s32ToI64(g[32 + 21]); const gfc: i64 = s32ToI64(g[32 + 22]); const bfc: i64 = s32ToI64(g[32 + 23]); var fp1: i64 = (rfc << 12) - (ir1 << 12); var fp2: i64 = (gfc << 12) - (ir2 << 12); var fp3: i64 = (bfc << 12) - (ir3 << 12); fp1 = gteClampMac(g, 1, fp1, sf); fp2 = gteClampMac(g, 2, fp2, sf); fp3 = gteClampMac(g, 3, fp3, sf); const ir1f: i64 = gteClampIr(g, 1, fp1, 0); const ir2f: i64 = gteClampIr(g, 2, fp2, 0); const ir3f: i64 = gteClampIr(g, 3, fp3, 0); const ir0: i64 = s16ToI64(g[8]); var m1: i64 = (ir1 << 12) + ir0 * ir1f; var m2: i64 = (ir2 << 12) + ir0 * ir2f; var m3: i64 = (ir3 << 12) + ir0 * ir3f; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1, m2, m3, lm); gtePushRgb(g, m1, m2, m3); } // DCPL - depth-cue color light. Like CDP but uses (RC*IR)<<4 to derive // the first MAC stage instead of BK+LC*IR. Final stage = FC interpolation. pub fn gteDcpl(g: *mut[] u32, sf: u32, lm: u32) { // DCPL = far-colour interpolation of (RGBC<<4)*IR. gteFarColorInterp // already consumes the (RC<<4)*IR term raw (matching DuckStation's // in_MAC = (RGBC*IR)<<4 fed straight into InterpolateColor), so the old // separate clampMac pre-stage here was dead - its MAC writes were // immediately overwritten - but it polluted the FLAG register with // spurious MAC1-3 overflow bits. mednafen/DuckStation/Avocado fold the // term directly with no extra clamp, so we drop the dead stage. gteFarColorInterp(g, sf, lm); const m1: i64 = s32ToI64(g[D_MAC1]); const m2: i64 = s32ToI64(g[D_MAC2]); const m3: i64 = s32ToI64(g[D_MAC3]); gtePushRgb(g, m1, m2, m3); } // GPF - general purpose flat (interpolate IR0 with current IR). // MAC = IR0 * IR; IR = clamp(MAC); push RGB FIFO from MAC>>4. pub fn gteGpf(g: *mut[] u32, sf: u32, lm: u32) { const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); const ir0: i64 = s16ToI64(g[8]); var m1: i64 = ir0 * ir1; var m2: i64 = ir0 * ir2; var m3: i64 = ir0 * ir3; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1, m2, m3, lm); gtePushRgb(g, m1, m2, m3); } // GPL - general purpose linear (extend MAC by sf bits then add IR0*IR). // MAC = (MAC << sf) + IR0 * IR; IR = clamp(MAC); push RGB FIFO. pub fn gteGpl(g: *mut[] u32, sf: u32, lm: u32) { const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); const ir0: i64 = s16ToI64(g[8]); var mac1: i64 = s32ToI64(g[D_MAC1]) << sf; var mac2: i64 = s32ToI64(g[D_MAC2]) << sf; var mac3: i64 = s32ToI64(g[D_MAC3]) << sf; var m1: i64 = mac1 + ir0 * ir1; var m2: i64 = mac2 + ir0 * ir2; var m3: i64 = mac3 + ir0 * ir3; m1 = gteClampMac(g, 1, m1, sf); m2 = gteClampMac(g, 2, m2, sf); m3 = gteClampMac(g, 3, m3, sf); g[D_MAC1] = (m1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1, m2, m3, lm); gtePushRgb(g, m1, m2, m3); } // Sign-extend a 16-bit value (stored in u32 low 16) to i64. pub fn s16ToI64(v: u32) i64 { const lo: u32 = v & 0xFFFF; if ((lo & 0x8000) != 0) { var w: i64 = lo as i64; return w - 0x10000; } return (v & 0xFFFF) as i64; } // Sign-extend a 32-bit value (stored in u32) to i64. pub fn s32ToI64(v: u32) i64 { var w: i64 = v as i64; if ((v & 0x80000000) != 0) { w = w - 0x100000000; } return w; } pub fn gteClampIr(g: *mut[] u32, i: u32, v: i64, lm: u32) i64 { var lo: i64 = -32768; if (lm != 0) { lo = 0; } if (v < lo) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x1000000 >> (i - 1)); return lo; } if (v > 32767) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x1000000 >> (i - 1)); return 32767; } return v; } // Clamp three MAC values into IR1..IR3 (with FLAG bits 24/23/22) and // store them - the tail shared by nearly every colour op. pub fn gteIrTriple(g: *mut[] u32, m1: i64, m2: i64, m3: i64, lm: u32) { g[9] = (gteClampIr(g, 1, m1, lm) as u64 & 0xFFFF) as u32; g[10] = (gteClampIr(g, 2, m2, lm) as u64 & 0xFFFF) as u32; g[11] = (gteClampIr(g, 3, m3, lm) as u64 & 0xFFFF) as u32; } // IR0 clamp 0..0x1000 -> bit 12. pub fn gteClampIr0(g: *mut[] u32, v: i64) i64 { if (v < 0) { g[32 + C_FLAG] = g[32 + C_FLAG] | 0x1000; return 0; } if (v > 0x1000) { g[32 + C_FLAG] = g[32 + C_FLAG] | 0x1000; return 0x1000; } return v; } // SZ3 saturation 0..0xFFFF -> bit 18. pub fn gteClampSz3(g: *mut[] u32, v: i64) u32 { if (v < 0) { g[32 + C_FLAG] = g[32 + C_FLAG] | 0x40000; return 0; } if (v > 0xFFFF) { g[32 + C_FLAG] = g[32 + C_FLAG] | 0x40000; return 0xFFFF; } return (v as u64 & 0xFFFF) as u32; } // SX/SY saturation -0x400..0x3FF -> bit 14/13. pub fn gteClampSxy(g: *mut[] u32, i: u32, v: i64) u32 { if (v < -1024) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x4000 >> (i - 1)); return 0xFFFFFC00; } if (v > 1023) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x4000 >> (i - 1)); return 0x3FF; } return ((v + 0x100000000) as u64 & 0xFFFF) as u32; } // RGB channel clamp 0..0xFF -> bit 21/20/19. pub fn gteClampRgb(g: *mut[] u32, i: u32, v: i64) u32 { if (v < 0) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x200000 >> (i - 1)); return 0; } if (v > 255) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x200000 >> (i - 1)); return 255; } return (v as u64 & 0xFF) as u32; } // MAC0 overflow -> bit 15 (neg) / 16 (pos); value is NOT clamped, only flagged. pub fn gteClampMac0(g: *mut[] u32, v: i64) i64 { const lim: i64 = (1 as i64) << 31; if (v < (0 - lim)) { g[32 + C_FLAG] = g[32 + C_FLAG] | 0x8000; } if (v > (lim - 1)) { g[32 + C_FLAG] = g[32 + C_FLAG] | 0x10000; } return v; } // MAC1..MAC3: flag 44-bit overflow (bit 30/29/28 pos, 27/26/25 neg), // truncate to 44 bits (sign-extend from bit 43), then >> sf. The // truncation only changes the result when the sum exceeds 44 bits, so // normal geometry is unaffected. pub fn gteClampMac(g: *mut[] u32, i: u32, v: i64, sf: u32) i64 { const lim: i64 = (1 as i64) << 43; if (v < (0 - lim)) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x8000000 >> (i - 1)); } if (v > (lim - 1)) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x40000000 >> (i - 1)); } const t: i64 = (v << 20) >> 20; // MAC1..3 are 32-bit, so truncate to int32 before this feeds the IR // clamp (a >32-bit result otherwise clamps IR / sets the saturation // flag from the wrong magnitude). return truncI32(t >> sf); } // Running-accumulation 44-bit check: flag overflow and truncate to 44 // bits, WITHOUT the >> sf (used between the additive terms of a 3-term // MAC sum so intermediate overflow wraps as on hardware). pub fn gteCheckMac(g: *mut[] u32, i: u32, v: i64) i64 { const lim: i64 = (1 as i64) << 43; if (v < (0 - lim)) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x8000000 >> (i - 1)); } if (v > (lim - 1)) { g[32 + C_FLAG] = g[32 + C_FLAG] | (0x40000000 >> (i - 1)); } return (v << 20) >> 20; } // Truncate an i64 to the signed 32-bit value range (low 32 bits, // sign-extended). Models the `(int32_t)` casts inside gteClampIrZ. pub fn truncI32(v: i64) i64 { const lo: u32 = (v as u64 & 0xFFFFFFFF) as u32; return s32ToI64(lo); } // IR3 clamp for RTPS/RTPT. The returned value clamps `value >> sf` to // [lm?0:-0x8000, 0x7FFF], but FLAG bit 22 is keyed off `value >> 12` // (each int32-truncated first). At sf=12 the two shifts agree; at sf=0 // they diverge - so a plain gteClampIr on the shifted MAC sets bit 22 // from the wrong magnitude. Note this clamp does NOT flag on the // value-clamp itself, only on the >>12 range check. pub fn gteClampIrZ(g: *mut[] u32, value: i64, sf: u32, lm: u32) i64 { const valueSf: i64 = truncI32(value >> sf); const value12: i64 = truncI32(value >> 12); var lo: i64 = -32768; if (lm != 0) { lo = 0; } if (value12 < -32768 || value12 > 32767) { g[32 + C_FLAG] = g[32 + C_FLAG] | 0x400000; // FLAG bit 22 (IR3 sat) } if (valueSf < lo) { return lo; } if (valueSf > 32767) { return 32767; } return valueSf; } // Count leading zero bits of a 32-bit value (32 for 0). Used by the // `clz` step inside gteDivide. pub fn gteClz32(v: u32) u32 { if (v == 0) { return 32; } var n: u32 = 0; var x: u32 = v; while ((x & 0x80000000) == 0) { x = x << 1; n = n + 1; } return n; } // GTE perspective divide - the hardware's Unsigned Newton-Raphson // reciprocal. On overflow (n >= 2d) // it returns 0x1FFFF and sets FLAG bit 17 (divide overflow). `n` is H, // `d` is SZ3; both are treated as unsigned 16-bit. pub fn gteDivide(g: *mut[] u32, h: u32, sz3: u32) u32 { const n: u32 = h & 0xFFFF; const d: u32 = sz3 & 0xFFFF; if (n >= d * 2) { g[32 + C_FLAG] = g[32 + C_FLAG] | 0x20000; // FLAG.17 divide overflow return 0x1FFFF; } const shift: u32 = gteClz32(d) - 16; const r1: i64 = ((d << shift) & 0x7FFF) as i64; const tIdx: u32 = ((r1 + 0x40) >> 7) as u32; const r2: i64 = (GTE_UNR_TABLE[tIdx] as i64) + 0x101; const r3: i64 = ((0x80 - (r2 * (r1 + 0x8000))) >> 8) & 0x1FFFF; const reciprocal: i64 = ((r2 * r3) + 0x80) >> 8; const nsh: u64 = (n << shift) as u64; const res: u64 = ((reciprocal as u64) * nsh + 0x8000) >> 16; if (res > 0x1FFFF) { return 0x1FFFF; } return (res & 0xFFFFFFFF) as u32; } // RTPS - rotate, translate, perspective single. Computes a screen- // space projection of vertex `vidx` and pushes it onto the SXY/SZ // FIFO. Includes the nested 44-bit MAC truncation, the gteClampIrZ IR3 // path, and the full FLAG bookkeeping, matching hardware. pub fn gteRtps(g: *mut[] u32, vidx: u32, sf: u32, lm: u32, last: u32) { const vSlot: u32 = vidx * 2; // V0 at 0, V1 at 2, V2 at 4 const vxy: u32 = g[vSlot]; const vz: u32 = g[vSlot + 1]; const vx: i64 = s16ToI64(vxy & 0xFFFF); const vy: i64 = s16ToI64((vxy >> 16) & 0xFFFF); const vzS: i64 = s16ToI64(vz & 0xFFFF); // C[0] low = RT11, high = RT12 // C[1] low = RT13, high = RT21 // C[2] low = RT22, high = RT23 // C[3] low = RT31, high = RT32 // C[4] low = RT33 const rt11: i64 = s16ToI64(g[32 + 0] & 0xFFFF); const rt12: i64 = s16ToI64((g[32 + 0] >> 16) & 0xFFFF); const rt13: i64 = s16ToI64(g[32 + 1] & 0xFFFF); const rt21: i64 = s16ToI64((g[32 + 1] >> 16) & 0xFFFF); const rt22: i64 = s16ToI64(g[32 + 2] & 0xFFFF); const rt23: i64 = s16ToI64((g[32 + 2] >> 16) & 0xFFFF); const rt31: i64 = s16ToI64(g[32 + 3] & 0xFFFF); const rt32: i64 = s16ToI64((g[32 + 3] >> 16) & 0xFFFF); const rt33: i64 = s16ToI64(g[32 + 4] & 0xFFFFFFFF); // Translation (32-bit signed per axis). const trx: i64 = s32ToI64(g[32 + 5]); const try_: i64 = s32ToI64(g[32 + 6]); const trz: i64 = s32ToI64(g[32 + 7]); // MAC = TR*4096 + R*V, with per-term 44-bit truncation nested between // the additive terms (gteCheckMac) so an intermediate that crosses 44 // bits wraps before the next add, matching hardware. Normal geometry // never crosses 44 bits, so the values are unchanged there; only the // overflow FLAG/wrap differs. const c1a: i64 = gteCheckMac(g, 1, (trx << 12) + rt11 * vx); const c1b: i64 = gteCheckMac(g, 1, c1a + rt12 * vy); const sm1: i64 = gteClampMac(g, 1, c1b + rt13 * vzS, sf); const c2a: i64 = gteCheckMac(g, 2, (try_ << 12) + rt21 * vx); const c2b: i64 = gteCheckMac(g, 2, c2a + rt22 * vy); const sm2: i64 = gteClampMac(g, 2, c2b + rt23 * vzS, sf); // macRaw3 is the raw (pre-shift) MAC3 sum - it feeds BOTH gteClampSz3 // and gteClampIrZ. const c3a: i64 = gteCheckMac(g, 3, (trz << 12) + rt31 * vx); const c3b: i64 = gteCheckMac(g, 3, c3a + rt32 * vy); const macRaw3: i64 = c3b + rt33 * vzS; const sm3: i64 = gteClampMac(g, 3, macRaw3, sf); // IR1/IR2 clamp the shifted MAC; IR3 uses the special gteClampIrZ // which keys FLAG bit 22 off macRaw3>>12 while clamping macRaw3>>sf // - differs from a plain IR clamp only when sf=0. const ir1: i64 = gteClampIr(g, 1, sm1, lm); const ir2: i64 = gteClampIr(g, 2, sm2, lm); const ir3: i64 = gteClampIrZ(g, macRaw3, sf, lm); // SZ FIFO: SZ0←SZ1, SZ1←SZ2, SZ2←SZ3, SZ3←clamp_sz3(macRaw3>>12), // always the >>12 form regardless of `sf`. g[16] = g[17]; g[17] = g[18]; g[18] = g[19]; g[19] = gteClampSz3(g, macRaw3 >> 12); // Perspective division. const h: u32 = g[32 + 26] & 0xFFFF; const sz3: u32 = g[19]; const div: u32 = gteDivide(g, h, sz3); // SXY = (OFX + IR * div) >> 16. OFX/OFY are 32-bit signed. const ofx: i64 = s32ToI64(g[32 + 24]); const ofy: i64 = s32ToI64(g[32 + 25]); // OFX+IR*div is wrapped in gteClampMac0 (MAC0 overflow flag) before // the >>16, then gteClampSxy (bit 14/13). const sx: i64 = gteClampMac0(g, ofx + ir1 * (div as i64)) >> 16; const sy: i64 = gteClampMac0(g, ofy + ir2 * (div as i64)) >> 16; // SXY FIFO push. g[D_SXY0] = g[D_SXY1]; g[D_SXY1] = g[D_SXY2]; g[D_SXY2] = (gteClampSxy(g, 1, sx) & 0xFFFF) | ((gteClampSxy(g, 2, sy) & 0xFFFF) << 16); // Write IR back and MAC for further pipeline stages. gteClampMac // stores the *shifted* (>> sf) value into MAC1..MAC3, so we store // sm1..sm3 (= mac >> sf), NOT the raw pre-shift sum. SZ3 above still // uses the unshifted mac3 >> 12. g[9] = (ir1 as u64 & 0xFFFF) as u32; g[10] = (ir2 as u64 & 0xFFFF) as u32; g[11] = (ir3 as u64 & 0xFFFF) as u32; g[D_MAC1] = (sm1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (sm2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (sm3 as u64 & 0xFFFFFFFF) as u32; // Depth-queue tail. Only the LAST vertex runs it: RTPT does RTP on // vertices 0 and 1, then RTP+DQ on vertex 2, and DuckStation gates the // DQ block on `last` (gte.cpp:885). Running it on vertices 0/1 would // set spurious FLAG bits 12/15/16 (15/16 feed bit 31), so RTPT could // report a false error. DQA is s16 - sign-extend the low half, not the // full 32 bits. if (last != 0) { const dqa: i64 = s16ToI64(g[32 + 27]); const dqb: i64 = s32ToI64(g[32 + 28]); const mac0: i64 = gteClampMac0(g, dqb + dqa * (div as i64)); g[D_MAC0] = (mac0 as u64 & 0xFFFFFFFF) as u32; // IR0 clamps to 0..0x1000 with FLAG bit 12. const ir0: i64 = gteClampIr0(g, mac0 >> 12); g[8] = (ir0 as u64 & 0xFFFF) as u32; } } // AVSZ3 / AVSZ4 - average Z of the last 3 or 4 SZ FIFO entries, // scaled by ZSF3 / ZSF4 respectively: // AVSZ3: MAC0 = ZSF3 * (SZ1 + SZ2 + SZ3); OTZ = clampSZ(MAC0 >> 12) // AVSZ4: MAC0 = ZSF4 * (SZ0 + SZ1 + SZ2 + SZ3); OTZ = clampSZ(MAC0 >> 12) // ZSF3 is at control reg 29 (s16), ZSF4 at control reg 30 (s16). pub fn gteAvsz(g: *mut[] u32, count: u32) { var sum: i64 = (g[17] as i64) + (g[18] as i64) + (g[19] as i64); var zsf: i64 = s16ToI64(g[32 + 29]); // ZSF3 default if (count == 4) { sum = sum + (g[16] as i64); zsf = s16ToI64(g[32 + 30]); // ZSF4 } const mac0: i64 = gteClampMac0(g, zsf * sum); g[D_MAC0] = (mac0 as u64 & 0xFFFFFFFF) as u32; g[7] = gteClampSz3(g, mac0 >> 12); // OTZ (bit 18) } // NCLIP - back-face cull test. MAC0 = signed cross product of the three // screen-space vertices currently sitting in the SXY FIFO. The BIOS // uses the sign of MAC0 to decide whether to draw a triangle. pub fn gteNclip(g: *mut[] u32) { const sx0: i64 = s16ToI64(g[D_SXY0] & 0xFFFF); const sy0: i64 = s16ToI64((g[D_SXY0] >> 16) & 0xFFFF); const sx1: i64 = s16ToI64(g[D_SXY1] & 0xFFFF); const sy1: i64 = s16ToI64((g[D_SXY1] >> 16) & 0xFFFF); const sx2: i64 = s16ToI64(g[D_SXY2] & 0xFFFF); const sy2: i64 = s16ToI64((g[D_SXY2] >> 16) & 0xFFFF); const v: i64 = gteClampMac0(g, sx0 * (sy1 - sy2) + sx1 * (sy2 - sy0) + sx2 * (sy0 - sy1)); g[D_MAC0] = (v as u64 & 0xFFFFFFFF) as u32; } // OP - outer product / cross product variant: // MAC1 = RT22*IR3 - RT33*IR2 // MAC2 = RT33*IR1 - RT11*IR3 // MAC3 = RT11*IR2 - RT22*IR1 // Uses the rotation matrix diagonal as the input vector. Used by some // lighting normal computations. pub fn gteOp(g: *mut[] u32, sf: u32, lm: u32) { const rt11: i64 = s16ToI64(g[32 + 0] & 0xFFFF); const rt22: i64 = s16ToI64(g[32 + 2] & 0xFFFF); const rt33: i64 = s16ToI64(g[32 + 4] & 0xFFFF); const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); // Each runs through gteClampMac (>>sf + flag) then the IR clamp. const mac1: i64 = gteClampMac(g, 1, rt22 * ir3 - rt33 * ir2, sf); const mac2: i64 = gteClampMac(g, 2, rt33 * ir1 - rt11 * ir3, sf); const mac3: i64 = gteClampMac(g, 3, rt11 * ir2 - rt22 * ir1, sf); g[D_MAC1] = (mac1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (mac2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (mac3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, mac1, mac2, mac3, lm); } // 1. L * V -> MAC, IR (vertex normal in light space) // 2. BK + LC*IR -> MAC, IR (background + reflected light) // 3. ir' = clamp((FC<<12) - (C<<4)*IR), with lm=0 // 4. MAC = (C<<4)*IR + IR0*ir' (interpolate to far-colour) // 5. IR = clamp(MAC, lm) // 6. RGB FIFO push; RC2/GC2/BC2 = clamp_rgb(MAC >> 4) // // Control-reg layout: // CR[8..12]: L matrix (LL11/LL12, LL13/LL21, LL22/LL23, LL31/LL32, LL33) // CR[13..15]: RBK / GBK / BBK (background colour, 32-bit signed) // CR[16..20]: LC matrix (LR1/LR2, LR3/LG1, LG2/LG3, LB1/LB2, LB3) // CR[21..23]: RFC / GFC / BFC (far colour, 32-bit signed) // // Data-reg layout: // DR[6] byte 0 = R, byte 1 = G, byte 2 = B, byte 3 = CODE (RGBC) // DR[8] = IR0 (depth-queue factor) // DR[20..22] = RGB FIFO entries 0/1/2 pub fn gteNcds(g: *mut[] u32, vidx: u32, sf: u32, lm: u32) { const vSlot: u32 = vidx * 2; const vxy: u32 = g[vSlot]; const vzS: u32 = g[vSlot + 1]; const vx: i64 = s16ToI64(vxy & 0xFFFF); const vy: i64 = s16ToI64((vxy >> 16) & 0xFFFF); const vz: i64 = s16ToI64(vzS & 0xFFFF); // Light matrix. const ll11: i64 = s16ToI64(g[32 + 8] & 0xFFFF); const ll12: i64 = s16ToI64((g[32 + 8] >> 16) & 0xFFFF); const ll13: i64 = s16ToI64(g[32 + 9] & 0xFFFF); const ll21: i64 = s16ToI64((g[32 + 9] >> 16) & 0xFFFF); const ll22: i64 = s16ToI64(g[32 + 10] & 0xFFFF); const ll23: i64 = s16ToI64((g[32 + 10] >> 16) & 0xFFFF); const ll31: i64 = s16ToI64(g[32 + 11] & 0xFFFF); const ll32: i64 = s16ToI64((g[32 + 11] >> 16) & 0xFFFF); const ll33: i64 = s16ToI64(g[32 + 12] & 0xFFFF); var m1a: i64 = ll11 * vx + ll12 * vy + ll13 * vz; var m2a: i64 = ll21 * vx + ll22 * vy + ll23 * vz; var m3a: i64 = ll31 * vx + ll32 * vy + ll33 * vz; m1a = gteClampMac(g, 1, m1a, sf); m2a = gteClampMac(g, 2, m2a, sf); m3a = gteClampMac(g, 3, m3a, sf); const ir1a: i64 = gteClampIr(g, 1, m1a, lm); const ir2a: i64 = gteClampIr(g, 2, m2a, lm); const ir3a: i64 = gteClampIr(g, 3, m3a, lm); const lr1: i64 = s16ToI64(g[32 + 16] & 0xFFFF); const lr2: i64 = s16ToI64((g[32 + 16] >> 16) & 0xFFFF); const lr3: i64 = s16ToI64(g[32 + 17] & 0xFFFF); const lg1: i64 = s16ToI64((g[32 + 17] >> 16) & 0xFFFF); const lg2: i64 = s16ToI64(g[32 + 18] & 0xFFFF); const lg3: i64 = s16ToI64((g[32 + 18] >> 16) & 0xFFFF); const lb1: i64 = s16ToI64(g[32 + 19] & 0xFFFF); const lb2: i64 = s16ToI64((g[32 + 19] >> 16) & 0xFFFF); const lb3: i64 = s16ToI64(g[32 + 20] & 0xFFFF); const rbk: i64 = s32ToI64(g[32 + 13]); const gbk: i64 = s32ToI64(g[32 + 14]); const bbk: i64 = s32ToI64(g[32 + 15]); const b1a: i64 = gteCheckMac(g, 1, (rbk << 12) + lr1 * ir1a); const b1b: i64 = gteCheckMac(g, 1, b1a + lr2 * ir2a); const m1b: i64 = gteClampMac(g, 1, b1b + lr3 * ir3a, sf); const b2a: i64 = gteCheckMac(g, 2, (gbk << 12) + lg1 * ir1a); const b2b: i64 = gteCheckMac(g, 2, b2a + lg2 * ir2a); const m2b: i64 = gteClampMac(g, 2, b2b + lg3 * ir3a, sf); const b3a: i64 = gteCheckMac(g, 3, (bbk << 12) + lb1 * ir1a); const b3b: i64 = gteCheckMac(g, 3, b3a + lb2 * ir2a); const m3b: i64 = gteClampMac(g, 3, b3b + lb3 * ir3a, sf); const ir1b: i64 = gteClampIr(g, 1, m1b, lm); const ir2b: i64 = gteClampIr(g, 2, m2b, lm); const ir3b: i64 = gteClampIr(g, 3, m3b, lm); const rgbc: u32 = g[6]; const rc: i64 = (rgbc & 0xFF) as i64; const gc: i64 = ((rgbc >> 8) & 0xFF) as i64; const bc: i64 = ((rgbc >> 16) & 0xFF) as i64; const rfc: i64 = s32ToI64(g[32 + 21]); const gfc: i64 = s32ToI64(g[32 + 22]); const bfc: i64 = s32ToI64(g[32 + 23]); var fp1: i64 = (rfc << 12) - (rc << 4) * ir1b; var fp2: i64 = (gfc << 12) - (gc << 4) * ir2b; var fp3: i64 = (bfc << 12) - (bc << 4) * ir3b; fp1 = gteClampMac(g, 1, fp1, sf); fp2 = gteClampMac(g, 2, fp2, sf); fp3 = gteClampMac(g, 3, fp3, sf); const ir1f: i64 = gteClampIr(g, 1, fp1, 0); const ir2f: i64 = gteClampIr(g, 2, fp2, 0); const ir3f: i64 = gteClampIr(g, 3, fp3, 0); const ir0: i64 = s16ToI64(g[8]); var m1c: i64 = (rc << 4) * ir1b + ir0 * ir1f; var m2c: i64 = (gc << 4) * ir2b + ir0 * ir2f; var m3c: i64 = (bc << 4) * ir3b + ir0 * ir3f; m1c = gteClampMac(g, 1, m1c, sf); m2c = gteClampMac(g, 2, m2c, sf); m3c = gteClampMac(g, 3, m3c, sf); g[D_MAC1] = (m1c as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (m2c as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (m3c as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, m1c, m2c, m3c, lm); gtePushRgb(g, m1c, m2c, m3c); } // SQR - square each IR component. Used for vector-length computations. // MAC{1,2,3} = IR{1,2,3}^2; IR{1,2,3} = clamp(MAC). pub fn gteSqr(g: *mut[] u32, sf: u32, lm: u32) { const ir1: i64 = s16ToI64(g[9]); const ir2: i64 = s16ToI64(g[10]); const ir3: i64 = s16ToI64(g[11]); const mac1: i64 = gteClampMac(g, 1, ir1 * ir1, sf); const mac2: i64 = gteClampMac(g, 2, ir2 * ir2, sf); const mac3: i64 = gteClampMac(g, 3, ir3 * ir3, sf); g[D_MAC1] = (mac1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (mac2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (mac3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, mac1, mac2, mac3, lm); } pub fn gteMvmva(g: *mut[] u32, opc: u32) { const sf: u32 = ((opc >> 19) & 1) * 12; const lm: u32 = (opc >> 10) & 1; const cvSel: u32 = (opc >> 13) & 3; const vSel: u32 = (opc >> 15) & 3; const mxSel: u32 = (opc >> 17) & 3; var m11: i64 = 0; var m12: i64 = 0; var m13: i64 = 0; var m21: i64 = 0; var m22: i64 = 0; var m23: i64 = 0; var m31: i64 = 0; var m32: i64 = 0; var m33: i64 = 0; if (mxSel == 3) { const rcg: i64 = (g[6] & 0xFF) as i64; const ir0g: i64 = s16ToI64(g[8]); const rt13: i64 = s16ToI64(g[32 + 1] & 0xFFFF); const rt22: i64 = s16ToI64(g[32 + 2] & 0xFFFF); m11 = -(rcg << 4); m12 = rcg << 4; m13 = ir0g; m21 = rt13; m22 = rt13; m23 = rt13; m31 = rt22; m32 = rt22; m33 = rt22; } else { var mxBase: u32 = 0; if (mxSel == 1) { mxBase = 8; } if (mxSel == 2) { mxBase = 16; } m11 = s16ToI64(g[32 + mxBase + 0] & 0xFFFF); m12 = s16ToI64((g[32 + mxBase + 0] >> 16) & 0xFFFF); m13 = s16ToI64(g[32 + mxBase + 1] & 0xFFFF); m21 = s16ToI64((g[32 + mxBase + 1] >> 16) & 0xFFFF); m22 = s16ToI64(g[32 + mxBase + 2] & 0xFFFF); m23 = s16ToI64((g[32 + mxBase + 2] >> 16) & 0xFFFF); m31 = s16ToI64(g[32 + mxBase + 3] & 0xFFFF); m32 = s16ToI64((g[32 + mxBase + 3] >> 16) & 0xFFFF); m33 = s16ToI64(g[32 + mxBase + 4] & 0xFFFF); } var vx: i64 = 0; var vy: i64 = 0; var vz: i64 = 0; if (vSel == 3) { vx = s16ToI64(g[9]); vy = s16ToI64(g[10]); vz = s16ToI64(g[11]); } else { const slot: u32 = vSel * 2; const vxy: u32 = g[slot]; const vzS: u32 = g[slot + 1]; vx = s16ToI64(vxy & 0xFFFF); vy = s16ToI64((vxy >> 16) & 0xFFFF); vz = s16ToI64(vzS & 0xFFFF); } var tx: i64 = 0; var ty: i64 = 0; var tz: i64 = 0; if (cvSel == 0) { tx = s32ToI64(g[32 + 5]); ty = s32ToI64(g[32 + 6]); tz = s32ToI64(g[32 + 7]); } if (cvSel == 1) { tx = s32ToI64(g[32 + 13]); ty = s32ToI64(g[32 + 14]); tz = s32ToI64(g[32 + 15]); } if (cvSel == 2) { tx = s32ToI64(g[32 + 21]); ty = s32ToI64(g[32 + 22]); tz = s32ToI64(g[32 + 23]); } var sm1: i64 = 0; var sm2: i64 = 0; var sm3: i64 = 0; if (cvSel == 2) { const k1: i64 = gteCheckMac(g, 1, m12 * vy); sm1 = gteClampMac(g, 1, k1 + m13 * vz, sf); const k2: i64 = gteCheckMac(g, 2, m22 * vy); sm2 = gteClampMac(g, 2, k2 + m23 * vz, sf); const k3: i64 = gteCheckMac(g, 3, m32 * vy); sm3 = gteClampMac(g, 3, k3 + m33 * vz, sf); const d1: i64 = gteClampMac(g, 1, (tx << 12) + m11 * vx, sf); const d2: i64 = gteClampMac(g, 2, (ty << 12) + m21 * vx, sf); const d3: i64 = gteClampMac(g, 3, (tz << 12) + m31 * vx, sf); gteClampIr(g, 1, d1, 0); gteClampIr(g, 2, d2, 0); gteClampIr(g, 3, d3, 0); } else { const c1a: i64 = gteCheckMac(g, 1, (tx << 12) + m11 * vx); const c1b: i64 = gteCheckMac(g, 1, c1a + m12 * vy); sm1 = gteClampMac(g, 1, c1b + m13 * vz, sf); const c2a: i64 = gteCheckMac(g, 2, (ty << 12) + m21 * vx); const c2b: i64 = gteCheckMac(g, 2, c2a + m22 * vy); sm2 = gteClampMac(g, 2, c2b + m23 * vz, sf); const c3a: i64 = gteCheckMac(g, 3, (tz << 12) + m31 * vx); const c3b: i64 = gteCheckMac(g, 3, c3a + m32 * vy); sm3 = gteClampMac(g, 3, c3b + m33 * vz, sf); } g[D_MAC1] = (sm1 as u64 & 0xFFFFFFFF) as u32; g[D_MAC2] = (sm2 as u64 & 0xFFFFFFFF) as u32; g[D_MAC3] = (sm3 as u64 & 0xFFFFFFFF) as u32; gteIrTriple(g, sm1, sm2, sm3, lm); }