mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
GP1(08) mirrors video mode into GPUSTAT bits 14/16-22; recompute on state load; gp1-mode-mirror gate
This commit is contained in:
@@ -3351,6 +3351,35 @@ void psx_gpu_write32(psx_gpu_t* gpu, uint32_t offset, uint32_t value) {
|
||||
} break;
|
||||
case 0x08:
|
||||
gpu->display_mode = value & 0xffffff;
|
||||
|
||||
/*
|
||||
Mirror the mode into GPUSTAT, per psx-spx:
|
||||
|
||||
GP1(08).0-1 hres1 -> GPUSTAT.17-18
|
||||
GP1(08).2 vres -> GPUSTAT.19
|
||||
GP1(08).3 PAL/NTSC -> GPUSTAT.20
|
||||
GP1(08).4 24bpp -> GPUSTAT.21
|
||||
GP1(08).5 interlace -> GPUSTAT.22
|
||||
GP1(08).6 hres2 -> GPUSTAT.16
|
||||
GP1(08).7 reverse -> GPUSTAT.14
|
||||
|
||||
These were never written, so a game reading its video mode back saw
|
||||
every field as 0 — 256x240, 15bpp, progressive, NTSC — regardless of
|
||||
what it had set. A readback lying about live state, the same defect
|
||||
class as GPUINFO(5) and GTE ORGB. For a 512-wide title (Crash runs
|
||||
GP1(08)=0x02) the readback claimed a 256-wide screen: an engine that
|
||||
sizes its own culling viewport from GPUSTAT drops exactly the outer
|
||||
flank geometry, which is the measured Crash wedge — the game submits
|
||||
no triangles over those pixels while the GPU stream stays well-formed.
|
||||
Confirmed in the device dump header: display_mode=000002 (hres=512)
|
||||
alongside stat=8000001e (mode bits all zero).
|
||||
Gate: gp1-mode-mirror in tests/gpu_renderer_parity.c.
|
||||
*/
|
||||
gpu->gpustat = (gpu->gpustat & ~0x007f4000u)
|
||||
| ((value & 0x3fu) << 17)
|
||||
| ((value & 0x40u) << 10)
|
||||
| ((value & 0x80u) << 7);
|
||||
|
||||
GPU_HW_DEBUG(
|
||||
"gp1-display-mode value=%08x display_mode=0x%08x video_standard=%s",
|
||||
value,
|
||||
@@ -3803,6 +3832,14 @@ int psx_gpu_load_state(psx_gpu_t* gpu, psx_state_reader_t* r) {
|
||||
gpu->gpuread = psx_sr_u32(r);
|
||||
gpu->gpustat = psx_sr_u32(r);
|
||||
|
||||
/* States saved before the GP1(08) -> GPUSTAT mirror existed carry zeros in the mode
|
||||
bits, and a game rarely re-sends GP1(08) after boot — recompute the mirror from the
|
||||
restored display_mode so a loaded state can't reintroduce the lying readback. */
|
||||
gpu->gpustat = (gpu->gpustat & ~0x007f4000u)
|
||||
| ((gpu->display_mode & 0x3fu) << 17)
|
||||
| ((gpu->display_mode & 0x40u) << 10)
|
||||
| ((gpu->display_mode & 0x80u) << 7);
|
||||
|
||||
gpu->draw_x1 = psx_sr_u32(r);
|
||||
gpu->draw_y1 = psx_sr_u32(r);
|
||||
gpu->draw_x2 = psx_sr_u32(r);
|
||||
|
||||
@@ -1395,6 +1395,55 @@ static int gp0_check_pixels(const char* name, const char* what, psx_gpu_t* gpu,
|
||||
set (negative fields) must echo verbatim rather than smearing sign bits over the word.
|
||||
The sweeps below include every such value.
|
||||
*/
|
||||
/*
|
||||
GP1(08) -> GPUSTAT mirror. psx-spx: mode bits 0-5 land in GPUSTAT 17-22, bit 6 (hres2)
|
||||
in 16, bit 7 (reverse) in 14. These were never written at all, so a game reading its
|
||||
video mode back saw 256x240/15bpp/progressive/NTSC forever — for Crash (512-wide,
|
||||
GP1(08)=0x02) the readback claimed a screen half the real width, and an engine that
|
||||
sizes its culling viewport from GPUSTAT drops exactly the outer flank geometry.
|
||||
Exhaustive over all 256 mode bytes, and the texpage/mask bits E1/E6 own must survive.
|
||||
*/
|
||||
static int run_gp1_mode_mirror_case(void) {
|
||||
const char* name = "gp1-mode-mirror";
|
||||
psx_gpu_t* gpu = make_gpu();
|
||||
int failed = 0;
|
||||
|
||||
/* Seed the E1/E6-owned low bits so a clobber is visible. */
|
||||
psx_gpu_write32(gpu, 0, 0xe1000000 | 0x2ff);
|
||||
psx_gpu_write32(gpu, 0, 0xe6000003);
|
||||
|
||||
for (uint32_t m = 0; m < 0x100 && !failed; m++) {
|
||||
psx_gpu_write32(gpu, 4, 0x08000000 | m);
|
||||
|
||||
uint32_t stat = psx_gpu_read32(gpu, 4);
|
||||
uint32_t want = ((m & 0x3fu) << 17) | ((m & 0x40u) << 10) | ((m & 0x80u) << 7);
|
||||
|
||||
if ((stat & 0x007f4000u) != want) {
|
||||
fprintf(stderr,
|
||||
"GPU_PARITY failed case=%s reason=mode-mirror mode=%02x stat=%08x "
|
||||
"mirror=%06x want=%06x\n",
|
||||
name, m, stat, stat & 0x007f4000u, want);
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
/* E1's texpage bits 0-10 and E6's mask bits 11-12 must be untouched. */
|
||||
if ((stat & 0x1fffu) != 0x1aff) {
|
||||
fprintf(stderr,
|
||||
"GPU_PARITY failed case=%s reason=low-bits-clobbered mode=%02x "
|
||||
"stat=%08x low=%04x want=1aff\n",
|
||||
name, m, stat, stat & 0x1fffu);
|
||||
failed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
psx_gpu_destroy(gpu);
|
||||
|
||||
if (!failed)
|
||||
printf("GPU_PARITY passed case=%s\n", name);
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
static int run_gpuinfo_roundtrip_case(void) {
|
||||
const char* name = "gpuinfo-roundtrip";
|
||||
psx_gpu_t* gpu = make_gpu();
|
||||
@@ -2301,6 +2350,10 @@ int main(void) {
|
||||
differential pair share. */
|
||||
failed |= run_gpuinfo_roundtrip_case();
|
||||
|
||||
/* GPUSTAT must tell the game the video mode it actually set — a zeroed mirror told
|
||||
every title it was on a 256-wide screen. */
|
||||
failed |= run_gp1_mode_mirror_case();
|
||||
|
||||
/* Turning accurate_mask_bit on must not make textured content disappear — the setting
|
||||
is inert until a game sends GP0(E6), and a sprite into a clean buffer draws in full
|
||||
under every E6. The mask cases above pin the rules per pixel; this asks whether the
|
||||
|
||||
Reference in New Issue
Block a user