GPU packet keys keep the enhancement aperture apart from the main RAM it aliases

The UI prepass re-read aperture node payloads through the RAM-folded key, so
THPS2's relocated prim arena discarded the HUD squash every frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-10-02 19:08:13 -07:00
parent 37946ab43d
commit 4b0e5bd1db
7 changed files with 109 additions and 15 deletions
+17
View File
@@ -49,6 +49,23 @@ static inline uint32_t psx_mod_gpu_dma_resolve_address_for(
return psx_ram_canonical_offset(canonical) & ~3u;
}
/*
* Identity of a GPU packet word at `address` (a CPU pointer or a DMA tag):
* the allocated enhancement aperture keeps its own physical address, main
* RAM folds to its word-aligned live offset. Every source-address key (UI
* prepass nodes and items, packet guards, title roles) uses this, so a packet
* in the aperture never shares a key with, or reads back, the main RAM its
* low bits alias.
*/
static inline uint32_t psx_gpu_packet_key_for(uint32_t address, uint32_t used) {
uint32_t canonical = address & 0x1FFFFFFCu;
if (psx_mod_gpu_dma_aperture_offset_for(
canonical, 4u, used, (uint32_t *)0))
return canonical;
return psx_ram_canonical_offset(canonical) & ~3u;
}
uint32_t psx_gpu_packet_key(uint32_t address);
uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t size, uint32_t alignment);
uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address);
uint32_t psx_mod_memory_alloc(uint32_t size, uint32_t alignment);
+3 -3
View File
@@ -2,7 +2,7 @@
#define PSX_WS_PRIMITIVE_ROLES_H
#include <stdint.h>
#include <stddef.h>
#include "psx_memory.h"
#include "mod_memory.h"
/* Title-supplied packet identity, independent of draw order and sprite tags.
* Keys name PsyQ P_TAG headers; GPU lookup names the following command word. */
@@ -15,7 +15,7 @@ typedef struct {
static inline WsPrimitiveRole* ws_role_write(WsPrimitiveRole* tags,
uint32_t primitive, uint32_t now) {
uint32_t key = psx_ram_canonical_offset(primitive) & ~3u;
uint32_t key = psx_gpu_packet_key(primitive);
if (!key) return NULL;
uint32_t slot = (key >> 2) & (WS_ROLE_BUCKETS - 1u), victim = slot;
for (unsigned i = 0; i < 4; ++i) {
@@ -36,7 +36,7 @@ static inline WsPrimitiveRole* ws_role_write(WsPrimitiveRole* tags,
static inline const WsPrimitiveRole* ws_role_read(const WsPrimitiveRole* tags,
uint32_t source, uint32_t now) {
if (source == 0xFFFFFFFFu) return NULL;
uint32_t key = psx_ram_canonical_offset(source - 4u) & ~3u;
uint32_t key = psx_gpu_packet_key(source - 4u);
if (!key) return NULL;
uint32_t slot = (key >> 2) & (WS_ROLE_BUCKETS - 1u);
for (unsigned i = 0; i < 4; ++i) {
+10 -7
View File
@@ -53,9 +53,10 @@
#include <string.h>
#include "gpu_timeline.h"
/* Word-aligned main-RAM key for a primitive/OT address through the live
* geometry (retail: the DMAC's 0x1FFFFC fold). */
#define GPU_RAM_KEY(a) (psx_ram_canonical_offset(a) & ~3u)
/* Packet identity of a primitive/OT address: main RAM through the live
* geometry (retail: the DMAC's 0x1FFFFC fold), the enhancement aperture by its
* own address (mod_memory.h). */
#define GPU_RAM_KEY(a) psx_gpu_packet_key(a)
extern uint16_t psx_read_half(uint32_t addr);
extern uint8_t psx_read_byte(uint32_t addr);
@@ -2466,7 +2467,7 @@ void gpu_ws_tag_background_prim(uint32_t prim) {
}
WsPrepassPacketGuard guard = ws_prepass_packet_guard(words, count);
ws_hud_anchor_insert(ws_background_tags, WS_HUD_ANCHOR_TABLE_SIZE,
(prim + 4u) & 0x1FFFFCu, 0, &guard,
GPU_RAM_KEY((prim + 4u)), 0, &guard,
(uint32_t)s_frame_count);
ws_background_tags_used = 1;
ws_background_tag_frame = (uint32_t)s_frame_count;
@@ -6474,8 +6475,8 @@ void gpu_ws_validate_linked_list_header(uint32_t addr, uint32_t header) {
void gpu_ws_validate_linked_list_node(uint32_t addr, uint32_t num_words) {
if (ws_ui_prepass_count == 0) return;
uint32_t resolved =
GPU_RAM_KEY(psx_mod_gpu_dma_resolve_address(addr));
const uint32_t node_addr = psx_mod_gpu_dma_resolve_address(addr);
const uint32_t resolved = GPU_RAM_KEY(node_addr);
const WsUiPrepassNode *node = NULL;
for (uint32_t i = 0; i < ws_ui_prepass_node_count; i++) {
if (ws_ui_prepass_nodes[i].addr == resolved) {
@@ -6489,9 +6490,11 @@ void gpu_ws_validate_linked_list_node(uint32_t addr, uint32_t num_words) {
return;
}
/* Re-read the payload where the walk reads it (the node's own memory),
* never through the key. */
uint32_t payload[255];
uint32_t first_addr =
psx_mod_gpu_dma_resolve_address(resolved + 4u);
psx_mod_gpu_dma_resolve_address(node_addr + 4u);
for (uint32_t i = 0; i < num_words; i++) {
payload[i] = psx_read_word(psx_mod_gpu_dma_resolve_address(
first_addr + i * 4u));
+7 -3
View File
@@ -144,6 +144,10 @@ uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address) {
address, mod_gpu_dma_memory_used);
}
uint32_t psx_gpu_packet_key(uint32_t address) {
return psx_gpu_packet_key_for(address, mod_gpu_dma_memory_used);
}
/* Exposed for inlined main-RAM load helpers in psx_cyc.h (VLC/decode hot path). */
uint8_t *g_psx_ram = ram;
/* PSX_LOAD_DELAY gate (default on). −1 = unread; 0/1 after first resolve. */
@@ -1355,9 +1359,9 @@ static void mmio_write32(uint32_t addr, uint32_t val) {
* 0x80030000, so ROM 0xBFC38B1C executes at 0x80050B1C
* (scph1001_relocated_store). */
if (debug_cpu_ptr && scph1001_relocated_store(0x80050B1Cu, 0xBFC38B1Cu)) {
/* Word-aligned RAM key through the live geometry (retail: the
* 0x1FFFFC fold, identical to the DMA/GPU source keys). */
src = psx_ram_canonical_offset(debug_cpu_ptr->gpr[4] - 4u) & ~3u;
/* The same packet key as the DMA/GPU source keys (retail: the
* 0x1FFFFC fold). */
src = psx_gpu_packet_key(debug_cpu_ptr->gpr[4] - 4u);
}
gpu_set_gp0_source(src);
gpu_write_gp0(val);
+13 -2
View File
@@ -54,6 +54,10 @@ int mod_texture_packet_precision(uint32_t source, float q[3], float xy[6]) {
}
static uint32_t test_ram[0x00200000u / 4u];
/* The enhancement GPU-DMA aperture (mod_memory.h): a test allocates it by
* setting test_aperture_used; reads and keys then follow the real rules. */
static uint32_t test_aperture[0x00020000u / 4u];
static uint32_t test_aperture_used;
static struct {
int calls;
@@ -100,7 +104,9 @@ static inline void gpu_exec_record_triangle(int x0, int y0, int x1, int y1,
}
uint32_t psx_read_word(uint32_t addr) {
uint32_t phys = addr & 0x1FFFFFFFu;
uint32_t phys = addr & 0x1FFFFFFFu, off;
if (psx_mod_gpu_dma_aperture_offset_for(phys, 4u, test_aperture_used, &off))
return test_aperture[off / 4u];
if (phys <= 0x00200000u - 4u)
return test_ram[phys / 4u];
return 0;
@@ -243,7 +249,12 @@ void color_lut_map555(const ColorLut *lut, uint16_t bgr555,
if (g) *g = (uint8_t)(((bgr555 >> 5) & 0x1Fu) << 3);
if (b) *b = (uint8_t)(((bgr555 >> 10) & 0x1Fu) << 3);
}
uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address) { return address; }
uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address) {
return psx_mod_gpu_dma_resolve_address_for(address, test_aperture_used);
}
uint32_t psx_gpu_packet_key(uint32_t address) {
return psx_gpu_packet_key_for(address, test_aperture_used);
}
void text_xlate_vram_upload(int x, int y, int w, int h) {
(void)x; (void)y; (void)w; (void)h;
}
@@ -250,6 +250,7 @@ void text_xlate_vram_upload(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
int ws_cull_should_keep(uint32_t addr) { (void)addr; return 1; }
int ws_ui_group_should_keep(uint32_t addr) { (void)addr; return 1; }
uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address) { return address; }
uint32_t psx_gpu_packet_key(uint32_t address) { return address & 0x001FFFFCu; }
void ws_ui_group_assign(WsUiGroupItem *items, size_t count,
int32_t display_width, int dense_menu, int in_place)
{ (void)items; (void)count; (void)display_width; (void)dense_menu; (void)in_place; }
@@ -107,6 +107,31 @@ static void add_rear_pieces(const RearPiece *pieces, int count) {
test_ram[NODE_GAP / 4u] = NODE_RING;
}
/* The same HUD list in the enhancement GPU-DMA aperture (THPS2's draw-distance
* prim arena), with unrelated words in the main RAM its low bits alias. */
#define AP_BASE 0x00800000u
static void put_node_ap(uint32_t addr, uint32_t next, const uint32_t *words,
uint32_t count) {
const uint32_t off = (addr - AP_BASE) / 4u;
test_aperture[off] = (count << 24) | (next & 0xFFFFFFu);
for (uint32_t i = 0; i < count; i++)
test_aperture[off + 1u + i] = words[i];
}
static void move_hud_to_aperture(void) {
const uint32_t nodes[3] = {NODE_RING, NODE_FILL, NODE_FLAT};
test_aperture_used = sizeof test_aperture;
memset(test_aperture, 0, sizeof test_aperture);
for (int i = 0; i < 3; i++) {
const uint32_t h = test_ram[nodes[i] / 4u], n = h >> 24;
const uint32_t next = (h & 0xFFFFFFu) == 0xFFFFFFu
? 0xFFFFFFu : AP_BASE + (h & 0xFFFFFFu);
put_node_ap(AP_BASE + nodes[i], next, &test_ram[nodes[i] / 4u + 1u], n);
}
test_ram[OT_HEAD / 4u] = AP_BASE + NODE_RING;
for (uint32_t a = NODE_RING; a < NODE_FLAT + 0x40u; a += 4u)
test_ram[a / 4u] = 0xDEAD0000u | a;
}
static void load_packet(uint32_t node, uint32_t count) {
for (uint32_t i = 0; i < count; i++)
gp0_cmd_buf[i] = test_ram[node / 4u + 1u + i];
@@ -473,6 +498,39 @@ int main(void) {
assert(gpu_exec_triangles.min_x == 50 && gpu_exec_triangles.max_x == 140);
}
/* A list in the aperture validates against its own words at DMA time:
* the prepass survives the walk and the fill squashes as in RAM. A RAM
* node at the aliased offset is a different node. */
reset_state(1);
build_hud(60, 128, 64, 120);
move_hud_to_aperture();
gpu_ws_prepass_linked_list(OT_HEAD);
assert(ws_ui_prepass_count == 3);
{
const uint32_t nodes[3] = {NODE_RING, NODE_FILL, NODE_FLAT};
const uint32_t stale = ws_ui_reject.stale;
for (int i = 0; i < 3; i++) {
const uint32_t a = AP_BASE + nodes[i];
const uint32_t h = test_aperture[(a - AP_BASE) / 4u];
gpu_ws_validate_linked_list_header(a, h);
gpu_ws_validate_linked_list_node(a, h >> 24);
}
assert(ws_ui_reject.stale == stale && ws_ui_prepass_count == 3);
gpu_exec_reset_triangles();
for (uint32_t i = 0; i < 8; i++)
gp0_cmd_buf[i] = test_aperture[(NODE_FILL + 4u) / 4u + i];
gp0_cmd_source_addr = AP_BASE + NODE_FILL + 4u;
gp0_words_needed = 8;
gp0_exec_shaded_quad();
const int32_t centre = 60 + (128 - 60) / 2;
assert(gpu_exec_triangles.min_x == ws_scale_about(64, centre));
assert(gpu_exec_triangles.max_x == ws_scale_about(120, centre));
assert(GPU_RAM_KEY(AP_BASE + NODE_FILL) != GPU_RAM_KEY(NODE_FILL));
gpu_ws_validate_linked_list_header(NODE_FILL, test_ram[NODE_FILL / 4u]);
assert(ws_ui_reject.stale == stale + 1 && ws_ui_stale_why[2] > 0);
}
test_aperture_used = 0;
puts("ws_auto_ui_untextured_exec_test: PASS");
return 0;
}