Something went wrong. Try again.
A Playstation One (PS1) emulator fully written in Jam
Something went wrong. Try again.
jamstation gte.jam
48 kB · 1140 lines
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141// 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-onlypub 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/2pub 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);}