mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Honor explicit HUD anchors in projection rendering
This commit is contained in:
@@ -4,6 +4,30 @@ Owner-approved scope, 2026-10-06. Tracking: `beads-eio.3.277`; shared execution
|
||||
work: `beads-wuhs`. Implementation is in progress. Source presence, a successful
|
||||
build, a short gameplay check, and owner acceptance are distinct milestones.
|
||||
|
||||
## First review pass
|
||||
|
||||
Owner steering after the status check: prioritize runnable candidates and human
|
||||
validation. Use build/configuration checks and focused tests for risky changes;
|
||||
defer broad automated gameplay and performance matrices until owner feedback.
|
||||
Windows review candidates come first in the build queue, with native Linux
|
||||
artifacts following. Missing enhancements and known defects stay explicit.
|
||||
|
||||
For each candidate, provide its launcher/path and this short playtest checklist:
|
||||
|
||||
1. Launch, start/load, and play for a few minutes.
|
||||
2. Check ordinary and ultrawide aspect settings: scene edges, HUD and dialogue.
|
||||
3. Move the camera: look for jitter, seams, missing geometry and bad textures.
|
||||
4. Pause/resume, change areas and revisit; note stalls and load times.
|
||||
5. Check audio and existing multiplayer where applicable.
|
||||
|
||||
The shared guarded HUD anchor now also supports projection-and-stretch mode.
|
||||
Title code identifies completed screen-space packets; a common edge/centre
|
||||
anchor can span separate DMA lists. Native-wide translation, packet guards,
|
||||
expiry and native 4:3 behavior remain covered by focused execution fixtures.
|
||||
This addresses the mechanism behind Ape's separate Status backing/text batches;
|
||||
its title binding and actual visual result still need review. Tracking:
|
||||
`beads-eio.3.125` and `beads-uutk`.
|
||||
|
||||
## Acceptance target
|
||||
|
||||
- Enhanced OpenGL is the normal renderer, with 1080p internal resolution.
|
||||
|
||||
@@ -451,7 +451,8 @@ void gpu_ws_set_nw_hud_corners(int on);
|
||||
void gpu_ws_tag_hud_primitive(uint32_t primitive, int edge);
|
||||
void gpu_ws_tag_world_primitive(uint32_t primitive, int is_world);
|
||||
void gpu_ws_set_adaptive_backdrop_preload(int enabled);
|
||||
/* Explicit native-wide HUD packet anchor from a trusted title plugin.
|
||||
/* Explicit screen-space HUD packet anchor from a trusted title plugin.
|
||||
* Native-wide translates; projection-and-stretch scales about the anchor.
|
||||
* `prim` is the address of the PsyQ P_TAG word; the drawn command starts at
|
||||
* prim+4. anchor: -1 = left, 0 = center, +1 = right. */
|
||||
void gpu_ws_tag_hud_prim(uint32_t prim, int anchor);
|
||||
|
||||
@@ -238,8 +238,12 @@ void psx_mod_set_native_wide_nclip_previous_site(uint32_t address, uint32_t expe
|
||||
void psx_mod_tag_hud_primitive(uint32_t primitive, int edge);
|
||||
/* Packet-guarded screen-space anchor: -1 left, +1 right, 0 stays centred.
|
||||
* Unlike the legacy role tag above, zero explicitly protects centred text
|
||||
* from automatic layout transforms. Word immediately before the GP0 colour
|
||||
* command; for a compound E1+SPRT packet this is its E1 word (P_TAG+4). */
|
||||
* from automatic layout transforms. Native-wide translates the group;
|
||||
* projection-and-stretch squashes around the selected screen anchor. This
|
||||
* works across separate DMA lists and is inert at native 4:3. Title code
|
||||
* owns classification and must tag each completed packet. Pass the word
|
||||
* immediately before the GP0 colour command; for a compound E1+SPRT packet
|
||||
* this is its E1 word (P_TAG+4). */
|
||||
void psx_mod_anchor_hud_primitive(uint32_t primitive, int edge);
|
||||
/* Screen-space masks identified by their title's producer, P_TAG addresses.
|
||||
* Flat panels extend only their exterior boundaries. Radial flat/Gouraud
|
||||
|
||||
+45
-7
@@ -2416,7 +2416,7 @@ void gpu_ws_tag_hud_prim(uint32_t prim, int anchor) {
|
||||
uint32_t command_addr = prim + 4u;
|
||||
uint32_t words[12];
|
||||
uint32_t word_count = 0;
|
||||
if (!ws_native_wide_configured() ||
|
||||
if (!(ws_native_wide_configured() || ws_configured()) ||
|
||||
(prim & 3u) != 0 || prim > UINT32_MAX - 4u ||
|
||||
!ws_hud_command_words(command_addr, words, &word_count)) {
|
||||
ws_hud_tag_stats.tag_rejected++;
|
||||
@@ -2547,9 +2547,8 @@ static int ws_nw_radial_mask_transform(int32_t x[4], int32_t y[4]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws_nw_explicit_hud_delta(int32_t *out_delta) {
|
||||
if (out_delta) *out_delta = 0;
|
||||
if (!ws_native_wide_active() || gp0_cmd_source_addr == 0xFFFFFFFFu)
|
||||
static int ws_explicit_hud_anchor(int *out_anchor) {
|
||||
if (gp0_cmd_source_addr == 0xFFFFFFFFu)
|
||||
return 0;
|
||||
if (gp0_words_needed <= 0 || gp0_words_needed > 12)
|
||||
return 0;
|
||||
@@ -2563,9 +2562,36 @@ static int ws_nw_explicit_hud_delta(int32_t *out_delta) {
|
||||
ws_tag_stats_note_lookup(&ws_hud_tag_stats, result);
|
||||
if (result != WS_HUD_ANCHOR_HIT)
|
||||
return 0;
|
||||
int32_t off = ws_nw_offset();
|
||||
if (out_anchor) *out_anchor = anchor;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws_nw_explicit_hud_delta(int32_t *out_delta) {
|
||||
if (out_delta) *out_delta = 0;
|
||||
if (!ws_native_wide_active()) return 0;
|
||||
int anchor;
|
||||
if (!ws_explicit_hud_anchor(&anchor)) return 0;
|
||||
if (out_delta)
|
||||
*out_delta = ws_hud_anchor_native_delta(1, off, anchor);
|
||||
*out_delta = ws_hud_anchor_native_delta(1, ws_nw_offset(), anchor);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* A title can identify one widget whose pieces arrive in separate DMA lists.
|
||||
* Per-list auto grouping cannot see that ownership. Use the same guarded
|
||||
* edge/centre role as native-wide, expressed as a squash pivot here. */
|
||||
static int ws_projection_hud_anchor(int32_t *out_anchor) {
|
||||
if (!ws_active()) return 0;
|
||||
int edge;
|
||||
if (!ws_explicit_hud_anchor(&edge)) return 0;
|
||||
if (out_anchor)
|
||||
*out_anchor = ws_disp_x() + (edge < 0 ? 0 : edge > 0 ? ws_disp_w() : ws_disp_w() / 2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws_projection_hud_transform(int32_t *x, int count) {
|
||||
int32_t anchor;
|
||||
if (!ws_projection_hud_anchor(&anchor)) return 0;
|
||||
for (int i = 0; i < count; i++) x[i] = ws_scale_about(x[i], anchor);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -2574,6 +2600,9 @@ static int ws_nw_explicit_hud_delta(int32_t *out_delta) {
|
||||
* a direct hit too in case a tag site passes the colour-word address. */
|
||||
static int ws_tagged_anchor(int32_t *out_ax) {
|
||||
if (!ws_active() || gp0_cmd_source_addr == 0xFFFFFFFFu) return 0;
|
||||
/* Explicit screen-space ownership wins over a recycled billboard tag.
|
||||
* The regular UI transform below will apply it exactly once. */
|
||||
if (ws_projection_hud_anchor(NULL)) return 0;
|
||||
uint32_t now = (uint32_t)s_frame_count;
|
||||
for (int variant = 0; variant < 2; variant++) {
|
||||
uint32_t key = GPU_RAM_KEY((gp0_cmd_source_addr - (variant ? 0u : 4u)));
|
||||
@@ -2896,6 +2925,7 @@ static int ws_axis_aligned_quad(const int32_t vx[4], const int32_t vy[4]) {
|
||||
* quad): squashed with its widget when the prepass admitted it as lying
|
||||
* inside that widget (ws_ui_prepass_finish). */
|
||||
static int ws_auto_ui_transform_part(int32_t *vx, int n) {
|
||||
if (ws_projection_hud_transform(vx, n)) return 1;
|
||||
if (ws_nw_explicit_hud_delta(NULL) || psx_ws_prim_is_tagged()) return 0;
|
||||
int32_t anchor;
|
||||
if (!ws_auto_ui_anchor(&anchor)) return 0;
|
||||
@@ -2906,6 +2936,7 @@ static int ws_auto_ui_transform_part(int32_t *vx, int n) {
|
||||
}
|
||||
|
||||
static int ws_auto_ui_transform_quad(int32_t vx[4], const int32_t vy[4]) {
|
||||
if (ws_projection_hud_transform(vx, 4)) return 1;
|
||||
if (ws_nw_explicit_hud_delta(NULL) || psx_ws_prim_is_tagged()) return 0;
|
||||
if (!ws_axis_aligned_quad(vx, vy)) return ws_auto_ui_transform_part(vx, 4);
|
||||
|
||||
@@ -2931,7 +2962,14 @@ static int ws_auto_ui_transform_quad(int32_t vx[4], const int32_t vy[4]) {
|
||||
}
|
||||
|
||||
static int ws_auto_ui_transform_rect(int32_t *x, int32_t y, int *w, int h) {
|
||||
if (!x || !w || *w <= 0 || ws_nw_explicit_hud_delta(NULL) || psx_ws_prim_is_tagged())
|
||||
if (!x || !w || *w <= 0) return 0;
|
||||
int32_t explicit_anchor;
|
||||
if (ws_projection_hud_anchor(&explicit_anchor)) {
|
||||
*x = ws_scale_about(*x, explicit_anchor);
|
||||
*w = (int)ws_scale_len(*w);
|
||||
return 1;
|
||||
}
|
||||
if (ws_nw_explicit_hud_delta(NULL) || psx_ws_prim_is_tagged())
|
||||
return 0;
|
||||
int32_t X = ws_disp_x(), W = ws_disp_w(), H = ws_disp_h();
|
||||
if ((*x <= X && *x + *w >= X + W && y <= 0 && y + h >= H) ||
|
||||
|
||||
@@ -190,11 +190,90 @@ static void test_wall_behind_front_layer(int triangle) {
|
||||
assert(ws_auto_ui_transform_count == 0);
|
||||
}
|
||||
|
||||
static void test_explicit_projection_widget(void) {
|
||||
const uint32_t tile = 0x20000u, glyph = 0x21000u;
|
||||
reset_state(0);
|
||||
hres1 = 0; hres2 = 1; /* Captured Ape display: 384x240. */
|
||||
h_display_x2 = h_display_x1 + 384u * 7u;
|
||||
ws_hud_sprt = 0;
|
||||
gpu_ws_set_auto_ui_squash(0); /* Explicit ownership is independent. */
|
||||
ws_hud_anchor_clear(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE);
|
||||
memset(test_ram, 0, sizeof(test_ram));
|
||||
/* Actual Status backing tile shape, followed by a separate glyph list.
|
||||
* Replacing the prepass must not lose the earlier list's widget anchor. */
|
||||
const uint32_t backing[] = {0x76606060u, 0x00C60146u, 0x77ABB090u};
|
||||
const uint32_t text[] = {0x7D808080u, pack_vertex(54, 64), 0x7C5D9800u};
|
||||
put_node(tile, 0xFFFFFFu, backing, 3);
|
||||
put_node(glyph, 0xFFFFFFu, text, 3);
|
||||
gpu_ws_tag_hud_prim(tile, 0);
|
||||
gpu_ws_tag_hud_prim(glyph, 0);
|
||||
test_ram[OT_HEAD / 4u] = glyph;
|
||||
gpu_ws_prepass_linked_list(OT_HEAD);
|
||||
load_packet(tile, 3);
|
||||
last_scaled_rect.calls = 0;
|
||||
gp0_exec_textured_8x8();
|
||||
assert(last_scaled_rect.calls == 1);
|
||||
assert(last_scaled_rect.x == ws_scale_about(326, 192));
|
||||
assert(last_scaled_rect.w == 3);
|
||||
assert(last_scaled_rect.u1 - last_scaled_rect.u0 == 8);
|
||||
|
||||
/* A stale legacy billboard classification cannot double-transform an
|
||||
* explicitly owned screen-space packet. */
|
||||
WsTag *legacy = &ws_tags[(glyph >> 2) & (WS_TAG_BUCKETS - 1)];
|
||||
legacy->key = glyph; legacy->stamp = (uint32_t)s_frame_count;
|
||||
legacy->anchor_x = 54;
|
||||
for (int edge = -1; edge <= 1; edge++) {
|
||||
gpu_ws_tag_hud_prim(glyph, edge);
|
||||
load_packet(glyph, 3);
|
||||
last_scaled_rect.calls = 0;
|
||||
gp0_exec_textured_16x16();
|
||||
assert(last_scaled_rect.calls == 1);
|
||||
assert(last_scaled_rect.x == ws_scale_about(54, (edge + 1) * 192));
|
||||
assert(last_scaled_rect.w == 6);
|
||||
}
|
||||
memset(ws_tags, 0, sizeof(ws_tags));
|
||||
|
||||
/* Complete packet guards, frame expiry and native 4:3 identity. */
|
||||
load_packet(glyph, 3);
|
||||
gp0_cmd_buf[2] ^= 1u;
|
||||
last_textured_rect.calls = last_scaled_rect.calls = 0;
|
||||
gp0_exec_textured_16x16();
|
||||
assert(last_textured_rect.calls == 1 && last_textured_rect.x == 54);
|
||||
assert(last_scaled_rect.calls == 0);
|
||||
s_frame_count += 3;
|
||||
load_packet(glyph, 3);
|
||||
last_textured_rect.calls = last_scaled_rect.calls = 0;
|
||||
gp0_exec_textured_16x16();
|
||||
assert(last_textured_rect.calls == 1 && last_textured_rect.x == 54);
|
||||
assert(last_scaled_rect.calls == 0);
|
||||
gpu_ws_tag_hud_prim(glyph, 0);
|
||||
ws_xnum = ws_xden = 1;
|
||||
last_textured_rect.calls = last_scaled_rect.calls = 0;
|
||||
gp0_exec_textured_16x16();
|
||||
assert(last_textured_rect.calls == 1 && last_textured_rect.x == 54);
|
||||
assert(last_scaled_rect.calls == 0);
|
||||
|
||||
/* Explicitly identified panels do not depend on automatic size/rank
|
||||
* heuristics; untagged world/fades still use the existing conservative gate. */
|
||||
ws_xnum = 3; ws_xden = 8;
|
||||
const uint32_t panel[] = {0x28202020u, pack_vertex(30, 30),
|
||||
pack_vertex(334, 30), pack_vertex(30, 206), pack_vertex(334, 206)};
|
||||
put_node(tile, 0xFFFFFFu, panel, 5);
|
||||
gpu_ws_tag_hud_prim(tile, 0);
|
||||
load_packet(tile, 5);
|
||||
gpu_exec_reset_triangles();
|
||||
gp0_exec_mono_quad();
|
||||
assert(gpu_exec_triangles.min_x == ws_scale_about(30, 192));
|
||||
assert(gpu_exec_triangles.max_x == ws_scale_about(334, 192));
|
||||
ws_hud_anchor_clear(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int gmin, gmax, fmin, fmax;
|
||||
|
||||
test_wall_behind_front_layer(0);
|
||||
test_wall_behind_front_layer(1);
|
||||
test_explicit_projection_widget();
|
||||
|
||||
/* A real HUD remains eligible when its last drawing layer is followed
|
||||
* by an empty OT bucket, as in Ape's memory-card menu. */
|
||||
|
||||
Reference in New Issue
Block a user