mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
widescreen: auto_ui_anchor = "in_place" squashes each HUD run about its own centre
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,10 @@ auto_ui_squash = true # pre-scan the current GPU linked list,
|
||||
# select its highest populated UI rank,
|
||||
# and share one anchor across each
|
||||
# complete glyph/icon run.
|
||||
auto_ui_anchor = "edges" # "edges" (default): each run pins to
|
||||
# its left/centre/right third.
|
||||
# "in_place": each run squashes about
|
||||
# its own centre.
|
||||
clear_reveal = true # clear synthetic native-wide side margins
|
||||
# at opted-in scene/map boundaries (default false).
|
||||
nw_left_hud_packet_lo = "0x000E3400" # optional targeted left-HUD packet range
|
||||
@@ -141,6 +145,15 @@ layer are grouped by texture and screen row before any command is transformed.
|
||||
Depth-sorted world packets, full-frame backdrops, and true 4:3 frames remain
|
||||
untouched.
|
||||
|
||||
By default each run is pinned to the nearest screen edge or centre third, so
|
||||
corner HUD moves to the wide-frame corners. Some HUD widgets mix those flat
|
||||
quads with GTE-projected parts the correction cannot move: Spider-Man's compass
|
||||
is two ring quads around a 3D arrow. Edge-anchoring the ring pulled it ~70 px
|
||||
off its arrow at 32:9. `auto_ui_anchor = "in_place"` squashes each run about
|
||||
its own centre instead, which corrects its proportions and leaves it where the
|
||||
stretched 4:3 layout places it. `gpu_state` reports the mode as
|
||||
`ws.auto_ui.in_place`.
|
||||
|
||||
**Changing `sprite_tag_funcs` requires a game regen** (the tag callback is
|
||||
emitted into the generated C). `widescreen.cull.keep` is consumed by both the
|
||||
native recompiler and the dirty-RAM interpreter: regenerate main/overlay native
|
||||
|
||||
@@ -1494,6 +1494,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
uint32_t ws_sprite_anchor_addr = 0;
|
||||
bool ws_hud_sprt_squash = false;
|
||||
bool ws_auto_ui_squash = false;
|
||||
bool ws_auto_ui_in_place = false;
|
||||
bool ws_full_2d = false;
|
||||
bool ws_gte_game_mode = false;
|
||||
bool ws_precise_nclip = false;
|
||||
@@ -1640,6 +1641,15 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
ws_hud_sprt_squash = toml::find<bool>(ws, "hud_sprt_squash");
|
||||
if (ws.contains("auto_ui_squash"))
|
||||
ws_auto_ui_squash = toml::find<bool>(ws, "auto_ui_squash");
|
||||
if (ws.contains("auto_ui_anchor")) {
|
||||
const auto anchor = toml::find<std::string>(ws, "auto_ui_anchor");
|
||||
if (anchor == "in_place")
|
||||
ws_auto_ui_in_place = true;
|
||||
else if (anchor != "edges")
|
||||
throw std::runtime_error(fmt::format(
|
||||
"{}: [widescreen] auto_ui_anchor must be \"edges\" or "
|
||||
"\"in_place\", got \"{}\"", config_path.string(), anchor));
|
||||
}
|
||||
if (ws.contains("full_2d"))
|
||||
ws_full_2d = toml::find<bool>(ws, "full_2d");
|
||||
if (ws.contains("gte_game_mode"))
|
||||
@@ -2223,6 +2233,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
/*ws_sprite_anchor_addr*/ ws_sprite_anchor_addr,
|
||||
/*ws_hud_sprt_squash*/ ws_hud_sprt_squash,
|
||||
/*ws_auto_ui_squash*/ ws_auto_ui_squash,
|
||||
/*ws_auto_ui_in_place*/ ws_auto_ui_in_place,
|
||||
/*data_shard_funcs*/ data_shard_funcs,
|
||||
/*mod_function_entry_funcs*/ mod_function_entry_funcs,
|
||||
/*hot_funcs*/ hot_funcs,
|
||||
|
||||
@@ -864,6 +864,11 @@ struct GameConfig {
|
||||
// the final ordering-table layer. Repeated glyph/icon rows share an anchor
|
||||
// so centred text and edge counters cannot split at thirds boundaries.
|
||||
bool ws_auto_ui_squash = false;
|
||||
// auto_ui_anchor = "in_place": squash each grouped UI run about its own
|
||||
// centre instead of pinning it to the nearest screen edge/centre third.
|
||||
// For HUD widgets that combine flat quads with GTE-projected parts the
|
||||
// correction cannot move (Spider-Man's compass ring and 3D arrow).
|
||||
bool ws_auto_ui_in_place = false;
|
||||
|
||||
// [data_shards] funcs: functions that get the memoized pure-function
|
||||
// replay entry/return hooks (psx_datashard_enter/psx_datashard_ret).
|
||||
|
||||
@@ -204,6 +204,9 @@ void gpu_ws_bg2d_set_host_arena(uint32_t base, uint32_t size);
|
||||
struct WsViewAnchor;
|
||||
int gpu_ws_bg2d_get_view(unsigned layer, struct WsViewAnchor *view);
|
||||
void gpu_ws_set_auto_ui_squash(int on);
|
||||
/* [widescreen] auto_ui_anchor = "in_place": each UI run squashes about its own
|
||||
* centre rather than an edge/centre third (default "edges"). */
|
||||
void gpu_ws_set_auto_ui_in_place(int on);
|
||||
/* [widescreen.bg2d] Capcom 2D background tile-loop widen — hooked at the renderer's
|
||||
* column-count / start-tile-col / start-screen-x instructions. Identity at 4:3
|
||||
* and in the engine's 512 hi-res mode. */
|
||||
@@ -535,6 +538,7 @@ typedef struct {
|
||||
uint32_t last_ovh_frame; /* newest SUSTAINED polygon-overhang frame (the
|
||||
2D-only-scene classifier's world signal) */
|
||||
int auto_ui_squash; /* final-OT grouped UI correction configured */
|
||||
int auto_ui_in_place; /* runs anchor at their own centre, not thirds */
|
||||
int auto_ui_dense; /* current list classified as a dense menu */
|
||||
uint32_t auto_ui_ot_rank; /* highest populated UI rank in current list */
|
||||
uint64_t auto_ui_candidates;
|
||||
|
||||
@@ -37,12 +37,17 @@ typedef struct {
|
||||
uint32_t root;
|
||||
} WsUiGroupItem;
|
||||
|
||||
/* Assign one thirds anchor to each complete spatial run. Items with the same
|
||||
/* Assign one anchor to each complete spatial run. Items with the same
|
||||
* texture/row key join when their horizontal intervals touch or are separated
|
||||
* by at most WS_UI_GROUP_JOIN_GAP pixels. This is intentionally a whole-frame
|
||||
* operation: animated text is coherent on its first frame, not one frame late. */
|
||||
* operation: animated text is coherent on its first frame, not one frame late.
|
||||
*
|
||||
* The anchor is the run's thirds anchor (left edge, centre, right edge), or
|
||||
* with in_place the run's own centre, so it keeps the position the stretched
|
||||
* 4:3 layout gives it. in_place is for HUD widgets whose GTE-projected parts
|
||||
* (never corrected) must stay registered with their flat quads. */
|
||||
void ws_ui_group_assign(WsUiGroupItem *items, size_t count,
|
||||
int32_t display_width, int dense_menu);
|
||||
int32_t display_width, int dense_menu, int in_place);
|
||||
|
||||
int32_t ws_ui_anchor_for_bounds(int32_t x, int32_t width,
|
||||
int32_t display_width);
|
||||
|
||||
@@ -5385,7 +5385,7 @@ static void handle_gpu_state(int id, const char *json)
|
||||
"\"cur_frame\":%llu,\"last_tag_frame\":%u,\"last_3d_frame\":%u,"
|
||||
"\"gte_verts\":%u,\"last_world3d_frame\":%u,"
|
||||
"\"ovh_prims\":%u,\"last_ovh_frame\":%u,"
|
||||
"\"auto_ui\":{\"configured\":%d,\"dense\":%d,\"ot_rank\":%u,"
|
||||
"\"auto_ui\":{\"configured\":%d,\"in_place\":%d,\"dense\":%d,\"ot_rank\":%u,"
|
||||
"\"candidates\":%llu,\"transforms\":%llu},"
|
||||
"\"aspect_cone\":{\"calls\":%llu,\"identity_43\":%llu,"
|
||||
"\"vanilla_keep\":%llu,\"visible_keep\":%llu,"
|
||||
@@ -5418,7 +5418,8 @@ static void handle_gpu_state(int id, const char *json)
|
||||
(unsigned long long)ws.cur_frame, ws.last_tag_frame,
|
||||
ws.last_3d_frame, ws.gte_verts, ws.last_world3d_frame,
|
||||
ws.ovh_prims, ws.last_ovh_frame,
|
||||
ws.auto_ui_squash, ws.auto_ui_dense, ws.auto_ui_ot_rank,
|
||||
ws.auto_ui_squash, ws.auto_ui_in_place, ws.auto_ui_dense,
|
||||
ws.auto_ui_ot_rank,
|
||||
(unsigned long long)ws.auto_ui_candidates,
|
||||
(unsigned long long)ws.auto_ui_transforms,
|
||||
(unsigned long long)ws.aspect_cone_calls,
|
||||
|
||||
+7
-1
@@ -106,6 +106,7 @@ static int32_t ws_xnum = 1, ws_xden = 1; /* X squash factor; 1/1 = off */
|
||||
static uint32_t ws_anchor_addr = 0; /* scratchpad addr of anchor SXY */
|
||||
static int ws_hud_sprt = 0; /* edge-anchor untagged HUD SPRTs */
|
||||
static int ws_auto_ui_squash;
|
||||
static int ws_auto_ui_in_place;
|
||||
static int ws_auto_ui_dense;
|
||||
static int ws_active(void);
|
||||
static int gp0_command_word_count(uint8_t opcode);
|
||||
@@ -215,6 +216,10 @@ static void ws_clear_all_reveal_margins(void);
|
||||
* engage (gpu_ws_set_full_2d); PSX_WS_FORCE_2D=1 forces it on for testing. */
|
||||
static int ws_full_2d = 0;
|
||||
void gpu_ws_set_full_2d(int on) { ws_full_2d = on ? 1 : 0; }
|
||||
void gpu_ws_set_auto_ui_in_place(int on) {
|
||||
ws_auto_ui_in_place = on ? 1 : 0;
|
||||
}
|
||||
|
||||
void gpu_ws_set_auto_ui_squash(int on) {
|
||||
ws_auto_ui_squash = on ? 1 : 0;
|
||||
ws_ui_prepass_count = 0;
|
||||
@@ -2061,6 +2066,7 @@ void gpu_ws_get_debug(GpuWsDebug* out) {
|
||||
out->ovh_prims = ws_ovh_prev;
|
||||
out->last_ovh_frame = ws_sust_ovh_stamp;
|
||||
out->auto_ui_squash = ws_auto_ui_squash;
|
||||
out->auto_ui_in_place = ws_auto_ui_in_place;
|
||||
out->auto_ui_dense = ws_auto_ui_dense;
|
||||
out->auto_ui_ot_rank =
|
||||
ws_ui_prepass_rank != 0xFFFFu ? ws_ui_prepass_rank : UINT32_MAX;
|
||||
@@ -5471,7 +5477,7 @@ void gpu_ws_prepass_linked_list(uint32_t start_addr) {
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++)
|
||||
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_dense, ws_auto_ui_in_place);
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++)
|
||||
ws_ui_prepass[i].group.anchor = group_origin + groups[i].anchor;
|
||||
}
|
||||
|
||||
@@ -13416,6 +13416,7 @@ int main(int argc, char** argv) {
|
||||
g_ws_anchor_addr = gc.ws_sprite_anchor_addr;
|
||||
g_ws_hud_sprt = gc.ws_hud_sprt_squash;
|
||||
gpu_ws_set_auto_ui_squash(gc.ws_auto_ui_squash ? 1 : 0);
|
||||
gpu_ws_set_auto_ui_in_place(gc.ws_auto_ui_in_place ? 1 : 0);
|
||||
/* [widescreen] full_2d — opt a pure-2D sprite game (MMX6) into the
|
||||
* widescreen present path. Applied to the GPU layer up front so the
|
||||
* ws engage at game entry classifies every frame as gameplay. */
|
||||
|
||||
@@ -103,8 +103,14 @@ static size_t root_of(size_t *parent, size_t index) {
|
||||
return index;
|
||||
}
|
||||
|
||||
static int32_t run_anchor(int32_t x, int32_t width, int32_t display_width,
|
||||
int in_place) {
|
||||
if (in_place) return x + width / 2;
|
||||
return ws_ui_anchor_for_bounds(x, width, display_width);
|
||||
}
|
||||
|
||||
void ws_ui_group_assign(WsUiGroupItem *items, size_t count,
|
||||
int32_t display_width, int dense_menu) {
|
||||
int32_t display_width, int dense_menu, int in_place) {
|
||||
if (!items || count == 0 || display_width <= 0) return;
|
||||
if (dense_menu) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
@@ -117,8 +123,8 @@ void ws_ui_group_assign(WsUiGroupItem *items, size_t count,
|
||||
size_t *parent = (size_t *)malloc(count * sizeof(*parent));
|
||||
if (!parent) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
items[i].anchor = ws_ui_anchor_for_bounds(
|
||||
items[i].x, items[i].width, display_width);
|
||||
items[i].anchor = run_anchor(
|
||||
items[i].x, items[i].width, display_width, in_place);
|
||||
items[i].root = (uint32_t)i; /* diag: each item stands alone */
|
||||
}
|
||||
return;
|
||||
@@ -164,7 +170,7 @@ void ws_ui_group_assign(WsUiGroupItem *items, size_t count,
|
||||
max_x = items[j].x + items[j].width;
|
||||
}
|
||||
items[i].anchor =
|
||||
ws_ui_anchor_for_bounds(min_x, max_x - min_x, display_width);
|
||||
run_anchor(min_x, max_x - min_x, display_width, in_place);
|
||||
items[i].root = (uint32_t)root;
|
||||
}
|
||||
free(parent);
|
||||
|
||||
@@ -208,8 +208,9 @@ int psx_ws_aspect_cone_contains(int32_t x, int32_t z, int32_t y,
|
||||
return 0;
|
||||
}
|
||||
void ws_ui_group_assign(WsUiGroupItem *items, size_t count,
|
||||
int32_t display_width, int dense_menu) {
|
||||
int32_t display_width, int dense_menu, int in_place) {
|
||||
(void)items; (void)count; (void)display_width; (void)dense_menu;
|
||||
(void)in_place;
|
||||
}
|
||||
int32_t ws_ui_anchor_for_bounds(int32_t x, int32_t width,
|
||||
int32_t display_width) {
|
||||
|
||||
@@ -21,7 +21,7 @@ int main(void) {
|
||||
{1, 168, 16, 100, 16}, {1, 184, 16, 100, 16}, {1, 200, 16, 100, 16},
|
||||
{1, 216, 16, 100, 16}, {1, 232, 16, 100, 16}, {1, 248, 16, 100, 16},
|
||||
};
|
||||
ws_ui_group_assign(text, 9, display_width, 0);
|
||||
ws_ui_group_assign(text, 9, display_width, 0, 0);
|
||||
for (int i = 0; i < 9; i++) assert(text[i].anchor == 192);
|
||||
assert(scale_about(184, text[3].anchor, 4, 7) ==
|
||||
scale_about(184, text[4].anchor, 4, 7));
|
||||
@@ -30,7 +30,7 @@ int main(void) {
|
||||
{2, 300, 16, 100, 16}, {2, 320, 16, 100, 16},
|
||||
{2, 340, 16, 100, 16}, {2, 360, 16, 100, 16},
|
||||
};
|
||||
ws_ui_group_assign(monkeys, 4, display_width, 0);
|
||||
ws_ui_group_assign(monkeys, 4, display_width, 0, 0);
|
||||
for (int i = 0; i < 4; i++) assert(monkeys[i].anchor == 384);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int32_t a = scale_about(monkeys[i].x, monkeys[i].anchor, 3, 4);
|
||||
@@ -43,12 +43,12 @@ int main(void) {
|
||||
{3, 20, 16, 100, 16}, {3, 36, 16, 100, 16},
|
||||
{3, 330, 16, 100, 16}, {3, 346, 16, 100, 16},
|
||||
};
|
||||
ws_ui_group_assign(edge_runs, 4, display_width, 0);
|
||||
ws_ui_group_assign(edge_runs, 4, display_width, 0, 0);
|
||||
assert(edge_runs[0].anchor == 0 && edge_runs[1].anchor == 0);
|
||||
assert(edge_runs[2].anchor == 384 && edge_runs[3].anchor == 384);
|
||||
|
||||
WsUiGroupItem dense[] = {{4, 8, 16, 100, 16}, {5, 340, 16, 100, 16}};
|
||||
ws_ui_group_assign(dense, 2, display_width, 1);
|
||||
ws_ui_group_assign(dense, 2, display_width, 1, 0);
|
||||
assert(dense[0].anchor == 192 && dense[1].anchor == 192);
|
||||
|
||||
/* A digit drawn on top of its background box is ONE element even though
|
||||
@@ -62,7 +62,7 @@ int main(void) {
|
||||
{12, 204, 60, 191, 8, 0, 0}, /* label stacked directly above it */
|
||||
{11, 205, 24, 10, 16, 0, 0}, /* top row, same columns */
|
||||
};
|
||||
ws_ui_group_assign(stacked, 4, display_width, 0);
|
||||
ws_ui_group_assign(stacked, 4, display_width, 0, 0);
|
||||
assert(stacked[0].root == stacked[1].root);
|
||||
/* One scanline of seam between the label and the box is still one
|
||||
* element — this is the WipEout 3 lap readout that flew apart. */
|
||||
@@ -82,7 +82,7 @@ int main(void) {
|
||||
{0xb938, 306,112, 236, 6, 0, 0}, /* meter bar, gap 18, rows meet */
|
||||
{0xf3e1, 418, 16, 236, 8, 0, 0}, /* bar end cap */
|
||||
};
|
||||
ws_ui_group_assign(submitted, 4, 508, 0);
|
||||
ws_ui_group_assign(submitted, 4, 508, 0, 0);
|
||||
assert(submitted[1].root == submitted[2].root); /* readout joins meters */
|
||||
assert(submitted[2].root == submitted[3].root);
|
||||
assert(submitted[0].root != submitted[1].root); /* left stays separate */
|
||||
@@ -96,9 +96,32 @@ int main(void) {
|
||||
{0x1111, 10, 4, 10, 4, 0, 0}, /* unrelated, breaks adjacency */
|
||||
{0xb938, 306,112, 236, 6, 0, 0},
|
||||
};
|
||||
ws_ui_group_assign(apart, 3, 508, 0);
|
||||
ws_ui_group_assign(apart, 3, 508, 0, 0);
|
||||
assert(apart[0].root != apart[2].root);
|
||||
|
||||
/* in_place: each run squashes about its own centre. Spider-Man's compass
|
||||
* is two ring quads at [381,482] around a GTE-projected arrow the UI
|
||||
* correction never moves; an edge anchor (512) pulled the ring ~70 px
|
||||
* off the arrow at 32:9. The run keeps one shared anchor, so its two
|
||||
* halves still meet. */
|
||||
WsUiGroupItem compass[] = {
|
||||
{0x5d9e901c, 381, 51, 184, 40, 0, 0},
|
||||
{0x5d9e901c, 431, 51, 184, 40, 0, 0},
|
||||
{0x2f754f62, 23, 26, 63, 12, 0, 0}, /* top-left health readout */
|
||||
};
|
||||
ws_ui_group_assign(compass, 3, 512, 0, 0);
|
||||
assert(compass[0].anchor == 512 && compass[1].anchor == 512);
|
||||
assert(compass[2].anchor == 0);
|
||||
ws_ui_group_assign(compass, 3, 512, 0, 1);
|
||||
assert(compass[0].anchor == 431 && compass[1].anchor == 431);
|
||||
assert(compass[0].root == compass[1].root);
|
||||
assert(compass[2].anchor == 23 + 26 / 2);
|
||||
assert(scale_about(381 + 51, compass[0].anchor, 9, 32) ==
|
||||
scale_about(431, compass[1].anchor, 9, 32));
|
||||
/* A dense 2D menu still centres every item, whatever the anchor mode. */
|
||||
ws_ui_group_assign(dense, 2, display_width, 1, 1);
|
||||
assert(dense[0].anchor == 192 && dense[1].anchor == 192);
|
||||
|
||||
assert(ws_ui_anchor_for_bounds(8, 32, display_width) == 0);
|
||||
assert(ws_ui_anchor_for_bounds(344, 32, display_width) == 384);
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user