A Playstation One (PS1) emulator fully written in Jam
43 kB
1211 lines
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Raphael Amorim
3
4// [0..15] command FIFO buffer
5// [16] buf_index
6// [17] cmd_args_remaining
7// [18] cmd_data_remaining
8// [19] state (0=RECV_CMD, 1=RECV_ARGS, 2=RECV_DATA)
9// [20] recv_data (latched arg byte for RECV_DATA pump)
10// [21] color (current command's 24-bit BGR colour)
11// [22..29] generic command counters: xpos, ypos, xsiz, ysiz, tsiz,
12// addr, xcnt, ycnt
13// [30..33] unused (was v0..v3 vertex scratch; the rasterizer reads none)
14// [34..37] OWNED BY main.jam's frame loop, NOT the GPU: [34]=last CPU-clocked
15// SPU sample, [35]=SPU sample accumulator, [36]=scanline,
16// [37]=f32 GPU-cycle accumulator (type-punned). Do NOT reuse here.
17// [38..43] C0 (VRAM->CPU) state: xcnt, ycnt, xsiz, ysiz, tsiz, addr
18// [44] gp1_10h_req
19// [45] gpuread
20// [46] gpustat
21// [47] display_mode
22// [48..51] draw_x1, draw_y1, draw_x2, draw_y2
23// [52..53] off_x, off_y (11-bit signed, sign-extended)
24// [54..57] texw_mx, texw_my, texw_ox, texw_oy
25// [58..59] clut_x, clut_y
26// [60..62] texp_x, texp_y, texp_d
27// [63..68] disp_x, disp_y, disp_x1..disp_y2
28// [69] unused
29// [70] texture-disable raw input (E1 bit 11)
30// [71] texture-disable allow gate (GP1 0x09); GPUSTAT.15 = g[71] & g[70]
31
32const { irqRaise, IC_VBLANK } = import("irq");
33
34const GPU_STATE_WORDS: u32 = 80;
35const VRAM_PIXELS: u32 = 524288;
36const GP_RECV_CMD: u32 = 0;
37const GP_RECV_ARGS: u32 = 1;
38const GP_RECV_DATA: u32 = 2;
39
40pub fn gpuToBgr555(c: u32) u32 {
41 return ((c & 0x0000F8) >> 3)
42 | ((c & 0x00F800) >> 6)
43 | ((c & 0xF80000) >> 9);
44}
45
46pub fn gpuFamilyCount(g: *mut[] u32, family: u32) u32 {
47 return g[72 + (family & 7)];
48}
49
50pub fn vramWritePixel(vram: *mut[] u8, x: u32, y: u32, bgr555: u32) {
51 if (x >= 1024 || y >= 512) { return; }
52 const off: u32 = (y * 1024 + x) * 2;
53 vram[off] = (bgr555 & 0xFF) as u8;
54 vram[off + 1] = ((bgr555 >> 8) & 0xFF) as u8;
55}
56
57pub fn vramReadPixel(vram: *mut[] u8, x: u32, y: u32) u32 {
58 if (x >= 1024 || y >= 512) { return 0; }
59 const off: u32 = (y * 1024 + x) * 2;
60 return (vram[off] as u32) | ((vram[off + 1] as u32) << 8);
61}
62
63pub fn gpuRead32(g: *mut[] u32, vram: *mut[] u8, off: u32) u32 {
64 if (off == 0x00) {
65 var data: u32 = 0;
66 if (g[42] != 0) {
67 // C0 (VRAM->CPU) transfer in progress - drain two pixels.
68 data = vramPackTwo(g, vram);
69 g[42] = g[42] - 2;
70 }
71 if (g[44] != 0) {
72 const sub: u32 = g[44] & 7;
73 if (sub == 2) {
74 data = ((g[57] >> 3) << 15) | ((g[56] >> 3) << 10)
75 | ((g[55] >> 3) << 5) | (g[54] >> 3);
76 }
77 if (sub == 3) { data = (g[49] << 10) | g[48]; }
78 if (sub == 4) { data = (g[51] << 10) | g[50]; }
79 if (sub == 5) { data = (g[53] << 10) | g[52]; }
80 g[44] = 0;
81 }
82 return data;
83 }
84 if (off == 0x04) {
85 // OR GPUSTAT with 0x1C000000 to keep the BIOS happy.
86 return g[46] | 0x1C000000;
87 }
88 return 0;
89}
90
91pub fn gpuRead16(g: *mut[] u32, vram: *mut[] u8, off: u32) u32 {
92 return gpuRead32(g, vram, off & 0xFFFFFFFC) & 0xFFFF;
93}
94
95pub fn gpuRead8(g: *mut[] u32, vram: *mut[] u8, off: u32) u32 {
96 return gpuRead32(g, vram, off & 0xFFFFFFFC) & 0xFF;
97}
98
99pub fn vramPackTwo(g: *mut[] u32, vram: *mut[] u8) u32 {
100 const baseX: u32 = g[43] & 0x3FF;
101 const baseY: u32 = (g[43] >> 10) & 0x1FF;
102 var x0: u32 = (baseX + g[38]) & 0x3FF;
103 var y0: u32 = (baseY + g[39]) & 0x1FF;
104 var data: u32 = vramReadPixel(vram, x0, y0);
105 g[38] = g[38] + 1;
106 if (g[38] == g[40]) { g[39] = g[39] + 1; g[38] = 0; }
107 g[45] = g[45] - 1;
108 if (g[45] > 0) {
109 var x1: u32 = (baseX + g[38]) & 0x3FF;
110 var y1: u32 = (baseY + g[39]) & 0x1FF;
111 data = data | (vramReadPixel(vram, x1, y1) << 16);
112 g[38] = g[38] + 1;
113 if (g[38] == g[40]) { g[39] = g[39] + 1; g[38] = 0; }
114 g[45] = g[45] - 1;
115 }
116 return data;
117}
118
119pub fn gpuWrite32(g: *mut[] u32, vram: *mut[] u8, off: u32, val: u32) {
120 match (off) {
121 0x00 { gpuGp0(g, vram, val); }
122 0x04 { gpuGp1(g, val); }
123 _ {}
124 }
125}
126
127pub fn gpuWrite16(g: *mut[] u32, vram: *mut[] u8, off: u32, val: u32) {
128 gpuWrite32(g, vram, off, val & 0xFFFF);
129}
130
131pub fn gpuWrite8(g: *mut[] u32, vram: *mut[] u8, off: u32, val: u32) {
132 gpuWrite32(g, vram, off, val & 0xFF);
133}
134
135pub fn gpuGp0(g: *mut[] u32, vram: *mut[] u8, val: u32) {
136 const st: u32 = g[19];
137 if (st == GP_RECV_CMD) {
138 g[16] = 0;
139 g[g[16]] = val;
140 g[16] = g[16] + 1;
141 gpuUpdateCmd(g, vram);
142 } else if (st == GP_RECV_ARGS) {
143 g[g[16]] = val;
144 g[16] = g[16] + 1;
145 if (g[17] > 0) { g[17] = g[17] - 1; }
146 gpuUpdateCmd(g, vram);
147 } else {
148 g[20] = val;
149 gpuUpdateCmd(g, vram);
150 }
151}
152
153// GP1 (control) - display enable, DMA direction, display area, etc.
154pub fn gpuGp1(g: *mut[] u32, val: u32) {
155 match ((val >> 24) & 0xFF) {
156 // GP1(0x00): leave gpustat untouched here.
157 // A previous clobber of gpustat wiped texpage / display-enable bits
158 // accumulated from prior GP0 commands; diff-trace showed Musashi
159 // reading GPUSTAT and getting the wrong value at PC=0x8005C204.
160 // Just reset the command-buffer state; leave gpustat alone.
161 0x00 | 0x01 {
162 g[19] = GP_RECV_CMD;
163 g[16] = 0;
164 g[17] = 0;
165 g[18] = 0;
166 }
167 // Acknowledge GPU IRQ (clear bit 24 of GPUSTAT).
168 0x02 { g[46] = g[46] & (~0x01000000); }
169 // Display enable: bit 23 of GPUSTAT = 1 when disabled.
170 0x03 {
171 g[46] = g[46] & (~0x00800000);
172 g[46] = g[46] | ((val << 23) & 0x00800000);
173 }
174 // GP1(04h) DMA direction. Handled as a no-op here: the direction is
175 // never reflected into GPUSTAT bits 29-30, so they stay 0. Real
176 // hardware *does* set them, but reflecting the direction here made
177 // GPUSTAT bit 30 read 1 (dir 2), and a game branch on that bit then
178 // diverged (REG_TRACE cmp, L≈19.477M). Keep it a no-op.
179 0x04 { }
180 0x05 {
181 g[63] = val & 0x3FF;
182 g[64] = (val >> 10) & 0x1FF;
183 }
184 0x06 {
185 g[65] = val & 0xFFF;
186 g[66] = (val >> 12) & 0xFFF;
187 }
188 0x07 {
189 g[67] = val & 0x1FF;
190 g[68] = (val >> 10) & 0x1FF;
191 }
192 0x08 { g[47] = val & 0xFFFFFF; }
193 // GP1(0x09) - Allow Texture Disable. Stores the allow flag in g[71];
194 // GPUSTAT.15 is NOT updated immediately (ps1-tests
195 // testUnsetAllowTextureDisablePreservesBit verifies that the
196 // current bit-15 stays put until the next GP0_E1 write).
197 0x09 { g[71] = val & 1; }
198 0x10 { g[44] = val & 7; }
199 _ {}
200 }
201}
202
203pub fn gpuUpdateCmd(g: *mut[] u32, vram: *mut[] u8) {
204 const cmd: u32 = (g[0] >> 24) & 0xFF;
205 const family: u32 = (g[0] >> 29) & 7;
206
207 if (g[19] == GP_RECV_CMD) {
208 g[72 + family] = g[72 + family] + 1;
209 }
210
211 match (family) {
212 1 { gpuPolyCmd(g, vram); return; }
213 2 { gpuLineCmd(g, vram); return; }
214 3 { gpuRectCmd(g, vram); return; }
215 _ {}
216 }
217
218 match (cmd) {
219 0x00 | 0x01 {
220 g[19] = GP_RECV_CMD; return;
221 }
222 0x02 {
223 gpuFillRect(g, vram); return;
224 }
225
226 0xE1 {
227 g[46] = (g[46] & 0xFFFFF800) | (g[0] & 0x7FF);
228 g[70] = (g[0] >> 11) & 1;
229 g[46] = (g[46] & 0xFFFF7FFF) | ((g[71] & g[70]) << 15);
230 g[60] = (g[46] & 0xF) << 6;
231 g[61] = (g[46] & 0x10) << 4;
232 g[62] = (g[46] >> 7) & 3;
233 g[19] = GP_RECV_CMD;
234 return;
235 }
236 0xE2 {
237 g[54] = (g[0] & 0x1F) << 3;
238 g[55] = ((g[0] >> 5) & 0x1F) << 3;
239 g[56] = ((g[0] >> 10) & 0x1F) << 3;
240 g[57] = ((g[0] >> 15) & 0x1F) << 3;
241 g[19] = GP_RECV_CMD;
242 return;
243 }
244 0xE3 {
245 g[48] = g[0] & 0x3FF;
246 g[49] = (g[0] >> 10) & 0x1FF;
247 g[19] = GP_RECV_CMD;
248 return;
249 }
250 0xE4 {
251 g[50] = g[0] & 0x3FF;
252 g[51] = (g[0] >> 10) & 0x1FF;
253 g[19] = GP_RECV_CMD;
254 return;
255 }
256 0xE5 {
257 var ox: u32 = g[0] & 0x7FF;
258 var oy: u32 = (g[0] >> 11) & 0x7FF;
259 if ((ox & 0x400) != 0) { ox = ox | 0xFFFFF800; }
260 if ((oy & 0x400) != 0) { oy = oy | 0xFFFFF800; }
261 g[52] = ox;
262 g[53] = oy;
263 g[19] = GP_RECV_CMD;
264 return;
265 }
266 0xE6 {
267 g[46] = (g[46] & 0xFFFFE7FF) | (((g[0] & 0x03) << 11) & 0x1800);
268 g[58] = g[0] & 0x01; // set-mask flag (raster-time)
269 g[59] = (g[0] >> 1) & 0x01; // check-mask flag
270 g[19] = GP_RECV_CMD;
271 return;
272 }
273
274 0x80 { gpuVramToVram(g, vram); return; }
275 0xA0 { gpuCpuToVram(g, vram); return; }
276 0xC0 { gpuVramToCpu(g, vram); return; }
277 _ { g[19] = GP_RECV_CMD; }
278 }
279}
280
281pub fn gpuFillRect(g: *mut[] u32, vram: *mut[] u8) {
282 if (g[19] == GP_RECV_CMD) {
283 g[19] = GP_RECV_ARGS;
284 g[17] = 2;
285 return;
286 }
287 if (g[17] != 0) { return; }
288 const color: u32 = gpuToBgr555(g[0] & 0xFFFFFF);
289 const x0: u32 = g[1] & 0x3F0;
290 const y0: u32 = (g[1] >> 16) & 0x1FF;
291 const w: u32 = ((g[2] & 0x3FF) + 0xF) & 0xFFFFFFF0;
292 const h: u32 = (g[2] >> 16) & 0x1FF;
293 var y: u32 = 0;
294 while (y < h) {
295 var x: u32 = 0;
296 while (x < w) {
297 vramWritePixel(vram, x0 + x, y0 + y, color);
298 x = x + 1;
299 }
300 y = y + 1;
301 }
302 g[19] = GP_RECV_CMD;
303}
304
305pub fn gpuVramToVram(g: *mut[] u32, vram: *mut[] u8) {
306 if (g[19] == GP_RECV_CMD) {
307 g[19] = GP_RECV_ARGS;
308 g[17] = 3;
309 return;
310 }
311 if (g[17] != 0) { return; }
312 const sx: u32 = g[1] & 0xFFFF;
313 const sy: u32 = (g[1] >> 16) & 0xFFFF;
314 const dx: u32 = g[2] & 0xFFFF;
315 const dy: u32 = (g[2] >> 16) & 0xFFFF;
316 const w: u32 = g[3] & 0xFFFF;
317 const h: u32 = (g[3] >> 16) & 0xFFFF;
318 var y: u32 = 0;
319 while (y < h) {
320 var x: u32 = 0;
321 while (x < w) {
322 const px: u32 = vramReadPixel(vram, sx + x, sy + y);
323 vramWritePixel(vram, dx + x, dy + y, px);
324 x = x + 1;
325 }
326 y = y + 1;
327 }
328 g[19] = GP_RECV_CMD;
329}
330
331pub fn gpuCpuToVram(g: *mut[] u32, vram: *mut[] u8) {
332 if (g[19] == GP_RECV_CMD) {
333 g[19] = GP_RECV_ARGS;
334 g[17] = 2;
335 return;
336 }
337 if (g[19] == GP_RECV_ARGS) {
338 if (g[17] != 0) { return; }
339 g[22] = g[1] & 0x3FF;
340 g[23] = (g[1] >> 16) & 0x1FF;
341 var xs: u32 = g[2] & 0xFFFF;
342 var ys: u32 = (g[2] >> 16) & 0xFFFF;
343 xs = ((xs - 1) & 0x3FF) + 1;
344 ys = ((ys - 1) & 0x1FF) + 1;
345 g[24] = xs;
346 g[25] = ys;
347 g[26] = ((xs * ys) + 1) & 0xFFFFFFFE;
348 g[28] = 0;
349 g[29] = 0;
350 g[19] = GP_RECV_DATA;
351 return;
352 }
353 const x0: u32 = (g[22] + g[28]) & 0x3FF;
354 const y0: u32 = (g[23] + g[29]) & 0x1FF;
355 vramWritePixel(vram, x0, y0, g[20] & 0xFFFF);
356 g[28] = g[28] + 1;
357 if (g[28] == g[24]) { g[29] = g[29] + 1; g[28] = 0; }
358 const x1: u32 = (g[22] + g[28]) & 0x3FF;
359 const y1: u32 = (g[23] + g[29]) & 0x1FF;
360 vramWritePixel(vram, x1, y1, (g[20] >> 16) & 0xFFFF);
361 g[28] = g[28] + 1;
362 if (g[28] == g[24]) { g[29] = g[29] + 1; g[28] = 0; }
363 g[26] = g[26] - 2;
364 if (g[26] == 0) {
365 g[28] = 0;
366 g[29] = 0;
367 g[19] = GP_RECV_CMD;
368 }
369}
370
371pub fn gpuVramToCpu(g: *mut[] u32, vram: *mut[] u8) {
372 if (g[19] == GP_RECV_CMD) {
373 g[19] = GP_RECV_ARGS;
374 g[17] = 2;
375 return;
376 }
377 if (g[17] != 0) { return; }
378 const x0: u32 = g[1] & 0x3FF;
379 const y0: u32 = (g[1] >> 16) & 0x1FF;
380 var xs: u32 = g[2] & 0xFFFF;
381 var ys: u32 = (g[2] >> 16) & 0xFFFF;
382 xs = ((xs - 1) & 0x3FF) + 1;
383 ys = ((ys - 1) & 0x1FF) + 1;
384 g[38] = 0;
385 g[39] = 0;
386 g[40] = xs;
387 g[41] = ys;
388 g[42] = ((xs * ys) + 1) & 0xFFFFFFFE;
389 g[43] = x0 | (y0 << 10);
390 g[45] = xs * ys;
391 g[19] = GP_RECV_CMD;
392}
393
394pub fn gpuPolyCmd(g: *mut[] u32, vram: *mut[] u8) {
395 if (g[19] == GP_RECV_CMD) {
396 const flags: u32 = (g[0] >> 24) & 0xFF;
397 const isQuad: bool = (flags & 0x08) != 0;
398 const isTex: bool = (flags & 0x04) != 0;
399 const isGour: bool = (flags & 0x10) != 0;
400 var verts: u32 = 3;
401 if (isQuad) { verts = 4; }
402 var perVert: u32 = 1;
403 if (isTex) { perVert = perVert + 1; }
404 if (isGour) { perVert = perVert + 1; }
405 var args: u32 = perVert * verts;
406 if (isGour) { args = args - 1; }
407 g[17] = args;
408 g[19] = GP_RECV_ARGS;
409 return;
410 }
411 if (g[17] != 0) { return; }
412 g[19] = GP_RECV_CMD;
413 gpuRasterPoly(g, vram);
414}
415
416pub fn gpuSe11(v: u32) u32 {
417 if ((v & 0x400) != 0) { return v | 0xFFFFF800; }
418 return v & 0x7FF;
419}
420
421pub fn gpuVertX(xy: u32) u32 {
422 return gpuSe11(xy & 0xFFFF);
423}
424
425pub fn gpuVertY(xy: u32) u32 {
426 return gpuSe11((xy >> 16) & 0xFFFF);
427}
428
429pub fn gpuEdge(ax: u32, ay: u32, bx: u32, by: u32, cx: u32, cy: u32) i64 {
430 const ax64: i64 = sext32(ax);
431 const ay64: i64 = sext32(ay);
432 const bx64: i64 = sext32(bx);
433 const by64: i64 = sext32(by);
434 const cx64: i64 = sext32(cx);
435 const cy64: i64 = sext32(cy);
436 return (bx64 - ax64) * (cy64 - ay64) - (by64 - ay64) * (cx64 - ax64);
437}
438
439pub fn sext32(v: u32) i64 {
440 var w: i64 = v as i64;
441 if ((v & 0x80000000) != 0) {
442 w = w - 0x100000000;
443 }
444 return w;
445}
446
447pub fn gpuTopLeftRule(z: i64, ax: u32, ay: u32, bx: u32, by: u32) bool {
448 if (z < 0) { return true; }
449 if (z != 0) { return false; }
450 const ays: i64 = sext32(ay);
451 const bys: i64 = sext32(by);
452 const axs: i64 = sext32(ax);
453 const bxs: i64 = sext32(bx);
454 if (bys > ays) { return true; }
455 if (bys == ays && bxs < axs) { return true; }
456 return false;
457}
458
459pub fn gpuMin3(a: u32, b: u32, c: u32) u32 {
460 var m: u32 = a;
461 if (sext32(b) < sext32(m)) { m = b; }
462 if (sext32(c) < sext32(m)) { m = c; }
463 return m;
464}
465
466pub fn gpuMax3(a: u32, b: u32, c: u32) u32 {
467 var m: u32 = a;
468 if (sext32(b) > sext32(m)) { m = b; }
469 if (sext32(c) > sext32(m)) { m = c; }
470 return m;
471}
472
473pub fn gpuRasterPoly(g: *mut[] u32, vram: *mut[] u8) {
474 const flags: u32 = (g[0] >> 24) & 0xFF;
475 const isQuad: bool = (flags & 0x08) != 0;
476 const isTex: bool = (flags & 0x04) != 0;
477 const isGour: bool = (flags & 0x10) != 0;
478 const isRaw: bool = (flags & 0x01) != 0;
479
480 // PSX command layout per nocash psx-spx:
481 // monochrome triangle buf[0]=cmd+colour, buf[1..3]=xy
482 // monochrome quad buf[0]=cmd+colour, buf[1..4]=xy
483 // textured triangle buf[0]=cmd+colour, buf[1]=vv0xy,
484 // buf[2]=vv0clut+uv, buf[3]=vv1xy,
485 // buf[4]=vv1page+uv, buf[5]=vv2xy,
486 // buf[6]=vv2uv
487 // textured quad +buf[7]=vv3xy, buf[8]=vv3uv
488 // shaded triangle buf[0]=colour0+cmd, buf[1]=vv0xy,
489 // buf[2]=colour1, buf[3]=vv1xy, ...
490 // We only render mono + textured here; shaded reuses the base
491 // colour from buf[0].
492
493 var v0x: u32 = 0; var v0y: u32 = 0;
494 var v1x: u32 = 0; var v1y: u32 = 0;
495 var v2x: u32 = 0; var v2y: u32 = 0;
496 var v3x: u32 = 0; var v3y: u32 = 0;
497 var uu0: u32 = 0; var vv0: u32 = 0;
498 var uu1: u32 = 0; var vv1: u32 = 0;
499 var uu2: u32 = 0; var vv2: u32 = 0;
500 var uu3: u32 = 0; var vv3: u32 = 0;
501
502 var c0: u32 = g[0] & 0xFFFFFF;
503 var c1: u32 = c0;
504 var c2: u32 = c0;
505 var c3: u32 = c0;
506 var clutX: u32 = 0; var clutY: u32 = 0;
507 var tpx: u32 = g[60];
508 var tpy: u32 = g[61];
509 var depth: u32 = g[62];
510
511 // Decode the four PSX polygon-command shapes:
512 // mono flat buf[0]=cmd+col, buf[1..n]=xy
513 // tex flat buf[0]=cmd+col, then per-vert (xy, page/clut+uv)
514 // mono shaded buf[0]=col0+cmd, then per-vert (xy, colN+pad)
515 // tex shaded buf[0]=col0+cmd, then per-vert (xy, page/clut+uv, colN)
516 if (isTex && isGour) {
517 v0x = gpuVertX(g[1]); v0y = gpuVertY(g[1]);
518 uu0 = g[2] & 0xFF; vv0 = (g[2] >> 8) & 0xFF;
519 const clut: u32 = (g[2] >> 16) & 0xFFFF;
520 clutX = (clut & 0x3F) << 4;
521 clutY = (clut >> 6) & 0x1FF;
522 c1 = g[3] & 0xFFFFFF;
523 v1x = gpuVertX(g[4]); v1y = gpuVertY(g[4]);
524 uu1 = g[5] & 0xFF; vv1 = (g[5] >> 8) & 0xFF;
525 const page: u32 = (g[5] >> 16) & 0xFFFF;
526 tpx = (page & 0xF) << 6;
527 tpy = (page & 0x10) << 4;
528 depth = (page >> 7) & 3;
529 // Undocumented behaviour (fixes Mortal Kombat II, Bubble Bobble,
530 // Driver 1 & 2): textured polys update GPUSTAT bits 0-8 from the
531 // polygon's tpage byte. Without this, the GPUSTAT texpage bits
532 // drift from the expected value.
533 // Undocumented behaviour: textured polys also update GPUSTAT
534 // bits 0-8 from the tpage byte, and (when GP1(0x09) "allow
535 // texture disable" is on) also drive bit 15 from tpage bit 11.
536 // ps1-tests gpu/gp0-e1 testTexturedPolygonsTextureDisable verifies.
537 g[46] = (g[46] & 0xFFFFFE00) | (page & 0x1FF);
538 g[70] = (page >> 11) & 1;
539 g[46] = (g[46] & 0xFFFF7FFF) | ((g[71] & g[70]) << 15);
540 g[60] = tpx;
541 g[61] = tpy;
542 g[62] = depth;
543 c2 = g[6] & 0xFFFFFF;
544 v2x = gpuVertX(g[7]); v2y = gpuVertY(g[7]);
545 uu2 = g[8] & 0xFF; vv2 = (g[8] >> 8) & 0xFF;
546 if (isQuad) {
547 c3 = g[9] & 0xFFFFFF;
548 v3x = gpuVertX(g[10]); v3y = gpuVertY(g[10]);
549 uu3 = g[11] & 0xFF; vv3 = (g[11] >> 8) & 0xFF;
550 }
551 } else if (isTex) {
552 v0x = gpuVertX(g[1]); v0y = gpuVertY(g[1]);
553 uu0 = g[2] & 0xFF; vv0 = (g[2] >> 8) & 0xFF;
554 const clut: u32 = (g[2] >> 16) & 0xFFFF;
555 clutX = (clut & 0x3F) << 4;
556 clutY = (clut >> 6) & 0x1FF;
557 v1x = gpuVertX(g[3]); v1y = gpuVertY(g[3]);
558 uu1 = g[4] & 0xFF; vv1 = (g[4] >> 8) & 0xFF;
559 const page: u32 = (g[4] >> 16) & 0xFFFF;
560 tpx = (page & 0xF) << 6;
561 tpy = (page & 0x10) << 4;
562 depth = (page >> 7) & 3;
563 // Undocumented behaviour: textured polys also update GPUSTAT
564 // bits 0-8 from the tpage byte, and (when GP1(0x09) "allow
565 // texture disable" is on) also drive bit 15 from tpage bit 11.
566 // ps1-tests gpu/gp0-e1 testTexturedPolygonsTextureDisable verifies.
567 g[46] = (g[46] & 0xFFFFFE00) | (page & 0x1FF);
568 g[70] = (page >> 11) & 1;
569 g[46] = (g[46] & 0xFFFF7FFF) | ((g[71] & g[70]) << 15);
570 // Undocumented behaviour (fixes Mortal Kombat II, Bubble Bobble,
571 // Driver 1 & 2 - and Musashi FMV):
572 // propagate the polygon's tpage into the derived globals so a
573 // following textured RECTANGLE (which carries clut but no
574 // tpage) inherits this state instead of stale values. Without
575 // it, the rect samples the wrong VRAM region -> texel 0 ->
576 // texel-0 skip -> entire FMV frame is black.
577 g[60] = tpx;
578 g[61] = tpy;
579 g[62] = depth;
580 v2x = gpuVertX(g[5]); v2y = gpuVertY(g[5]);
581 uu2 = g[6] & 0xFF; vv2 = (g[6] >> 8) & 0xFF;
582 if (isQuad) {
583 v3x = gpuVertX(g[7]); v3y = gpuVertY(g[7]);
584 uu3 = g[8] & 0xFF; vv3 = (g[8] >> 8) & 0xFF;
585 }
586 } else if (isGour) {
587 // gouraud flat (cmd 0x30, 0x32, 0x38, 0x3A) - col0 already in c0
588 v0x = gpuVertX(g[1]); v0y = gpuVertY(g[1]);
589 c1 = g[2] & 0xFFFFFF;
590 v1x = gpuVertX(g[3]); v1y = gpuVertY(g[3]);
591 c2 = g[4] & 0xFFFFFF;
592 v2x = gpuVertX(g[5]); v2y = gpuVertY(g[5]);
593 if (isQuad) {
594 c3 = g[6] & 0xFFFFFF;
595 v3x = gpuVertX(g[7]); v3y = gpuVertY(g[7]);
596 }
597 } else {
598 // monochrome flat (cmd 0x20, 0x22, 0x28, 0x2A)
599 v0x = gpuVertX(g[1]); v0y = gpuVertY(g[1]);
600 v1x = gpuVertX(g[2]); v1y = gpuVertY(g[2]);
601 v2x = gpuVertX(g[3]); v2y = gpuVertY(g[3]);
602 if (isQuad) {
603 v3x = gpuVertX(g[4]); v3y = gpuVertY(g[4]);
604 }
605 }
606
607 const baseColor: u32 = g[0] & 0xFFFFFF;
608
609 const isTransp: bool = (flags & 0x02) != 0;
610 var transpMode: u32 = (g[46] >> 5) & 3;
611 if (isTex) {
612 var page: u32 = (g[4] >> 16) & 0xFFFF;
613 if (isGour) {
614 page = (g[5] >> 16) & 0xFFFF;
615 }
616 transpMode = (page >> 5) & 3;
617 }
618
619 gpuRasterTri(g, vram, isTex, isRaw, isTransp, transpMode, isGour,
620 v0x, v0y, uu0, vv0, c0,
621 v1x, v1y, uu1, vv1, c1,
622 v2x, v2y, uu2, vv2, c2,
623 tpx, tpy, clutX, clutY, depth, baseColor);
624 if (isQuad) {
625 gpuRasterTri(g, vram, isTex, isRaw, isTransp, transpMode, isGour,
626 v1x, v1y, uu1, vv1, c1,
627 v2x, v2y, uu2, vv2, c2,
628 v3x, v3y, uu3, vv3, c3,
629 tpx, tpy, clutX, clutY, depth, baseColor);
630 }
631}
632
633pub fn gpuRasterTri(g: *mut[] u32, vram: *mut[] u8,
634 isTex: bool, isRaw: bool, isTransp: bool,
635 transpMode: u32, isGour: bool,
636 ax0: u32, ay0: u32, au: u32, av: u32, ac: u32,
637 bx0: u32, by0: u32, bu: u32, bv: u32, bc: u32,
638 cx0: u32, cy0: u32, cu: u32, cv: u32, cc: u32,
639 tpx: u32, tpy: u32, clutX: u32, clutY: u32,
640 depth: u32, baseColor: u32) {
641 const ox: u32 = g[52];
642 const oy: u32 = g[53];
643 var ax: u32 = ax0 + ox;
644 var ay: u32 = ay0 + oy;
645 var bx: u32 = bx0 + ox;
646 var by: u32 = by0 + oy;
647 var cx: u32 = cx0 + ox;
648 var cy: u32 = cy0 + oy;
649 var bu2: u32 = bu; var bv2: u32 = bv;
650 var cu2: u32 = cu; var cv2: u32 = cv;
651 var bc2: u32 = bc;
652 var cc2: u32 = cc;
653
654 const area: i64 = gpuEdge(ax, ay, bx, by, cx, cy);
655 if (area == 0) { return; }
656 if (area < 0) {
657 const tx: u32 = bx; const ty: u32 = by;
658 bx = cx; by = cy; cx = tx; cy = ty;
659 const tu: u32 = bu2; const tv: u32 = bv2;
660 bu2 = cu2; bv2 = cv2;
661 cu2 = tu; cv2 = tv;
662 const tc: u32 = bc2;
663 bc2 = cc2; cc2 = tc;
664 }
665
666 const xmin: u32 = gpuMin3(ax, bx, cx);
667 const ymin: u32 = gpuMin3(ay, by, cy);
668 const xmax: u32 = gpuMax3(ax, bx, cx);
669 const ymax: u32 = gpuMax3(ay, by, cy);
670 // Reject only triangles wider/taller than the GPU's real limits
671 if (sext32(xmax - xmin) > 2048 || sext32(ymax - ymin) > 1024) {
672 return;
673 }
674
675 const absArea: i64 = gpuAbsI64(gpuEdge(ax, ay, bx, by, cx, cy));
676 const dx1: u32 = g[48];
677 const dy1: u32 = g[49];
678 const dx2: u32 = g[50];
679 const dy2: u32 = g[51];
680
681 var y: u32 = ymin;
682 while (sext32(y) <= sext32(ymax)) {
683 var x: u32 = xmin;
684 while (sext32(x) <= sext32(xmax)) {
685 if (sext32(x) >= sext32(dx1) && sext32(x) <= sext32(dx2) &&
686 sext32(y) >= sext32(dy1) && sext32(y) <= sext32(dy2)) {
687 const z0: i64 = gpuEdge(bx, by, cx, cy, x, y);
688 const z1: i64 = gpuEdge(cx, cy, ax, ay, x, y);
689 const z2: i64 = gpuEdge(ax, ay, bx, by, x, y);
690 if (!gpuTopLeftRule(z0, bx, by, cx, cy) &&
691 !gpuTopLeftRule(z1, cx, cy, ax, ay) &&
692 !gpuTopLeftRule(z2, ax, ay, bx, by)) {
693 var color: u32 = 0;
694 if (isTex) {
695 const tx: i64 = (z0 * (au as i64) + z1 * (bu2 as i64)
696 + z2 * (cu2 as i64));
697 const ty: i64 = (z0 * (av as i64) + z1 * (bv2 as i64)
698 + z2 * (cv2 as i64));
699 const txu: u32 = gpuDivClamp(tx, absArea, 0xFF);
700 const tyu: u32 = gpuDivClamp(ty, absArea, 0xFF);
701 // GP0(0xE2) texture window: mask/offset/wrap.
702 const txw: u32 = gpuTexWinX(g, txu);
703 const tyw: u32 = gpuTexWinY(g, tyu);
704 const texel: u32 = gpuFetchTexel(vram, txw, tyw,
705 tpx, tpy,
706 clutX, clutY, depth);
707 if (texel == 0) {
708 x = x + 1;
709 continue;
710 }
711 if (isRaw) {
712 color = texel;
713 } else {
714 // Modulate by the per-vertex gouraud colour
715 // when shaded (the interpolated colour is the
716 // modulator), else the flat base.
717 var modc: u32 = baseColor;
718 if (isGour) {
719 const ar: i64 = (ac as i64) & 0xFF;
720 const ag: i64 = ((ac as i64) >> 8) & 0xFF;
721 const ab: i64 = ((ac as i64) >> 16) & 0xFF;
722 const br: i64 = (bc2 as i64) & 0xFF;
723 const bg: i64 = ((bc2 as i64) >> 8) & 0xFF;
724 const bb: i64 = ((bc2 as i64) >> 16) & 0xFF;
725 const cr: i64 = (cc2 as i64) & 0xFF;
726 const cg: i64 = ((cc2 as i64) >> 8) & 0xFF;
727 const cb: i64 = ((cc2 as i64) >> 16) & 0xFF;
728 const nr: i64 = z0 * ar + z1 * br + z2 * cr;
729 const ng: i64 = z0 * ag + z1 * bg + z2 * cg;
730 const nb: i64 = z0 * ab + z1 * bb + z2 * cb;
731 const ir: u32 = gpuSatChan(nr, absArea, 0);
732 const ig: u32 = gpuSatChan(ng, absArea, 0);
733 const ib: u32 = gpuSatChan(nb, absArea, 0);
734 modc = ir | (ig << 8) | (ib << 16);
735 }
736 color = gpuModulate(texel, modc);
737 }
738 if (isTransp && (texel & 0x8000) != 0) {
739 const back: u32 = vramReadPixel(vram,
740 x & 0x3FF, y & 0x1FF);
741 color = gpuBlend(color, back, transpMode);
742 }
743 } else {
744 // Untextured: gouraud-interpolate the three
745 // per-vertex colours by barycentric weights, or
746 // use the base colour if not shaded. A 4x4 ordered
747 // dither kernel is applied pre-clamp for gradient
748 // banding suppression.
749 var rgb: u32 = baseColor;
750 if (isGour) {
751 const ar: i64 = (ac as i64) & 0xFF;
752 const ag: i64 = ((ac as i64) >> 8) & 0xFF;
753 const ab: i64 = ((ac as i64) >> 16) & 0xFF;
754 const br: i64 = (bc2 as i64) & 0xFF;
755 const bg: i64 = ((bc2 as i64) >> 8) & 0xFF;
756 const bb: i64 = ((bc2 as i64) >> 16) & 0xFF;
757 const cr: i64 = (cc2 as i64) & 0xFF;
758 const cg: i64 = ((cc2 as i64) >> 8) & 0xFF;
759 const cb: i64 = ((cc2 as i64) >> 16) & 0xFF;
760 // Unrounded barycentric numerator (z·c sum).
761 const nr: i64 = z0 * ar + z1 * br + z2 * cr;
762 const ng: i64 = z0 * ag + z1 * bg + z2 * cg;
763 const nb: i64 = z0 * ab + z1 * bb + z2 * cb;
764 // dither offset from a 4x4 kernel indexed by
765 // (x - xmin) & 3 and (y - ymin) & 3.
766 const dx: u32 = (x - xmin) & 3;
767 const dy: u32 = (y - ymin) & 3;
768 const dith: i64 = gpuDitherKernel((dy * 4) + dx);
769 const ir: u32 = gpuSatChan(nr, absArea, dith);
770 const ig: u32 = gpuSatChan(ng, absArea, dith);
771 const ib: u32 = gpuSatChan(nb, absArea, dith);
772 rgb = ir | (ig << 8) | (ib << 16);
773 }
774 color = gpuToBgr555(rgb);
775 if (isTransp) {
776 const back: u32 = vramReadPixel(vram,
777 x & 0x3FF, y & 0x1FF);
778 color = gpuBlend(color, back, transpMode);
779 }
780 }
781 vramWritePixel(vram, x & 0x3FF, y & 0x1FF, color);
782 }
783 }
784 x = x + 1;
785 }
786 y = y + 1;
787 }
788}
789
790pub fn gpuAbsI64(v: i64) i64 {
791 if (v < 0) { return -v; }
792 return v;
793}
794
795// 4x4 ordered dither kernel.
796// Indexed by `dy * 4 + dx` where `dx = (x - xmin) & 3`, `dy = (y - ymin) & 3`.
797pub fn gpuDitherKernel(idx: u32) i64 {
798 match (idx) {
799 0 { return -4; }
800 1 { return 0; }
801 2 { return -3; }
802 3 { return 1; }
803 4 { return 2; }
804 5 { return -2; }
805 6 { return 3; }
806 7 { return -1; }
807 8 { return -3; }
808 9 { return 1; }
809 10 { return -4; }
810 11 { return 0; }
811 12 { return 3; }
812 13 { return -1; }
813 14 { return 2; }
814 _ { return -2; }
815 }
816}
817
818pub fn gpuSatChan(num: i64, area: i64, dith: i64) u32 {
819 if (area == 0) { return 0; }
820 var quot: i64 = num / area;
821 quot = quot + dith;
822 if (quot < 0) { quot = 0; }
823 if (quot > 255) { quot = 255; }
824 return quot as u32;
825}
826
827pub fn gpuDivClamp(num: i64, den: i64, maxOut: u32) u32 {
828 if (den == 0) { return 0; }
829 var q: i64 = num / den;
830 if (q < 0) { return 0; }
831 if (q > (maxOut as i64)) { return maxOut; }
832 return q as u32;
833}
834
835pub fn gpuLineCmd(g: *mut[] u32, vram: *mut[] u8) {
836 const flags: u32 = (g[0] >> 24) & 0xFF;
837 const isGour: bool = (flags & 0x10) != 0;
838 const isPoly: bool = (flags & 0x08) != 0;
839 if (g[19] == GP_RECV_CMD) {
840 g[19] = GP_RECV_ARGS;
841 if (isPoly) {
842 g[17] = 2; return;
843 }
844 var args: u32 = 2;
845 if (isGour) {
846 args = args + 1;
847 }
848 g[17] = args;
849 return;
850 }
851 if (isPoly) {
852 // A poly-line ends when the most-recently received word masks to
853 // 0x50005000, and it draws NOTHING (the poly-line render path is
854 // not implemented). Bound the FIFO write index so we don't
855 // overflow the 16-word buffer.
856 const last: u32 = g[g[16] - 1];
857 if ((last & 0xF000F000) == 0x50005000) {
858 g[19] = GP_RECV_CMD;
859 return;
860 }
861 g[16] = 1;
862 return;
863 }
864 if (g[17] != 0) { return; }
865 var v0xy: u32 = 0;
866 var v1xy: u32 = 0;
867 if (isGour) {
868 v0xy = g[1];
869 v1xy = g[3];
870 } else {
871 v0xy = g[1];
872 v1xy = g[2];
873 }
874 const color: u32 = gpuToBgr555(g[0] & 0xFFFFFF);
875 const x0: u32 = gpuVertX(v0xy) + g[52];
876 const y0: u32 = gpuVertY(v0xy) + g[53];
877 const x1: u32 = gpuVertX(v1xy) + g[52];
878 const y1: u32 = gpuVertY(v1xy) + g[53];
879 gpuPlotLine(g, vram, x0, y0, x1, y1, color);
880 g[19] = GP_RECV_CMD;
881}
882
883// Bresenham line rasterizer.
884pub fn gpuPlotLine(g: *mut[] u32, vram: *mut[] u8,
885 x0: u32, y0: u32, x1: u32, y1: u32, color: u32) {
886 const sx0: i64 = sext32(x0);
887 const sy0: i64 = sext32(y0);
888 const sx1: i64 = sext32(x1);
889 const sy1: i64 = sext32(y1);
890 var adx: i64 = sx1 - sx0;
891 var ady: i64 = sy1 - sy0;
892 if (adx < 0) { adx = -adx; }
893 if (ady < 0) { ady = -ady; }
894 if (ady < adx) {
895 if (sx0 > sx1) { gpuPlotLineLow(g, vram, x1, y1, x0, y0, color); }
896 else { gpuPlotLineLow(g, vram, x0, y0, x1, y1, color); }
897 } else {
898 if (sy0 > sy1) { gpuPlotLineHigh(g, vram, x1, y1, x0, y0, color); }
899 else { gpuPlotLineHigh(g, vram, x0, y0, x1, y1, color); }
900 }
901}
902
903// Shallow-slope: x increments each step, y on Bresenham crossings.
904pub fn gpuPlotLineLow(g: *mut[] u32, vram: *mut[] u8,
905 x0: u32, y0: u32, x1: u32, y1: u32, color: u32) {
906 const sx0: i64 = sext32(x0);
907 const sy0: i64 = sext32(y0);
908 const sx1: i64 = sext32(x1);
909 const sy1: i64 = sext32(y1);
910 const dx: i64 = sx1 - sx0;
911 var dy: i64 = sy1 - sy0;
912 var yi: i64 = 1;
913 if (dy < 0) { yi = -1; dy = -dy; }
914 var d: i64 = (2 * dy) - dx;
915 var y: i64 = sy0;
916 var x: i64 = sx0;
917 while (x < sx1) {
918 gpuPlotPixel(g, vram, x, y, color);
919 if (d > 0) {
920 y = y + yi;
921 d = d + 2 * (dy - dx);
922 } else {
923 d = d + 2 * dy;
924 }
925 x = x + 1;
926 }
927}
928
929// Steep-slope: y increments each step, x on Bresenham crossings.
930pub fn gpuPlotLineHigh(g: *mut[] u32, vram: *mut[] u8,
931 x0: u32, y0: u32, x1: u32, y1: u32, color: u32) {
932 const sx0: i64 = sext32(x0);
933 const sy0: i64 = sext32(y0);
934 const sx1: i64 = sext32(x1);
935 const sy1: i64 = sext32(y1);
936 var dx: i64 = sx1 - sx0;
937 const dy: i64 = sy1 - sy0;
938 var xi: i64 = 1;
939 if (dx < 0) { xi = -1; dx = -dx; }
940 var d: i64 = (2 * dx) - dy;
941 var x: i64 = sx0;
942 var y: i64 = sy0;
943 while (y < sy1) {
944 gpuPlotPixel(g, vram, x, y, color);
945 if (d > 0) {
946 x = x + xi;
947 d = d + 2 * (dx - dy);
948 } else {
949 d = d + 2 * dx;
950 }
951 y = y + 1;
952 }
953}
954
955// Bounds-checked + drawing-area-clipped pixel write.
956pub fn gpuPlotPixel(g: *mut[] u32, vram: *mut[] u8,
957 x: i64, y: i64, color: u32) {
958 const dx1: i64 = sext32(g[48]);
959 const dy1: i64 = sext32(g[49]);
960 const dx2: i64 = sext32(g[50]);
961 const dy2: i64 = sext32(g[51]);
962 if (x < dx1 || x > dx2 || y < dy1 || y > dy2) { return; }
963 if (x < 0 || x >= 1024 || y < 0 || y >= 512) { return; }
964 vramWritePixel(vram, (x as u32) & 0x3FF, (y as u32) & 0x1FF, color);
965}
966
967pub fn gpuRectCmd(g: *mut[] u32, vram: *mut[] u8) {
968 if (g[19] == GP_RECV_CMD) {
969 const flags: u32 = (g[0] >> 24) & 0xFF;
970 const sizeBits: u32 = (flags >> 3) & 3;
971 const isTex: bool = (flags & 0x04) != 0;
972 var args: u32 = 1;
973 if (sizeBits == 0) { args = args + 1; }
974 if (isTex) { args = args + 1; }
975 g[17] = args;
976 g[19] = GP_RECV_ARGS;
977 return;
978 }
979 if (g[17] != 0) { return; }
980 const flags: u32 = (g[0] >> 24) & 0xFF;
981 const sizeBits: u32 = (flags >> 3) & 3;
982 const isTex: bool = (flags & 0x04) != 0;
983 const isRaw: bool = (flags & 0x01) != 0;
984 const isTransp: bool = (flags & 0x02) != 0;
985 const transpMode: u32 = (g[46] >> 5) & 3;
986 var bufIdx: u32 = 1;
987 const xy: u32 = g[bufIdx];
988 bufIdx = bufIdx + 1;
989 var uvword: u32 = 0;
990 if (isTex) {
991 uvword = g[bufIdx];
992 bufIdx = bufIdx + 1;
993 }
994 var w: u32 = 1;
995 var h: u32 = 1;
996 match (sizeBits) {
997 1 { w = 1; h = 1; }
998 2 { w = 8; h = 8; }
999 3 { w = 16; h = 16; }
1000 0 {
1001 const sz: u32 = g[bufIdx];
1002 w = sz & 0xFFFF;
1003 h = (sz >> 16) & 0xFFFF;
1004 }
1005 _ {}
1006 }
1007 var x: u32 = gpuSe11((xy & 0xFFFF) + g[52]);
1008 var y: u32 = gpuSe11(((xy >> 16) & 0xFFFF) + g[53]);
1009
1010 const dx1: i64 = sext32(g[48]);
1011 const dy1: i64 = sext32(g[49]);
1012 const dx2: i64 = sext32(g[50]);
1013 const dy2: i64 = sext32(g[51]);
1014 const sx: i64 = sext32(x);
1015 const sy: i64 = sext32(y);
1016
1017 if (!isTex) {
1018 const color: u32 = gpuToBgr555(g[0] & 0xFFFFFF);
1019 var iy: u32 = 0;
1020 while (iy < h) {
1021 var ix: u32 = 0;
1022 while (ix < w) {
1023 const px: i64 = sx + (ix as i64);
1024 const py: i64 = sy + (iy as i64);
1025 if (px >= dx1 && px <= dx2 && py >= dy1 && py <= dy2 &&
1026 px >= 0 && px < 1024 && py >= 0 && py < 512) {
1027 var outColor: u32 = color;
1028 if (isTransp) {
1029 const back: u32 = vramReadPixel(vram,
1030 (px as u32) & 0x3FF, (py as u32) & 0x1FF);
1031 outColor = gpuBlend(color, back, transpMode);
1032 }
1033 vramWritePixel(vram, (px as u32) & 0x3FF,
1034 (py as u32) & 0x1FF, outColor);
1035 }
1036 ix = ix + 1;
1037 }
1038 iy = iy + 1;
1039 }
1040 } else {
1041 const uu0: u32 = uvword & 0xFF;
1042 const vv0: u32 = (uvword >> 8) & 0xFF;
1043 const clut: u32 = (uvword >> 16) & 0xFFFF;
1044 const clutx: u32 = (clut & 0x3F) << 4;
1045 const cluty: u32 = (clut >> 6) & 0x1FF;
1046 var iy: u32 = 0;
1047 while (iy < h) {
1048 var ix: u32 = 0;
1049 while (ix < w) {
1050 const px: i64 = sx + (ix as i64);
1051 const py: i64 = sy + (iy as i64);
1052 if (px >= dx1 && px <= dx2 && py >= dy1 && py <= dy2 &&
1053 px >= 0 && px < 1024 && py >= 0 && py < 512) {
1054 const tx: u32 = gpuTexWinX(g, (uu0 + ix) & 0xFF);
1055 const ty: u32 = gpuTexWinY(g, (vv0 + iy) & 0xFF);
1056 const texel: u32 = gpuFetchTexel(vram, tx, ty, g[60], g[61],
1057 clutx, cluty, g[62]);
1058 if (texel != 0) {
1059 var color: u32 = texel;
1060 if (!isRaw) {
1061 color = gpuModulate(texel, g[0] & 0xFFFFFF);
1062 }
1063 var thisTransp: bool = false;
1064 if (isTransp) {
1065 thisTransp = (texel & 0x8000) != 0;
1066 }
1067 if (thisTransp) {
1068 const back: u32 = vramReadPixel(vram,
1069 (px as u32) & 0x3FF, (py as u32) & 0x1FF);
1070 color = gpuBlend(color, back, transpMode);
1071 }
1072 vramWritePixel(vram, (px as u32) & 0x3FF,
1073 (py as u32) & 0x1FF, color);
1074 }
1075 }
1076 ix = ix + 1;
1077 }
1078 iy = iy + 1;
1079 }
1080 }
1081 g[19] = GP_RECV_CMD;
1082}
1083
1084pub fn gpuTexWinX(g: *mut[] u32, t: u32) u32 {
1085 return ((t & (~g[54])) | (g[56] & g[54])) & 0xFF;
1086}
1087
1088pub fn gpuTexWinY(g: *mut[] u32, t: u32) u32 {
1089 return ((t & (~g[55])) | (g[57] & g[55])) & 0xFF;
1090}
1091
1092pub fn gpuFetchTexel(vram: *mut[] u8, tx: u32, ty: u32,
1093 tpx: u32, tpy: u32,
1094 clutx: u32, cluty: u32, depth: u32) u32 {
1095 if (depth == 0) {
1096 // 4 texels per VRAM pixel.
1097 const texel: u32 = vramReadPixel(vram, tpx + (tx >> 2), tpy + ty);
1098 const idx: u32 = (texel >> ((tx & 3) * 4)) & 0xF;
1099 return vramReadPixel(vram, clutx + idx, cluty);
1100 }
1101 if (depth == 1) {
1102 // 2 texels per VRAM pixel.
1103 const texel: u32 = vramReadPixel(vram, tpx + (tx >> 1), tpy + ty);
1104 const idx: u32 = (texel >> ((tx & 1) * 8)) & 0xFF;
1105 return vramReadPixel(vram, clutx + idx, cluty);
1106 }
1107 // 15bpp direct.
1108 return vramReadPixel(vram, tpx + tx, tpy + ty);
1109}
1110
1111pub fn gpuModulate(texel: u32, mod: u32) u32 {
1112 const tr: u32 = (texel & 0x1F) << 3;
1113 const tg: u32 = ((texel >> 5) & 0x1F) << 3;
1114 const tb: u32 = ((texel >> 10) & 0x1F) << 3;
1115 const mr: u32 = mod & 0xFF;
1116 const mg: u32 = (mod >> 8) & 0xFF;
1117 const mb: u32 = (mod >> 16) & 0xFF;
1118 var cr: u32 = ((tr * mr) + 0x40) >> 7;
1119 var cg: u32 = ((tg * mg) + 0x40) >> 7;
1120 var cb: u32 = ((tb * mb) + 0x40) >> 7;
1121 if (cr > 255) { cr = 255; }
1122 if (cg > 255) { cg = 255; }
1123 if (cb > 255) { cb = 255; }
1124 return gpuToBgr555(cr | (cg << 8) | (cb << 16));
1125}
1126
1127// 0 B/2 + F/2
1128// 1 B + F
1129// 2 B - F
1130// 3 B + F/4
1131pub fn gpuBlend(fore: u32, back: u32, mode: u32) u32 {
1132 const fr: u32 = (fore & 0x1F) << 3;
1133 const fg: u32 = ((fore >> 5) & 0x1F) << 3;
1134 const fb: u32 = ((fore >> 10) & 0x1F) << 3;
1135 const br: u32 = (back & 0x1F) << 3;
1136 const bg: u32 = ((back >> 5) & 0x1F) << 3;
1137 const bb: u32 = ((back >> 10) & 0x1F) << 3;
1138 var cr: u32 = 0;
1139 var cg: u32 = 0;
1140 var cb: u32 = 0;
1141 if (mode == 0) {
1142 cr = (br + fr) >> 1;
1143 cg = (bg + fg) >> 1;
1144 cb = (bb + fb) >> 1;
1145 } else if (mode == 1) {
1146 cr = br + fr;
1147 cg = bg + fg;
1148 cb = bb + fb;
1149 } else if (mode == 2) {
1150 // The blend is `br - fr`; we'd underflow in u32, so clamp at 0.
1151 if (br > fr) { cr = br - fr; } else { cr = 0; }
1152 if (bg > fg) { cg = bg - fg; } else { cg = 0; }
1153 if (bb > fb) { cb = bb - fb; } else { cb = 0; }
1154 } else {
1155 cr = br + (fr >> 2);
1156 cg = bg + (fg >> 2);
1157 cb = bb + (fb >> 2);
1158 }
1159 if (cr > 255) { cr = 255; }
1160 if (cg > 255) { cg = 255; }
1161 if (cb > 255) { cb = 255; }
1162 return gpuToBgr555(cr | (cg << 8) | (cb << 16));
1163}
1164
1165pub const Gpu = struct {
1166 buf: [80]u32,
1167
1168 pub fn init() Self {
1169 var s: Self = Self { buf: [0; 80] };
1170 s.buf[19] = GP_RECV_CMD;
1171 s.buf[46] = 0x00800000;
1172 s.buf[47] = 1;
1173 return s;
1174 }
1175
1176 pub fn familyCount(self: Self, family: u32) u32 {
1177 return gpuFamilyCount(self.buf.asMutPtr(), family);
1178 }
1179
1180 pub fn read8(self: Self, vram: *mut[] u8, off: u32) u32 {
1181 return gpuRead8(self.buf.asMutPtr(), vram, off);
1182 }
1183
1184 pub fn read16(self: Self, vram: *mut[] u8, off: u32) u32 {
1185 return gpuRead16(self.buf.asMutPtr(), vram, off);
1186 }
1187
1188 pub fn read32(self: Self, vram: *mut[] u8, off: u32) u32 {
1189 return gpuRead32(self.buf.asMutPtr(), vram, off);
1190 }
1191
1192 pub fn write8(self: Self, vram: *mut[] u8, off: u32, val: u32) {
1193 gpuWrite8(self.buf.asMutPtr(), vram, off, val);
1194 }
1195
1196 pub fn write16(self: Self, vram: *mut[] u8, off: u32, val: u32) {
1197 gpuWrite16(self.buf.asMutPtr(), vram, off, val);
1198 }
1199
1200 pub fn write32(self: Self, vram: *mut[] u8, off: u32, val: u32) {
1201 gpuWrite32(self.buf.asMutPtr(), vram, off, val);
1202 }
1203
1204 pub fn gp0(self: Self, vram: *mut[] u8, val: u32) {
1205 gpuGp0(self.buf.asMutPtr(), vram, val);
1206 }
1207
1208 pub fn bufPtr(self: Self) *mut[] u32 {
1209 return self.buf.asMutPtr();
1210 }
1211};