mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
115 lines
5.3 KiB
C
115 lines
5.3 KiB
C
#ifndef PSX_PGXP_H
|
|
#define PSX_PGXP_H
|
|
|
|
/*
|
|
PGXP — precise-vertex pipeline.
|
|
|
|
The GTE computes screen coordinates internally at much higher precision than
|
|
the 11-bit signed integers games read out of the SXY FIFO. This module
|
|
captures the pre-truncation coordinates at RTPS/RTPT retirement, follows the
|
|
truncated words as the game copies them into RAM (SWC2 directly, or
|
|
MFC2 + SW), and re-attaches the precise values when the very same words come
|
|
back through GPU DMA as GP0 polygon vertices. A hardware rasterizer backend
|
|
can then place vertices at sub-pixel positions instead of snapped integers,
|
|
which is what removes the PS1's polygon wobble.
|
|
|
|
Everything here is DERIVED data:
|
|
* it is never serialized into save states (psx/state.c calls
|
|
psx_pgxp_reset() on load instead — precision degrades to plain integers
|
|
for the one frame it takes the GTE to re-transform the scene);
|
|
* a miss anywhere simply leaves the vertex integer-only, which is exactly
|
|
the behaviour with the feature off.
|
|
|
|
Validation rule (load-bearing, do not weaken): a cached precise coordinate
|
|
is only ever attached when the cached low-precision 32-bit word equals the
|
|
word actually being consumed. The game may overwrite a tracked address, or
|
|
hand-modify a vertex after reading it from the GTE; a stale attach produces
|
|
geometry far worse than no PGXP at all.
|
|
|
|
The module is process-global, matching the single-machine reality of every
|
|
frontend in this repo. All entry points are cheap no-ops while disabled;
|
|
hot call sites additionally guard with psx_pgxp_active() so the disabled
|
|
cost is one predictable branch. Default is OFF ([video] pgxp in
|
|
settings.toml) and with it off nothing in the pipeline changes behaviour.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
struct vertex_t;
|
|
|
|
/* Read-only outside psx/pgxp.c. Exposed so per-instruction call sites can gate
|
|
on a single global load instead of a function call. */
|
|
extern int g_psx_pgxp_enabled;
|
|
|
|
#define psx_pgxp_active() (g_psx_pgxp_enabled)
|
|
|
|
/* Lifetime / control. Enabling allocates the address cache (~10 MiB) on first
|
|
use and keeps it across later toggles, so flipping the setting mid-game can
|
|
never race the emulation thread against free(). */
|
|
void psx_pgxp_set_enabled(int enabled);
|
|
int psx_pgxp_enabled(void);
|
|
|
|
/* Drop every derived value: GTE FIFO shadow, CPU register shadow, address
|
|
cache, and the DMA->GP0 address queue. Wired into psx_soft_reset() and
|
|
the save-state load path. */
|
|
void psx_pgxp_reset(void);
|
|
|
|
/* ---- capture (psx/cpu.c, GTE) ----
|
|
Called as an RTPS/RTPT result lands in SXY2 (GTE_RTP/GTE_RTP_DQ retirement).
|
|
`sxy` is the packed (sy << 16) | (sx & 0xffff) register value the game will
|
|
read; fx/fy are the pre-truncation screen coordinates in pixels; fw is the
|
|
depth used for the projection divide (SZ3), for later perspective-correct
|
|
interpolation. Shifts the module's 3-deep FIFO shadow exactly like the
|
|
hardware FIFO shifts. */
|
|
void psx_pgxp_gte_vertex(uint32_t sxy, float fx, float fy, float fw);
|
|
|
|
/* Called on every GTE data-register write (MTC2 / LWC2 / SXYP pushes) so
|
|
direct writes into the SXY FIFO invalidate — and reg-15 pushes shift — the
|
|
shadow. `reg` is the raw 0..63 index gte_write_register() receives. */
|
|
void psx_pgxp_gte_reg_write(uint32_t reg, uint32_t value);
|
|
|
|
/* ---- tracking (psx/cpu.c, memory traffic) ---- */
|
|
|
|
/* SWC2: the GTE register `reg` (0..31 data space) was stored to `addr` with
|
|
contents `value`. SXY sources create/overwrite a cache entry; anything else
|
|
invalidates a stale entry at that address. */
|
|
void psx_pgxp_cpu_swc2(uint32_t addr, uint32_t value, uint32_t reg);
|
|
|
|
/* MFC2: GTE data register `reg` was read into CPU register `rt` (value is what
|
|
will land after the load delay). Keeps a per-CPU-register shadow so a later
|
|
plain SW can carry the precision along. */
|
|
void psx_pgxp_cpu_mfc2(uint32_t rt, uint32_t value, uint32_t reg);
|
|
|
|
/* SW: CPU register `rt` (contents `value`) was stored to `addr`. Attaches the
|
|
register shadow when it still matches, otherwise invalidates the address. */
|
|
void psx_pgxp_cpu_sw(uint32_t addr, uint32_t value, uint32_t rt);
|
|
|
|
/* ---- submission (psx/dev/dma.c -> psx/dev/gpu.c) ----
|
|
dma.c notes the RAM source address immediately before writing each word to
|
|
GP0 (0x1f801810). The bus write lands in psx_gpu_write32() synchronously, so
|
|
a single pending latch is enough: the GPU consumes it into a per-buf-slot
|
|
address table (psx_pgxp_gp0_slot) or discards it (image data words). A GP0
|
|
word arriving with no note pending — every CPU MMIO write — records "no
|
|
address", so a stale DMA address can never attach to the wrong word. */
|
|
void psx_pgxp_note_gp0_word(uint32_t addr);
|
|
void psx_pgxp_gp0_slot(int buf_index);
|
|
void psx_pgxp_gp0_discard(void);
|
|
|
|
/* ---- lookup (psx/dev/gpu.c, vertex parse) ----
|
|
`word` is gpu->buf[buf_index] (the packed y<<16|x vertex word), `buf_index`
|
|
selects the source address noted for that slot. On a validated hit fills
|
|
v->px/py/pw and sets v->precise_valid; otherwise clears precise_valid (and
|
|
gives px/py/pw inert values), so calling it unconditionally also serves as
|
|
the field initializer for downstream consumers. */
|
|
void psx_pgxp_poly_vertex(struct vertex_t* v, uint32_t word, int buf_index);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSX_PGXP_H */
|