mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
netplay: duplicate shared HUD counters into both unsplit views
This commit is contained in:
@@ -434,6 +434,18 @@ if(BUILD_TESTING)
|
||||
add_test(NAME ws_auto_ui_untextured_exec_test
|
||||
COMMAND ws_auto_ui_untextured_exec_test)
|
||||
|
||||
add_executable(ws_shared_split_ui_exec_test
|
||||
tests/test_ws_shared_split_ui_exec.c
|
||||
src/ws_ui_group.c
|
||||
src/psx_video_timing.c)
|
||||
target_include_directories(ws_shared_split_ui_exec_test PRIVATE include src)
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(ws_shared_split_ui_exec_test PRIVATE
|
||||
-ffunction-sections -fdata-sections)
|
||||
target_link_options(ws_shared_split_ui_exec_test PRIVATE -Wl,--gc-sections)
|
||||
endif()
|
||||
add_test(NAME ws_shared_split_ui_exec_test COMMAND ws_shared_split_ui_exec_test)
|
||||
|
||||
add_executable(ws_view_anchor_test tests/test_ws_view_anchor.c)
|
||||
add_executable(ws_view_anchor_gpu_test tests/test_ws_view_anchor_gpu.c
|
||||
src/psx_video_timing.c)
|
||||
|
||||
+84
-6
@@ -124,11 +124,16 @@ typedef struct {
|
||||
int32_t y;
|
||||
int32_t h;
|
||||
uint8_t op;
|
||||
uint8_t full_draw_area;
|
||||
uint8_t shared_split_ui;
|
||||
WsPrepassPacketGuard packet_guard;
|
||||
} WsUiPrepassItem;
|
||||
static WsUiPrepassItem ws_ui_prepass[WS_UI_PREPASS_MAX];
|
||||
static uint32_t ws_ui_prepass_count;
|
||||
static uint16_t ws_ui_prepass_rank = 0xFFFFu;
|
||||
/* Shared HUD packets execute once in each half. Keep the original packet
|
||||
* intact for provenance guards, textures, diagnostics and guest RAM. */
|
||||
static int ws_shared_ui_copy = -1;
|
||||
|
||||
#define WS_UI_PREPASS_NODE_MAX 8192u
|
||||
typedef struct {
|
||||
@@ -2531,6 +2536,29 @@ static int ws_auto_ui_anchor(int32_t *out_anchor) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ws_shared_ui_packet(void) {
|
||||
uint32_t op = gp0_cmd_buf[0] >> 24;
|
||||
if (!((op >= 0x28u && op <= 0x3Fu && (op & 8u)) ||
|
||||
(op >= 0x60u && op <= 0x7Fu))) return 0;
|
||||
if (!ws_local_viewport_cfg || ws_mode == 2 || !ws_vertical_split_active() ||
|
||||
psx_ws_prim_is_tagged()) return 0;
|
||||
int32_t anchor;
|
||||
if (!ws_auto_ui_anchor(&anchor)) return 0;
|
||||
uint32_t src = GPU_RAM_KEY(gp0_cmd_source_addr);
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++)
|
||||
if (ws_ui_prepass[i].src_addr == src)
|
||||
return ws_ui_prepass[i].shared_split_ui;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ws_shared_ui_delta(void) {
|
||||
if (ws_shared_ui_copy < 0) return 0;
|
||||
/* Projection mode still renders the complete native framebuffer. Both
|
||||
* peers draw both copies identically, then present their selected half. */
|
||||
int32_t W = ws_disp_w();
|
||||
return (ws_shared_ui_copy ? 3 * W / 4 : W / 4) - W / 2;
|
||||
}
|
||||
|
||||
static uint32_t ws_auto_ui_group_key_words(const uint32_t *words,
|
||||
uint32_t op,
|
||||
int32_t y, int32_t h) {
|
||||
@@ -2587,7 +2615,8 @@ static int ws_auto_ui_transform_quad(int32_t vx[4], const int32_t vy[4]) {
|
||||
|
||||
int32_t anchor;
|
||||
if (!ws_auto_ui_anchor(&anchor)) return 0;
|
||||
for (int i = 0; i < 4; i++) vx[i] = ws_scale_about(vx[i], anchor);
|
||||
for (int i = 0; i < 4; i++)
|
||||
vx[i] = ws_scale_about(vx[i], anchor) + ws_shared_ui_delta();
|
||||
ws_auto_ui_transform_count++;
|
||||
return 1;
|
||||
}
|
||||
@@ -2601,7 +2630,7 @@ static int ws_auto_ui_transform_rect(int32_t *x, int32_t y, int *w, int h) {
|
||||
return 0;
|
||||
int32_t anchor;
|
||||
if (!ws_auto_ui_anchor(&anchor)) return 0;
|
||||
*x = ws_scale_about(*x, anchor);
|
||||
*x = ws_scale_about(*x, anchor) + ws_shared_ui_delta();
|
||||
*w = (int)ws_scale_len(*w);
|
||||
ws_auto_ui_transform_count++;
|
||||
return 1;
|
||||
@@ -5305,7 +5334,8 @@ static int gp0_command_word_count(uint8_t opcode) {
|
||||
}
|
||||
|
||||
static void ws_ui_prepass_add(const uint32_t *words, uint32_t word_count,
|
||||
uint32_t source_addr, uint16_t rank) {
|
||||
uint32_t source_addr, uint16_t rank,
|
||||
int full_draw_area) {
|
||||
if (rank == 0xFFFFu) return;
|
||||
if (ws_ui_prepass_count >= WS_UI_PREPASS_MAX) { ws_ui_reject.cap++; return; }
|
||||
uint32_t op = words[0] >> 24;
|
||||
@@ -5402,6 +5432,8 @@ static void ws_ui_prepass_add(const uint32_t *words, uint32_t word_count,
|
||||
item->y = min_y;
|
||||
item->h = height;
|
||||
item->op = (uint8_t)op;
|
||||
item->full_draw_area = full_draw_area ? 1 : 0;
|
||||
item->shared_split_ui = 0;
|
||||
item->packet_guard = ws_prepass_packet_guard(words, word_count);
|
||||
}
|
||||
|
||||
@@ -5444,6 +5476,9 @@ void gpu_ws_prepass_linked_list(uint32_t start_addr) {
|
||||
uint32_t safety = 0;
|
||||
uint16_t rank = 0xFFFFu;
|
||||
const uint32_t max_nodes = 0x40000u;
|
||||
int area_left = (int)draw_area_left, area_right = (int)draw_area_right;
|
||||
GpuDisplayInfo di;
|
||||
gpu_get_display_info(&di);
|
||||
|
||||
for (;;) {
|
||||
if (safety++ > max_nodes) {
|
||||
@@ -5473,7 +5508,7 @@ void gpu_ws_prepass_linked_list(uint32_t start_addr) {
|
||||
ws_prepass_packet_guard(payload, num_words);
|
||||
if (num_words == 0) {
|
||||
rank = rank == 0xFFFFu ? 0u : (uint16_t)(rank + 1u);
|
||||
} else if (rank != 0xFFFFu) {
|
||||
} else {
|
||||
uint32_t word_addr =
|
||||
psx_mod_gpu_dma_resolve_address(addr + 4u);
|
||||
uint32_t offset = 0;
|
||||
@@ -5496,7 +5531,10 @@ void gpu_ws_prepass_linked_list(uint32_t start_addr) {
|
||||
}
|
||||
ws_ui_prepass_add(words, (uint32_t)count,
|
||||
psx_mod_gpu_dma_resolve_address(
|
||||
word_addr + offset * 4u), rank);
|
||||
word_addr + offset * 4u), rank,
|
||||
area_right - area_left + 1 >= (int)di.width);
|
||||
if (op == 0xE3u) area_left = (int)(words[0] & 0x3FFu);
|
||||
if (op == 0xE4u) area_right = (int)(words[0] & 0x3FFu);
|
||||
offset += (uint32_t)count;
|
||||
}
|
||||
}
|
||||
@@ -5600,8 +5638,36 @@ void gpu_ws_prepass_linked_list(uint32_t start_addr) {
|
||||
groups[i] = ws_ui_prepass[i].group;
|
||||
ws_ui_group_assign(groups, ws_ui_prepass_count, ws_disp_w(),
|
||||
ws_auto_ui_dense, ws_auto_ui_in_place);
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++)
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++) {
|
||||
ws_ui_prepass[i].group.anchor = group_origin + groups[i].anchor;
|
||||
ws_ui_prepass[i].group.root = groups[i].root;
|
||||
}
|
||||
|
||||
/* A shared counter can comprise many glyphs and shadow packets, none of
|
||||
* which individually crosses the split. Classify the complete admitted
|
||||
* front-layer run, and require full-screen draw provenance. Per-player
|
||||
* HUD, world geometry, divider strips and pause menus cannot qualify. */
|
||||
if (ws_local_viewport_cfg && ws_mode != 2 && ws_vertical_split_active()) {
|
||||
const int32_t W = ws_disp_w(), H = ws_disp_h(), seam = W / 2;
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++) {
|
||||
int32_t x0 = W, x1 = 0, y0 = H, y1 = 0;
|
||||
int full = 1;
|
||||
for (uint32_t j = 0; j < ws_ui_prepass_count; j++) {
|
||||
if (groups[j].root != groups[i].root) continue;
|
||||
const WsUiGroupItem *g = &groups[j];
|
||||
if (g->x < x0) x0 = g->x;
|
||||
if (g->x + g->width > x1) x1 = g->x + g->width;
|
||||
if (g->y < y0) y0 = g->y;
|
||||
if (g->y + g->height > y1) y1 = g->y + g->height;
|
||||
full &= ws_ui_prepass[j].full_draw_area;
|
||||
}
|
||||
if (full && x0 < seam && x1 > seam &&
|
||||
x1 - x0 <= W / 2 && y1 - y0 <= H / 4) {
|
||||
ws_ui_prepass[i].shared_split_ui = 1;
|
||||
ws_ui_prepass[i].group.anchor = group_origin + seam;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gpu_ws_validate_linked_list_header(uint32_t addr, uint32_t header) {
|
||||
@@ -5918,6 +5984,8 @@ void gpu_ws_census_set(int on) { ws_census_on = on ? 1 : 0; }
|
||||
uint64_t gpu_ws_census_seq(void) { return ws_census_seq; }
|
||||
|
||||
/* Execute a fully-collected GP0 command */
|
||||
static void gp0_dispatch_command(uint8_t opcode);
|
||||
|
||||
static void gp0_execute_command(void) {
|
||||
uint8_t opcode = (gp0_cmd_buf[0] >> 24) & 0xFF;
|
||||
gp0_opcode_count[opcode]++;
|
||||
@@ -5968,6 +6036,16 @@ static void gp0_execute_command(void) {
|
||||
else if (opcode >= 0x80 && opcode <= 0xDF) gp0_copy_count++;
|
||||
else if (opcode >= 0xE1 && opcode <= 0xE6) gp0_env_count++;
|
||||
|
||||
if (opcode >= 0x20 && opcode <= 0x7F && ws_shared_ui_packet()) {
|
||||
for (ws_shared_ui_copy = 0; ws_shared_ui_copy < 2; ws_shared_ui_copy++)
|
||||
gp0_dispatch_command(opcode);
|
||||
ws_shared_ui_copy = -1;
|
||||
} else {
|
||||
gp0_dispatch_command(opcode);
|
||||
}
|
||||
}
|
||||
|
||||
static void gp0_dispatch_command(uint8_t opcode) {
|
||||
switch (opcode) {
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
/* Exercise real OT admission, complete glyph/shadow grouping and GP0 replay.
|
||||
* No single glyph in this counter crosses the seam. */
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#define GPU_EXEC_REAL_UI_GROUP
|
||||
#include "../src/gpu.c"
|
||||
#include "gpu_exec_stubs.inc"
|
||||
|
||||
#define HEAD 0x10000u
|
||||
#define ENV 0x10100u
|
||||
#define GLYPH 0x10200u
|
||||
#define GATE 0x18000u
|
||||
|
||||
static uint32_t vertex(int x, int y) {
|
||||
return (uint16_t)x | ((uint32_t)(uint16_t)y << 16);
|
||||
}
|
||||
|
||||
static void counter(int full_area, int tall) {
|
||||
memset(test_ram, 0, sizeof(test_ram));
|
||||
s_frame_count = 100;
|
||||
ws_mode = 1; ws_full_2d = 1;
|
||||
ws_cfg_num = 32; ws_cfg_den = 9;
|
||||
ws_xnum = 3; ws_xden = 8; /* half of a 4:3 display -> 16:9 */
|
||||
ws_local_viewport_cfg = 1; ws_local_viewport_slot = 0;
|
||||
split_recent_left_age = split_recent_right_age = 0;
|
||||
ws_census_on = 0;
|
||||
hres1 = 2; hres2 = 0; video_mode = 0;
|
||||
display_depth = display_disabled = display_area_x = display_area_y = 0;
|
||||
h_display_x1 = 0x200; h_display_x2 = 0xC00;
|
||||
v_display_y1 = 0x10; v_display_y2 = 0x100;
|
||||
draw_offset_x = draw_offset_y = 0;
|
||||
draw_area_left = draw_area_top = 0;
|
||||
draw_area_right = 511; draw_area_bottom = 239;
|
||||
gp0_cmd_source_addr = UINT32_MAX;
|
||||
gpu_ws_set_auto_ui_squash(1);
|
||||
gpu_ws_set_auto_ui_in_place(0);
|
||||
uint32_t running = 0;
|
||||
gpu_ws_set_local_viewport_state_gate(GATE, &running, 1);
|
||||
test_ram[HEAD / 4] = ENV;
|
||||
test_ram[ENV / 4] = (2u << 24) | GLYPH;
|
||||
test_ram[ENV / 4 + 1] = 0xE3000000u;
|
||||
test_ram[ENV / 4 + 2] = 0xE4000000u | (239u << 10) |
|
||||
(full_area ? 511u : 255u);
|
||||
const int xs[] = {224, 246, 260, 278};
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
unsigned addr = GLYPH + 0x40u * i;
|
||||
int shadow = i & 1u;
|
||||
int x = xs[i / 2] + shadow;
|
||||
int y = 12 + shadow;
|
||||
int h = tall ? 220 : 18;
|
||||
uint32_t q[] = {
|
||||
shadow ? 0x2E404040u : 0x2C808080u,
|
||||
vertex(x, y), shadow ? 0x01000000u : 0u,
|
||||
vertex(x + 12, y), 0x0008000Cu,
|
||||
vertex(x, y + h), (uint32_t)h << 8,
|
||||
vertex(x + 12, y + h), ((uint32_t)h << 8) | 12u,
|
||||
};
|
||||
test_ram[addr / 4] = (9u << 24) |
|
||||
(i == 7 ? 0xFFFFFFu : addr + 0x40u);
|
||||
memcpy(&test_ram[addr / 4 + 1], q, sizeof(q));
|
||||
}
|
||||
gpu_ws_prepass_linked_list(HEAD);
|
||||
assert(ws_disp_w() == 512);
|
||||
assert(ws_ui_prepass_count == 8);
|
||||
}
|
||||
|
||||
static void load_glyph(unsigned i) {
|
||||
uint32_t addr = GLYPH + 0x40u * i + 4;
|
||||
memcpy(gp0_cmd_buf, &test_ram[addr / 4], 9 * sizeof(uint32_t));
|
||||
gp0_cmd_source_addr = addr;
|
||||
gp0_words_needed = 9;
|
||||
last_scaled_rect.calls = 0;
|
||||
}
|
||||
|
||||
static void expect_shared(int shared) {
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
assert(ws_ui_prepass[i].shared_split_ui == shared);
|
||||
load_glyph(i);
|
||||
assert(ws_shared_ui_packet() == shared);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
counter(1, 0);
|
||||
expect_shared(1);
|
||||
for (unsigned slot = 0; slot < 2; slot++) {
|
||||
ws_local_viewport_slot = (int)slot;
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
load_glyph(i);
|
||||
uint32_t original[9];
|
||||
memcpy(original, gp0_cmd_buf, sizeof(original));
|
||||
int32_t x, y;
|
||||
parse_vertex(original[1], &x, &y);
|
||||
for (ws_shared_ui_copy = 0; ws_shared_ui_copy < 2; ws_shared_ui_copy++) {
|
||||
gp0_exec_textured_quad();
|
||||
int base = ws_shared_ui_copy ? 256 : 0;
|
||||
int expected = base + 128 + ws_scale_about(x, 256) - 256;
|
||||
assert(last_scaled_rect.x == expected);
|
||||
assert(last_scaled_rect.x > base && last_scaled_rect.x +
|
||||
last_scaled_rect.w < base + 256);
|
||||
assert(last_scaled_rect.y == y);
|
||||
assert(last_scaled_rect.u0 == 0 && last_scaled_rect.u1 == 12);
|
||||
}
|
||||
ws_shared_ui_copy = -1;
|
||||
last_scaled_rect.calls = 0;
|
||||
gp0_execute_command();
|
||||
assert(last_scaled_rect.calls == 2);
|
||||
assert(memcmp(original, gp0_cmd_buf, sizeof(original)) == 0);
|
||||
assert(ws_shared_ui_copy == -1);
|
||||
}
|
||||
}
|
||||
/* Wider aspects keep the complete counter inside each half. */
|
||||
ws_xnum = 2; ws_xden = 7;
|
||||
load_glyph(0);
|
||||
gp0_execute_command();
|
||||
assert(last_scaled_rect.calls == 2);
|
||||
|
||||
/* Source reuse, pause, offline play and per-player clips fail closed. */
|
||||
load_glyph(0);
|
||||
gp0_cmd_buf[1] ^= 1u;
|
||||
assert(!ws_shared_ui_packet());
|
||||
counter(1, 0);
|
||||
test_ram[GATE / 4] = 1;
|
||||
load_glyph(0);
|
||||
gp0_execute_command();
|
||||
assert(last_scaled_rect.calls == 1);
|
||||
counter(1, 0);
|
||||
ws_local_viewport_cfg = 0;
|
||||
load_glyph(0);
|
||||
gp0_execute_command();
|
||||
assert(last_scaled_rect.calls == 1);
|
||||
counter(0, 0);
|
||||
expect_shared(0);
|
||||
counter(1, 1);
|
||||
expect_shared(0);
|
||||
puts("shared split HUD execution passed");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user