fix: DMA ch2 GPU→RAM now reads GPUREAD instead of writing zeros

The VRAM-to-RAM DMA path (channel 2, direction 0) was stubbed to fill
the destination buffer with zeros. This broke all offscreen VRAM
readback — the BIOS shell draws spiral/splash textures to an offscreen
VRAM region, then DMA-transfers the rendered pixels back to RAM for
later use as texture atlas entries. With the stub, all texture buffers
stayed zero, producing invisible button backgrounds.

Fix: call gpu_read_gpuread() for each DMA word instead of writing 0.

Also includes: CDROM GetID two-phase response fix, GPU raw texture
mode fix (opcode bit 0 instead of color==0x808080), debug server
A0/C0 upload history commands with CPU context capture, and extended
write trace ranges.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-04-24 14:30:57 -07:00
parent c4398aa35a
commit 4054dc1c08
5 changed files with 245 additions and 35 deletions
+5
View File
@@ -133,6 +133,11 @@ int debug_server_get_input_override(void);
/* Set the CPU state pointer for register queries. Call after CPU init. */
void debug_server_set_cpu(CPUState *cpu);
/* Global CPU pointer — set by debug_server_set_cpu(). Available for
* hardware simulation modules that need to read CPU state (e.g. GPU
* capturing stack values during A0 uploads). */
extern CPUState *debug_cpu_ptr;
void debug_server_send_line(const char *json);
void debug_server_send_fmt(const char *fmt, ...);
+10 -13
View File
@@ -223,19 +223,15 @@ static void exec_command(uint8_t cmd) {
break;
case 0x1A: /* GetID */
if (!has_disc()) {
response_push(stat_reg | CDSTAT_IDERROR);
response_push(0x80); /* NOT_READY */
set_irq(CDIRQ_ERROR);
fire_cdrom_irq();
} else {
response_push(stat_reg);
set_irq(CDIRQ_ACK);
pending.cmd = 0x1A;
pending.pending = 1;
pending.delay = 30000;
pending.phase = 1;
}
/* GetID always sends INT3 (ACK) first, then a pending
* second response: INT2 (COMPLETE) with disc ID if present,
* or INT5 (ERROR) if no disc / lid open. */
response_push(stat_reg);
set_irq(CDIRQ_ACK);
pending.cmd = 0x1A;
pending.pending = 1;
pending.delay = 30000;
pending.phase = 1;
break;
case 0x1B: /* ReadS */
@@ -356,6 +352,7 @@ static void process_pending(void) {
case 0x1E: /* ReadTOC complete */
response_push(stat_reg);
set_irq(CDIRQ_COMPLETE);
fire_cdrom_irq();
break;
default:
+74 -2
View File
@@ -449,6 +449,69 @@ static void handle_irq_state(int id, const char *json)
/* GPU opcode counter — defined in gpu.c */
extern uint32_t gpu_get_opcode_count(uint8_t op);
extern int gpu_get_a0_count(void);
extern int gpu_get_a0_history(int index, int *x, int *y, int *w, int *h,
uint32_t *fw0, uint32_t *fw1, int *wcount);
extern int gpu_get_a0_extra(int index, uint32_t *func, uint32_t *sp, uint32_t *ra,
uint32_t *s1, uint32_t *stack10);
static void handle_a0_history(int id, const char *json)
{
(void)json;
int count = gpu_get_a0_count();
/* Use dynamic allocation for large output */
int bufsz = 65536;
char *buf = (char*)malloc(bufsz);
if (!buf) { send_fmt("{\"id\":%d,\"ok\":false,\"error\":\"OOM\"}", id); return; }
int pos = snprintf(buf, bufsz, "{\"id\":%d,\"ok\":true,\"count\":%d,\"uploads\":[", id, count);
for (int i = 0; i < count && pos < bufsz - 500; i++) {
int x, y, w, h, wcount;
uint32_t fw0, fw1, func, sp, ra, s1, stk[10];
gpu_get_a0_history(i, &x, &y, &w, &h, &fw0, &fw1, &wcount);
gpu_get_a0_extra(i, &func, &sp, &ra, &s1, stk);
pos += snprintf(buf + pos, bufsz - pos,
"%s{\"i\":%d,\"x\":%d,\"y\":%d,\"w\":%d,\"h\":%d,"
"\"fw0\":\"0x%08X\",\"fw1\":\"0x%08X\",\"words\":%d,"
"\"func\":\"0x%08X\",\"sp\":\"0x%08X\",\"ra\":\"0x%08X\","
"\"s1\":\"0x%08X\","
"\"stk\":[\"0x%08X\",\"0x%08X\",\"0x%08X\",\"0x%08X\","
"\"0x%08X\",\"0x%08X\",\"0x%08X\",\"0x%08X\","
"\"0x%08X\",\"0x%08X\"]}",
i ? "," : "", i, x, y, w, h, fw0, fw1, wcount,
func, sp, ra, s1,
stk[0], stk[1], stk[2], stk[3], stk[4], stk[5], stk[6], stk[7],
stk[8], stk[9]);
}
pos += snprintf(buf + pos, bufsz - pos, "]}");
send_fmt("%s", buf);
free(buf);
}
extern int gpu_get_c0_count(void);
extern int gpu_get_c0_history(int index, int *x, int *y, int *w, int *h,
uint32_t *func, uint32_t *sp, uint32_t *s1,
uint32_t *fw0, uint32_t *fw1, int *rcount);
static void handle_c0_history(int id, const char *json)
{
(void)json;
int count = gpu_get_c0_count();
char buf[8192];
int pos = snprintf(buf, sizeof(buf), "{\"id\":%d,\"ok\":true,\"count\":%d,\"reads\":[", id, count);
for (int i = 0; i < count && pos < (int)sizeof(buf) - 300; i++) {
int x, y, w, h, rcount;
uint32_t func, sp, s1, fw0, fw1;
gpu_get_c0_history(i, &x, &y, &w, &h, &func, &sp, &s1, &fw0, &fw1, &rcount);
pos += snprintf(buf + pos, sizeof(buf) - pos,
"%s{\"i\":%d,\"x\":%d,\"y\":%d,\"w\":%d,\"h\":%d,"
"\"func\":\"0x%08X\",\"sp\":\"0x%08X\",\"s1\":\"0x%08X\","
"\"fw0\":\"0x%08X\",\"fw1\":\"0x%08X\",\"reads\":%d}",
i ? "," : "", i, x, y, w, h, func, sp, s1, fw0, fw1, rcount);
}
pos += snprintf(buf + pos, sizeof(buf) - pos, "]}");
send_fmt("%s", buf);
}
static void handle_gpu_opcodes(int id, const char *json)
{
(void)json;
@@ -1328,6 +1391,8 @@ static const CmdEntry s_commands[] = {
{ "screenshot", handle_screenshot },
{ "screenshot_file", handle_screenshot_file },
{ "gpu_opcodes", handle_gpu_opcodes },
{ "a0_history", handle_a0_history },
{ "c0_history", handle_c0_history },
{ "capture_quads", handle_capture_quads },
{ "get_quads", handle_get_quads },
{ "gte_state", handle_gte_state },
@@ -1414,12 +1479,19 @@ void debug_server_init(int port)
s_wtrace_seq = 0;
s_wtrace_head = 0;
/* Phase 4.5: watch EB4 area + DF8/DFC area. */
/* Phase 4.5: watch EB4 area + DF8/DFC area + EvCB slot 1 status + state machine
* + spiral texture buffer. */
s_wtrace_ranges[0].lo = 0x00079EB0u;
s_wtrace_ranges[0].hi = 0x00079EC0u;
s_wtrace_ranges[1].lo = 0x00079DF0u;
s_wtrace_ranges[1].hi = 0x00079E04u;
s_wtrace_range_count = 2;
s_wtrace_ranges[2].lo = 0x0000E044u; /* EvCB slot 1 (class+status+spec) */
s_wtrace_ranges[2].hi = 0x0000E054u;
s_wtrace_ranges[3].lo = 0x00066940u; /* shell state machine 0x80066940 */
s_wtrace_ranges[3].hi = 0x00066954u;
s_wtrace_ranges[4].lo = 0x001B6810u; /* spiral texture buffer (heap @ 0x801B6814) */
s_wtrace_ranges[4].hi = 0x001B6830u;
s_wtrace_range_count = 5;
/* Tier 1: heap-allocate MMIO trace ring buffer (2 MB). */
if (!s_mmio_trace) {
+4 -6
View File
@@ -80,11 +80,8 @@ static void execute_ch2_gpu(void) {
uint32_t sync_mode = (chcr >> 9) & 3; /* 0=burst, 1=block, 2=linked-list */
if (direction == 0) {
/* GPU → RAM (VRAM read) — not fully implemented.
* Fill target RAM with zeros and complete the transfer so the
* DMA interrupt fires and the handler chain can process it.
* The BIOS uses this during display setup; the actual VRAM
* contents aren't critical for boot progression. */
/* GPU → RAM (VRAM read): read pixel data via GPUREAD.
* A prior GP0(C0h) command must have set up the VRAM read region. */
if (sync_mode == 1) {
uint32_t block_size = channels[2].bcr & 0xFFFF;
uint32_t block_count = (channels[2].bcr >> 16) & 0xFFFF;
@@ -92,7 +89,8 @@ static void execute_ch2_gpu(void) {
uint32_t addr = channels[2].madr & 0x1FFFFCu;
int32_t addr_step = step ? -4 : 4;
for (uint32_t i = 0; i < total_words; i++) {
psx_write_word(addr, 0);
uint32_t pixel_data = gpu_read_gpuread();
psx_write_word(addr, pixel_data);
addr = (addr + addr_step) & 0x1FFFFCu;
}
}
+152 -14
View File
@@ -12,6 +12,8 @@
#include "gpu.h"
#include "gpu_sw_renderer.h"
#include "debug_server.h"
#include "cpu_state.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -107,6 +109,17 @@ static uint32_t v_display_y2;
/* GPUREAD latch (GP1(10h) get-info result, or VRAM read data) */
static uint32_t gpuread_latch;
/* C0 (VRAM→CPU) capture slot — forward declaration for gpu_read_gpuread */
#define C0_HISTORY_CAP_FWD 32
static struct C0HistEntry {
uint16_t x, y, w, h;
uint32_t func_addr, sp_val, s1_val;
uint32_t first_words[4];
int read_count;
} c0_history_fwd[C0_HISTORY_CAP_FWD];
static int c0_history_count_fwd = 0;
static int c0_capture_slot_fwd = -1;
/* Vblank presentation callback */
static gpu_vblank_cb vblank_callback;
@@ -275,6 +288,15 @@ uint32_t gpu_read_gpuread(void) {
}
}
/* Capture first words for C0 debug */
if (c0_capture_slot_fwd >= 0 && c0_capture_slot_fwd < C0_HISTORY_CAP_FWD) {
int rc = c0_history_fwd[c0_capture_slot_fwd].read_count++;
if (rc < 4)
c0_history_fwd[c0_capture_slot_fwd].first_words[rc] = value;
if (!vram_read_active)
c0_capture_slot_fwd = -1; /* transfer complete */
}
gpuread_latch = value;
return value;
}
@@ -536,13 +558,13 @@ static uint16_t current_texpage(void) {
}
/* Helper: set up SW renderer state before a textured draw.
* semi_trans: whether the primitive has semi-transparency enabled (from opcode bit) */
static void setup_textured_draw(uint32_t color24, int semi_trans) {
* semi_trans: whether the primitive has semi-transparency enabled (from opcode bit)
* raw_texture: 1 if this is a raw-texture opcode (bit 0 of GP0 opcode set) */
static void setup_textured_draw(uint32_t color24, int semi_trans, int raw_texture) {
uint32_t r = (color24 >> 0) & 0xFF;
uint32_t g = (color24 >> 8) & 0xFF;
uint32_t b = (color24 >> 16) & 0xFF;
int raw = (r == 0x80 && g == 0x80 && b == 0x80) ? 1 : 0;
sw_set_color_modulation((int)r, (int)g, (int)b, raw);
sw_set_color_modulation((int)r, (int)g, (int)b, raw_texture);
sw_set_semi_transparency(semi_trans, (int)semi_transparency);
}
@@ -550,6 +572,7 @@ static void setup_textured_draw(uint32_t color24, int semi_trans) {
static void gp0_exec_textured_tri(void) {
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
int32_t vx[3], vy[3];
int u[3], v[3];
/* Layout: color+v0, texcoord0+clut, v1, texcoord1+tpage, v2, texcoord2 */
@@ -571,7 +594,7 @@ static void gp0_exec_textured_tri(void) {
vy[i] += draw_offset_y;
}
setup_textured_draw(color24, semi_trans);
setup_textured_draw(color24, semi_trans, raw_texture);
sw_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
@@ -582,6 +605,7 @@ static void gp0_exec_textured_tri(void) {
static void gp0_exec_textured_quad(void) {
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
int32_t vx[4], vy[4];
int u[4], v[4];
/* Layout: color+v0, tc0+clut, v1, tc1+tpage, v2, tc2, v3, tc3 */
@@ -603,7 +627,7 @@ static void gp0_exec_textured_quad(void) {
vy[i] += draw_offset_y;
}
setup_textured_draw(color24, semi_trans);
setup_textured_draw(color24, semi_trans, raw_texture);
sw_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
@@ -618,6 +642,7 @@ static void gp0_exec_textured_quad(void) {
static void gp0_exec_shaded_textured_tri(void) {
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
int32_t vx[3], vy[3];
int u[3], v[3];
/* Layout: C0, V0, TC0+clut, C1, V1, TC1+tpage, C2, V2, TC2 */
@@ -637,7 +662,7 @@ static void gp0_exec_shaded_textured_tri(void) {
vy[i] += draw_offset_y;
}
setup_textured_draw(color24, semi_trans);
setup_textured_draw(color24, semi_trans, raw_texture);
sw_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
@@ -648,6 +673,7 @@ static void gp0_exec_shaded_textured_tri(void) {
static void gp0_exec_shaded_textured_quad(void) {
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
int32_t vx[4], vy[4];
int u[4], v[4];
/* Layout: C0, V0, TC0+clut, C1, V1, TC1+tpage, C2, V2, TC2, C3, V3, TC3 */
@@ -669,7 +695,7 @@ static void gp0_exec_shaded_textured_quad(void) {
vy[i] += draw_offset_y;
}
setup_textured_draw(color24, semi_trans);
setup_textured_draw(color24, semi_trans, raw_texture);
sw_draw_textured_triangle(vx[0], vy[0], u[0], v[0],
vx[1], vy[1], u[1], v[1],
vx[2], vy[2], u[2], v[2],
@@ -726,6 +752,7 @@ static void gp0_exec_mono_rect(void) {
static void gp0_exec_textured_rect(void) {
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
int32_t x0, y0;
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
x0 += draw_offset_x; y0 += draw_offset_y;
@@ -739,7 +766,7 @@ static void gp0_exec_textured_rect(void) {
if (w > 1023) w = 1023;
if (h > 511) h = 511;
setup_textured_draw(color24, semi_trans);
setup_textured_draw(color24, semi_trans, raw_texture);
sw_draw_textured_rect(x0, y0, w, h, u0, v0, clut_x, clut_y, current_texpage());
}
@@ -758,6 +785,7 @@ static void gp0_exec_mono_dot(void) {
static void gp0_exec_textured_8x8(void) {
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
int32_t x0, y0;
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
x0 += draw_offset_x; y0 += draw_offset_y;
@@ -767,7 +795,7 @@ static void gp0_exec_textured_8x8(void) {
uint16_t clut_x = (clut & 0x3F) * 16;
uint16_t clut_y = (clut >> 6) & 0x1FF;
setup_textured_draw(color24, semi_trans);
setup_textured_draw(color24, semi_trans, raw_texture);
sw_draw_textured_rect(x0, y0, 8, 8, u0, v0, clut_x, clut_y, current_texpage());
}
@@ -786,6 +814,7 @@ static void gp0_exec_mono_8x8(void) {
static void gp0_exec_textured_16x16(void) {
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
int32_t x0, y0;
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
x0 += draw_offset_x; y0 += draw_offset_y;
@@ -795,7 +824,7 @@ static void gp0_exec_textured_16x16(void) {
uint16_t clut_x = (clut & 0x3F) * 16;
uint16_t clut_y = (clut >> 6) & 0x1FF;
setup_textured_draw(color24, semi_trans);
setup_textured_draw(color24, semi_trans, raw_texture);
sw_draw_textured_rect(x0, y0, 16, 16, u0, v0, clut_x, clut_y, current_texpage());
}
@@ -900,6 +929,46 @@ static void gp0_exec_mask_bit(void) {
sw_set_mask_bits((int)set_mask_bit, (int)check_mask_bit);
}
/* A0 upload history for debug inspection */
#define A0_HISTORY_CAP 128
typedef struct {
uint16_t x, y, w, h;
uint32_t first_words[4];
int word_count;
uint32_t func_addr;
uint32_t sp_val; /* CPU $sp at time of A0 header */
uint32_t ra_val; /* CPU $ra register at time of A0 header */
uint32_t s1_val; /* CPU $s1 = source data pointer in func_1FC38524 */
uint32_t stack[10]; /* first 10 words from sp (sp+32=saved $s1, sp+36=saved $ra) */
} A0HistEntry;
static A0HistEntry a0_history[A0_HISTORY_CAP];
static int a0_history_count = 0;
static int a0_capture_slot = -1; /* currently capturing data words for this slot */
extern uint32_t psx_read_word(uint32_t addr);
int gpu_get_a0_history(int index, int *x, int *y, int *w, int *h,
uint32_t *fw0, uint32_t *fw1, int *wcount) {
if (index < 0 || index >= a0_history_count) return 0;
*x = a0_history[index].x; *y = a0_history[index].y;
*w = a0_history[index].w; *h = a0_history[index].h;
*fw0 = a0_history[index].first_words[0];
*fw1 = a0_history[index].first_words[1];
*wcount = a0_history[index].word_count;
return 1;
}
int gpu_get_a0_count(void) { return a0_history_count; }
int gpu_get_a0_extra(int index, uint32_t *func, uint32_t *sp, uint32_t *ra,
uint32_t *s1, uint32_t *stack10) {
if (index < 0 || index >= a0_history_count) return 0;
*func = a0_history[index].func_addr;
*sp = a0_history[index].sp_val;
*ra = a0_history[index].ra_val;
*s1 = a0_history[index].s1_val;
memcpy(stack10, a0_history[index].stack, 10 * sizeof(uint32_t));
return 1;
}
static void gp0_exec_cpu_to_vram(void) {
/* 0xA0 — CPU→VRAM Copy (header: 3 words)
* Word 0: command
@@ -915,6 +984,34 @@ static void gp0_exec_cpu_to_vram(void) {
vram_write_w = (w == 0) ? 0x400 : (uint16_t)w;
vram_write_h = (h == 0) ? 0x200 : (uint16_t)h;
/* Record for debug */
if (a0_history_count < A0_HISTORY_CAP) {
int slot = a0_history_count++;
a0_history[slot].x = vram_write_x;
a0_history[slot].y = vram_write_y;
a0_history[slot].w = vram_write_w;
a0_history[slot].h = vram_write_h;
a0_history[slot].word_count = 0;
a0_history[slot].func_addr = g_debug_current_func_addr;
memset(a0_history[slot].first_words, 0, sizeof(a0_history[slot].first_words));
/* Capture CPU context for caller tracing.
* func_1FC38524 (shell LoadImage) uses $s1 as source data pointer. */
a0_history[slot].sp_val = 0;
a0_history[slot].ra_val = 0;
a0_history[slot].s1_val = 0;
memset(a0_history[slot].stack, 0, sizeof(a0_history[slot].stack));
if (debug_cpu_ptr) {
uint32_t sp = debug_cpu_ptr->gpr[29];
a0_history[slot].sp_val = sp;
a0_history[slot].ra_val = debug_cpu_ptr->gpr[31];
a0_history[slot].s1_val = debug_cpu_ptr->gpr[17]; /* $s1 */
uint32_t sp_phys = sp & 0x1FFFFFu;
for (int si = 0; si < 10 && sp_phys + (si + 1) * 4 <= 0x200000u; si++)
a0_history[slot].stack[si] = psx_read_word(sp + si * 4);
}
a0_capture_slot = slot;
}
vram_write_col = 0;
vram_write_row = 0;
@@ -925,6 +1022,28 @@ static void gp0_exec_cpu_to_vram(void) {
gp0_state = GP0_VRAM_WRITE;
}
/* C0 (VRAM→CPU) history — uses c0_history_fwd declared at top of file */
#define C0_HISTORY_CAP C0_HISTORY_CAP_FWD
#define c0_history c0_history_fwd
#define c0_history_count c0_history_count_fwd
#define c0_capture_slot c0_capture_slot_fwd
int gpu_get_c0_count(void) { return c0_history_count; }
int gpu_get_c0_history(int index, int *x, int *y, int *w, int *h,
uint32_t *func, uint32_t *sp, uint32_t *s1,
uint32_t *fw0, uint32_t *fw1, int *rcount) {
if (index < 0 || index >= c0_history_count) return 0;
*x = c0_history[index].x; *y = c0_history[index].y;
*w = c0_history[index].w; *h = c0_history[index].h;
*func = c0_history[index].func_addr;
*sp = c0_history[index].sp_val;
*s1 = c0_history[index].s1_val;
*fw0 = c0_history[index].first_words[0];
*fw1 = c0_history[index].first_words[1];
*rcount = c0_history[index].read_count;
return 1;
}
static void gp0_exec_vram_to_cpu(void) {
/* 0xC0 — VRAM→CPU Copy (3 words)
* Word 1: source coords
@@ -938,8 +1057,20 @@ static void gp0_exec_vram_to_cpu(void) {
vram_read_w = (w == 0) ? 0x400 : (uint16_t)w;
vram_read_h = (h == 0) ? 0x200 : (uint16_t)h;
/* DuckStation uses (val-1)&mask+1 for VRAM→CPU, but the effect
* is identical: 0→max. We use ReplaceZero like CPU→VRAM. */
/* Record for debug */
if (c0_history_count < C0_HISTORY_CAP) {
int slot = c0_history_count++;
c0_history[slot].x = vram_read_x;
c0_history[slot].y = vram_read_y;
c0_history[slot].w = vram_read_w;
c0_history[slot].h = vram_read_h;
c0_history[slot].func_addr = g_debug_current_func_addr;
c0_history[slot].sp_val = debug_cpu_ptr ? debug_cpu_ptr->gpr[29] : 0;
c0_history[slot].s1_val = debug_cpu_ptr ? debug_cpu_ptr->gpr[17] : 0;
memset(c0_history[slot].first_words, 0, sizeof(c0_history[slot].first_words));
c0_history[slot].read_count = 0;
c0_capture_slot = slot;
}
vram_read_col = 0;
vram_read_row = 0;
@@ -1136,6 +1267,7 @@ static void gp0_execute_command(void) {
/* 1x1 textured dot: cmd, vertex, texcoord+clut (no size word) */
uint32_t color24 = gp0_cmd_buf[0] & 0xFFFFFFu;
int semi_trans = (gp0_cmd_buf[0] >> 25) & 1;
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
int32_t x0, y0;
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
x0 += draw_offset_x; y0 += draw_offset_y;
@@ -1144,7 +1276,7 @@ static void gp0_execute_command(void) {
uint16_t clut = (uint16_t)(gp0_cmd_buf[2] >> 16);
uint16_t clut_x = (clut & 0x3F) * 16;
uint16_t clut_y = (clut >> 6) & 0x1FF;
setup_textured_draw(color24, semi_trans);
setup_textured_draw(color24, semi_trans, raw_texture);
sw_draw_textured_rect(x0, y0, 1, 1, u0, v0, clut_x, clut_y, current_texpage());
break;
}
@@ -1264,6 +1396,12 @@ void gpu_write_gp0(uint32_t val) {
/* State: consuming pixel data for CPU→VRAM transfer */
if (gp0_state == GP0_VRAM_WRITE) {
/* Capture first few data words for debug */
if (a0_capture_slot >= 0 && a0_capture_slot < A0_HISTORY_CAP) {
int wc = a0_history[a0_capture_slot].word_count++;
if (wc < 4)
a0_history[a0_capture_slot].first_words[wc] = val;
}
/* Each word contains two 16-bit pixels (low halfword first) */
for (int i = 0; i < 2; i++) {
uint16_t pixel = (uint16_t)(val >> (i * 16));