Files
psxrecomp/runtime/tests/test_gpu_render_pass_provenance.c
T

303 lines
11 KiB
C

/* Exercise the real GPU checkpoint and GP0 provenance consumers without a GL
* context. RAM rollback deliberately follows GPU restore, as render_pass.c
* does: a later canonical DMA must find the proof for its restored words. */
#define GPU_EXEC_REAL_UI_GROUP
#define main gpu_packet_fixture_main
#include "test_gpu_textured_dot_nw_shift_exec.c"
#undef main
static const uint32_t command_addr = 0x10004u;
static const uint32_t canonical[] = {
0x28020304u, 0, 320, 240u << 16, (240u << 16) | 320u
};
static void packet_to_ram(const uint32_t *words, uint32_t count) {
memcpy(&test_ram[command_addr / 4u], words, count * sizeof(words[0]));
}
static void packet_to_gp0(const uint32_t *words, uint32_t count) {
gp0_cmd_source_addr = command_addr;
gp0_words_needed = (int)count;
memcpy(gp0_cmd_buf, words, count * sizeof(words[0]));
}
static void start_scene(void) {
reset_gpu_state_for_test();
configure_native_wide_16_9();
ws_hud_anchor_clear(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_hud_anchor_clear(ws_screen_mask_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_radial_mask_clear(ws_radial_screen_mask_tags);
ws_repeat_rect_tag_clear(ws_repeat_rect_tags);
memset(ws_primitive_roles, 0, sizeof(ws_primitive_roles));
ws_ui_prepass_count = ws_ui_prepass_node_count = 0;
ws_ui_prepass_rank = 0xFFFFu;
ws_reset_scene_history();
ws_nw_phase_backdrop = 0;
s_bg_phase_frame = UINT32_MAX;
packet_to_ram(canonical, 5);
packet_to_gp0(canonical, 5);
}
static void test_retagged_canonical_packet(void) {
start_scene();
gpu_ws_tag_background_prim(command_addr - 4u);
gpu_ws_tag_hud_prim(command_addr - 4u, -1);
gpu_ws_tag_world_primitive(command_addr - 4u, 1);
gpu_ws_tag_hud_primitive(command_addr - 4u, -1);
const int32_t offset = ws_nw_offset();
assert(offset > 0);
GpuWsTagStats before, after;
gpu_ws_get_tag_stats(NULL, &before);
/* Repeated in-between draws reuse the same packet address and guest frame. */
for (uint32_t pass = 1; pass <= 3; ++pass) {
uint32_t synthetic[5];
memcpy(synthetic, canonical, sizeof(synthetic));
synthetic[1] += pass;
assert(gpu_pass_checkpoint_save());
packet_to_ram(synthetic, 5);
packet_to_gp0(synthetic, 5);
gpu_ws_tag_background_prim(command_addr - 4u);
gpu_ws_tag_hud_prim(command_addr - 4u, 1);
gpu_ws_tag_world_primitive(command_addr - 4u, 0);
gpu_ws_tag_hud_primitive(command_addr - 4u, 1);
int32_t delta;
assert(psx_ws_prim_in_backdrop() == 1);
assert(ws_nw_explicit_hud_delta(&delta) && delta == offset);
gpu_pass_checkpoint_restore();
packet_to_ram(canonical, 5);
packet_to_gp0(canonical, 5);
assert(psx_ws_prim_in_backdrop() == 1);
assert(ws_nw_explicit_hud_delta(&delta) && delta == -offset);
assert(ws_tagged_world_primitive());
assert(ws_tagged_hud_edge() == -1);
assert(gpu_ws_background_requires_full_composite());
packet_to_gp0(synthetic, 5);
assert(!ws_nw_explicit_background());
assert(!ws_nw_explicit_hud_delta(&delta));
packet_to_gp0(canonical, 5);
}
gpu_ws_get_tag_stats(NULL, &after);
assert(after.tag_calls == before.tag_calls + 3);
assert(after.hit >= before.hit + 6); /* Diagnostic activity is not rolled back. */
}
static void test_pass_only_tags_and_phase(void) {
/* No image is consumed: discarded/watchdog-aborted passes use this same
* GPU restore. render_pass_abort_test covers the watchdog control flow
* separately with a mocked GPU/presenter. */
start_scene();
ws_nw_phase_backdrop = 1;
ws_bg_phase_note(0x28u);
assert(psx_ws_prim_in_backdrop() == 1);
assert(!gpu_ws_background_requires_full_composite());
assert(gpu_pass_checkpoint_save());
gpu_ws_tag_background_prim(command_addr - 4u);
gpu_ws_tag_hud_prim(command_addr - 4u, 1);
gpu_ws_tag_world_primitive(command_addr - 4u, 1);
ws_bg_phase_note(0x30u);
assert(gpu_ws_background_requires_full_composite());
gpu_pass_checkpoint_restore();
packet_to_ram(canonical, 5);
packet_to_gp0(canonical, 5);
assert(!ws_nw_explicit_background());
assert(!ws_nw_explicit_hud_delta(NULL));
assert(!gpu_ws_background_stretch_active());
assert(!gpu_ws_background_requires_full_composite());
assert(!ws_tagged_world_primitive());
assert(psx_ws_prim_in_backdrop() == 1); /* Original early-backdrop phase. */
}
static void test_aperture_hud_anchor(void) {
start_scene();
test_aperture_used = 64;
memcpy(test_aperture + 1, canonical, sizeof(canonical));
gp0_words_needed = 5;
memcpy(gp0_cmd_buf, canonical, sizeof(canonical));
const uint32_t packet = PSX_MOD_GPU_DMA_GUEST_BASE;
for (int edge = -1; edge <= 1; ++edge) {
gpu_ws_tag_hud_prim(packet, edge);
for (unsigned alias = 0; alias < 2; ++alias) {
gp0_cmd_source_addr = (alias ? packet & 0xFFFFFFu : packet) + 4;
int32_t delta;
assert(ws_nw_explicit_hud_delta(&delta));
assert(delta == edge * ws_nw_offset());
}
}
gpu_ws_set_view_anchor(4, 8, 12, 16);
for (int side = -1; side <= 1; ++side) {
ws_view = (WsViewAnchor){0, 0, side * ws_nw_offset(), 7, 9};
ws_view_frame = (uint32_t)s_frame_count;
gp0_cmd_buf[0] = canonical[0];
gp0_execute_command();
assert(fixture_wide_view.shift == 0);
assert(fixture_wide_view.left == 0 && fixture_wide_view.right == 0);
gp0_cmd_buf[0] ^= 1;
gp0_execute_command();
assert(fixture_wide_view.shift == ws_view.shift);
assert(fixture_wide_view.left == 7 && fixture_wide_view.right == 9);
}
gpu_ws_set_view_anchor(0, 0, 0, 0);
gp0_cmd_buf[0] = canonical[0];
gp0_cmd_buf[1] = 1;
assert(!ws_nw_explicit_hud_delta(NULL));
gp0_cmd_buf[1] = canonical[1];
gp0_cmd_source_addr = 4;
assert(!ws_nw_explicit_hud_delta(NULL));
GpuWsTagStats before, after;
gpu_ws_get_tag_stats(&before, NULL);
test_aperture_used = 16;
gpu_ws_tag_hud_prim(packet, 1);
gpu_ws_get_tag_stats(&after, NULL);
assert(after.tag_rejected == before.tag_rejected + 1);
test_aperture_used = 0;
}
static void test_hud_dot_draw(void) {
start_scene();
set_dot_packet(command_addr, 20, 20);
gp0_words_needed = 3;
uint32_t dot[3];
memcpy(dot, gp0_cmd_buf, sizeof(dot));
gpu_ws_tag_hud_prim(command_addr - 4u, -1);
assert(gpu_pass_checkpoint_save());
set_dot_packet(command_addr, 80, 20);
gpu_ws_tag_hud_prim(command_addr - 4u, 1);
exec_dot_and_expect(80 + ws_nw_offset(), 20);
gpu_pass_checkpoint_restore();
packet_to_ram(dot, 3);
packet_to_gp0(dot, 3);
exec_dot_and_expect(20 - ws_nw_offset(), 20);
}
static void test_screen_mask_packet(void) {
start_scene();
gpu_ws_tag_screen_mask_quad(command_addr - 4u);
gpu_ws_tag_radial_screen_mask_quad(command_addr - 4u, 2.f);
assert(gpu_pass_checkpoint_save());
uint32_t synthetic[5];
memcpy(synthetic, canonical, sizeof(synthetic));
synthetic[1] = 16;
packet_to_ram(synthetic, 5);
gpu_ws_tag_screen_mask_quad(command_addr - 4u);
gpu_ws_tag_radial_screen_mask_quad(command_addr - 4u, 3.f);
gpu_pass_checkpoint_restore();
packet_to_ram(canonical, 5);
packet_to_gp0(canonical, 5);
assert(ws_hud_anchor_lookup(ws_screen_mask_tags, WS_HUD_ANCHOR_TABLE_SIZE,
command_addr, gp0_cmd_buf, 5, (uint32_t)s_frame_count, NULL));
int32_t x[4] = {0, 320, 0, 320}, y[4] = {0, 0, 240, 240};
assert(ws_nw_radial_mask_transform(x, y));
assert(x[0] == -160 && x[1] == 480); /* Canonical scale 2, not pass scale 3. */
}
static void test_repeat_rect_packet(void) {
start_scene();
const uint32_t rect[] = {0x64808080u, 0, 0, (64u << 16) | 32u};
uint32_t synthetic[4];
memcpy(synthetic, rect, sizeof(synthetic));
synthetic[1] = 8;
packet_to_ram(rect, 4);
packet_to_gp0(rect, 4);
gpu_ws_tag_black_reveal_rect(command_addr - 4u);
gpu_ws_tag_repeat_rect(command_addr - 4u, 128);
assert(gpu_pass_checkpoint_save());
packet_to_ram(synthetic, 4);
gpu_ws_tag_black_reveal_rect(command_addr - 4u);
gpu_ws_tag_repeat_rect(command_addr - 4u, 64);
gpu_pass_checkpoint_restore();
packet_to_ram(rect, 4);
packet_to_gp0(rect, 4);
assert(ws_hud_anchor_lookup(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE,
command_addr, gp0_cmd_buf, 4, (uint32_t)s_frame_count, NULL));
assert(ws_repeat_rect_tag_lookup(ws_repeat_rect_tags, command_addr,
gp0_cmd_buf, 4, (uint32_t)s_frame_count) == 128);
}
static void test_scene_evidence(void) {
start_scene();
ws_full_2d = 0;
gpu_ws_set_gte_game_mode(1);
assert(gpu_ws_present_native_43());
assert(gpu_pass_checkpoint_save());
psx_ws_note_gte_project(3);
assert(!gpu_ws_present_native_43());
gpu_pass_checkpoint_restore();
packet_to_ram(canonical, 5);
assert(gpu_ws_present_native_43());
gpu_ws_set_gte_game_mode(0);
}
static void test_sprite_anchor(void) {
start_scene();
/* Sprite-anchor coordinates are consumed by the projection/squash path;
* native-wide uses the same table only for packet identity. */
ws_mode = 1;
ws_xnum = 3;
ws_xden = 4;
const uint32_t anchor_addr = 0x20000u;
ws_anchor_addr = anchor_addr;
CPUState cpu = {0};
cpu.read_word = psx_read_word;
cpu.gpr[4] = command_addr - 4u;
test_ram[anchor_addr / 4u] = 40;
psx_ws_sprite_tag(&cpu);
assert(gpu_pass_checkpoint_save());
test_ram[anchor_addr / 4u] = 80;
psx_ws_sprite_tag(&cpu);
gpu_pass_checkpoint_restore();
test_ram[anchor_addr / 4u] = 40;
int32_t anchor;
assert(ws_tagged_anchor(&anchor) && anchor == 40);
}
static void test_auto_ui_prepass(void) {
start_scene();
ws_mode = 1;
ws_xnum = 3;
ws_xden = 4;
gpu_ws_set_auto_ui_squash(1);
const uint32_t hud[] = {
0x28802040u, pack_vertex(20, 20), pack_vertex(40, 20),
pack_vertex(20, 32), pack_vertex(40, 32)
};
uint32_t synthetic[5];
memcpy(synthetic, hud, sizeof(synthetic));
synthetic[1] = pack_vertex(24, 20);
synthetic[3] = pack_vertex(24, 32);
test_ram[(command_addr - 4u) / 4u] = (5u << 24) | 0xFFFFFFu;
const uint32_t ot_head = 0xFF00u;
test_ram[ot_head / 4u] = command_addr - 4u; /* Empty OT entry establishes rank 0. */
packet_to_ram(hud, 5);
gpu_ws_prepass_linked_list(ot_head);
packet_to_gp0(hud, 5);
int32_t anchor;
assert(ws_auto_ui_anchor(&anchor));
assert(gpu_pass_checkpoint_save());
packet_to_ram(synthetic, 5);
gpu_ws_prepass_linked_list(ot_head);
gpu_pass_checkpoint_restore();
packet_to_ram(hud, 5);
gpu_ws_validate_linked_list_node(command_addr - 4u, 5);
packet_to_gp0(hud, 5);
assert(ws_auto_ui_anchor(&anchor));
packet_to_gp0(synthetic, 5);
assert(!ws_auto_ui_anchor(&anchor));
gpu_ws_set_auto_ui_squash(0);
}
int main(void) {
test_retagged_canonical_packet();
test_pass_only_tags_and_phase();
test_aperture_hud_anchor();
test_hud_dot_draw();
test_screen_mask_packet();
test_repeat_rect_packet();
test_sprite_anchor();
test_auto_ui_prepass();
test_scene_evidence();
puts("gpu_render_pass_provenance: PASS");
return 0;
}