mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
Widescreen HUD size: proportional option shrinks the auto-UI HUD beyond 16:9
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -140,6 +140,10 @@ auto_ui_anchor = "edges" # "edges" (default): each run pins to
|
||||
# its left/centre/right third.
|
||||
# "in_place": each run squashes about
|
||||
# its own centre.
|
||||
auto_ui_size = "proportional" # "original" (default): the HUD keeps
|
||||
# the display height's scale.
|
||||
# "proportional": beyond 16:9 it shrinks
|
||||
# by sqrt((16:9) / aspect).
|
||||
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
|
||||
@@ -204,6 +208,16 @@ 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`.
|
||||
|
||||
The HUD is proportion-corrected horizontally but keeps the display height's
|
||||
scale, so on a very wide window it reads large against the much wider view.
|
||||
`auto_ui_size = "proportional"` leaves it unchanged up to 16:9 and shrinks it
|
||||
by sqrt((16:9) / aspect) beyond (0.87 at 21:9, 0.71 at 32:9), on both axes:
|
||||
each run about its horizontal anchor and its vertical anchor (its top edge in
|
||||
the upper half of the display, its bottom edge in the lower half), scaling
|
||||
both edges of rectangles and sprites so neighbouring parts stay butted. A mod
|
||||
may switch it per session with `psx_mod_set_widescreen_hud_size()`.
|
||||
Projection widescreen only (native-wide does not squash the HUD).
|
||||
|
||||
**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
|
||||
|
||||
@@ -1673,6 +1673,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
bool ws_hud_sprt_squash = false;
|
||||
bool ws_auto_ui_squash = false;
|
||||
bool ws_auto_ui_in_place = false;
|
||||
bool ws_auto_ui_proportional = false;
|
||||
bool ws_full_2d = false;
|
||||
bool ws_gte_game_mode = false;
|
||||
bool ws_precise_nclip = false;
|
||||
@@ -1836,6 +1837,15 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
"{}: [widescreen] auto_ui_anchor must be \"edges\" or "
|
||||
"\"in_place\", got \"{}\"", config_path.string(), anchor));
|
||||
}
|
||||
if (ws.contains("auto_ui_size")) {
|
||||
const auto size = toml::find<std::string>(ws, "auto_ui_size");
|
||||
if (size == "proportional")
|
||||
ws_auto_ui_proportional = true;
|
||||
else if (size != "original")
|
||||
throw std::runtime_error(fmt::format(
|
||||
"{}: [widescreen] auto_ui_size must be \"original\" or "
|
||||
"\"proportional\", got \"{}\"", config_path.string(), size));
|
||||
}
|
||||
if (ws.contains("full_2d"))
|
||||
ws_full_2d = toml::find<bool>(ws, "full_2d");
|
||||
if (ws.contains("gte_game_mode"))
|
||||
@@ -2480,6 +2490,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
/*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,
|
||||
/*ws_auto_ui_proportional*/ ws_auto_ui_proportional,
|
||||
/*data_shard_funcs*/ data_shard_funcs,
|
||||
/*mod_function_entry_funcs*/ mod_function_entry_funcs,
|
||||
/*mod_instruction_sites*/ mod_instruction_sites,
|
||||
|
||||
@@ -963,6 +963,11 @@ struct GameConfig {
|
||||
// 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;
|
||||
// auto_ui_size = "proportional": beyond 16:9 the auto-UI HUD shrinks by
|
||||
// sqrt((16:9) / aspect) about each widget's anchor, so a wide window
|
||||
// does not show a 4:3-height HUD across a much wider view. "original"
|
||||
// (default) keeps the HUD at the display height's scale.
|
||||
bool ws_auto_ui_proportional = false;
|
||||
|
||||
// [data_shards] funcs: functions that get the memoized pure-function
|
||||
// replay entry/return hooks (psx_datashard_enter/psx_datashard_ret).
|
||||
|
||||
@@ -212,6 +212,9 @@ 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] auto_ui_size: 1 = proportional (the auto-UI HUD shrinks by
|
||||
* sqrt((16:9) / aspect) beyond 16:9), 0 = original. */
|
||||
void gpu_ws_set_auto_ui_proportional(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. */
|
||||
|
||||
@@ -393,6 +393,15 @@ int psx_mod_set_fixed_display_aspect(uint32_t numerator,
|
||||
*/
|
||||
int psx_mod_set_adaptive_display_aspect(uint32_t max_numerator,
|
||||
uint32_t max_denominator);
|
||||
/*
|
||||
* HUD size for the auto-UI widescreen HUD ([widescreen] auto_ui_squash):
|
||||
* 0 keeps it at the display height's scale ("original"), 1 makes it
|
||||
* proportional (unchanged up to 16:9, shrinking by sqrt((16:9) / aspect)
|
||||
* beyond, about each widget's anchors). Overrides game.toml
|
||||
* [widescreen] auto_ui_size for the session; every session start restores the
|
||||
* title's setting before activation.
|
||||
*/
|
||||
int psx_mod_set_widescreen_hud_size(int proportional);
|
||||
/*
|
||||
* Set the wall-clock cadence of simulated guest VBlanks. A value of zero
|
||||
* removes frontend pacing; 60 and higher request that many native guest
|
||||
|
||||
+143
-62
@@ -46,6 +46,8 @@
|
||||
#include "ws_screen_mask.h"
|
||||
#include "ws_radial_screen_mask.h"
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -140,6 +142,10 @@ typedef struct {
|
||||
/* A triangle or a rotated quad: UI only as part of a widget it lies
|
||||
* inside (ws_ui_prepass_admit_enclosed), never on its own. */
|
||||
uint8_t enclosed_only;
|
||||
/* Vertical anchor of the item's run (auto_ui_size proportional): the
|
||||
* run's top edge in the upper half of the display, its bottom edge in
|
||||
* the lower half, so a shrunken widget keeps hugging its screen edge. */
|
||||
int32_t anchor_y;
|
||||
WsPrepassPacketGuard packet_guard;
|
||||
} WsUiPrepassItem;
|
||||
static WsUiPrepassItem ws_ui_prepass[WS_UI_PREPASS_MAX];
|
||||
@@ -245,6 +251,11 @@ 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;
|
||||
}
|
||||
/* [widescreen] auto_ui_size (gpu.c ws_auto_ui_size). */
|
||||
static int ws_auto_ui_proportional = 0;
|
||||
void gpu_ws_set_auto_ui_proportional(int on) {
|
||||
ws_auto_ui_proportional = on ? 1 : 0;
|
||||
}
|
||||
|
||||
void gpu_ws_set_auto_ui_squash(int on) {
|
||||
ws_auto_ui_squash = on ? 1 : 0;
|
||||
@@ -2841,10 +2852,10 @@ static void ws_ui_prepass_invalidate_stale(void) {
|
||||
ws_ui_reject.stale++;
|
||||
}
|
||||
|
||||
static int ws_auto_ui_anchor(int32_t *out_anchor) {
|
||||
static const WsUiPrepassItem *ws_auto_ui_item(void) {
|
||||
if (!ws_auto_ui_squash || !ws_active() ||
|
||||
gp0_cmd_source_addr == 0xFFFFFFFFu)
|
||||
return 0;
|
||||
return NULL;
|
||||
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) continue;
|
||||
@@ -2852,13 +2863,38 @@ static int ws_auto_ui_anchor(int32_t *out_anchor) {
|
||||
gp0_cmd_buf,
|
||||
(uint32_t)gp0_words_needed)) {
|
||||
ws_ui_prepass_invalidate_stale();
|
||||
return 0;
|
||||
return NULL;
|
||||
}
|
||||
if (out_anchor) *out_anchor = ws_ui_prepass[i].group.anchor;
|
||||
ws_auto_ui_candidate_count++;
|
||||
return 1;
|
||||
return &ws_ui_prepass[i];
|
||||
}
|
||||
return 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ws_auto_ui_anchor(int32_t *out_anchor) {
|
||||
const WsUiPrepassItem *it = ws_auto_ui_item();
|
||||
if (!it) return 0;
|
||||
if (out_anchor) *out_anchor = it->group.anchor;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* auto_ui_size = "proportional": the HUD keeps its authored size up to 16:9
|
||||
* and shrinks by sqrt((16:9) / aspect) beyond it (0.87 at 21:9, 0.71 at
|
||||
* 32:9), on both axes about the widget's anchors. The squash factor
|
||||
* xnum/xden is (4:3) / aspect, so (16:9) / aspect = (4/3) * xnum/xden.
|
||||
* Projection widescreen only: native-wide does not squash the HUD. */
|
||||
static double ws_auto_ui_size(void) {
|
||||
if (!ws_auto_ui_proportional || ws_mode == 2 || ws_xden <= 0) return 1.0;
|
||||
const double r = (4.0 / 3.0) * (double)ws_xnum / (double)ws_xden;
|
||||
return r >= 1.0 ? 1.0 : sqrt(r);
|
||||
}
|
||||
static int32_t ws_ui_x(int32_t x, int32_t ax, double s) {
|
||||
if (s >= 1.0) return ws_scale_about(x, ax);
|
||||
return ax + (int32_t)lround((double)(x - ax) * ws_xnum / ws_xden * s);
|
||||
}
|
||||
static int32_t ws_ui_y(int32_t y, int32_t ay, double s) {
|
||||
if (s >= 1.0) return y;
|
||||
return ay + (int32_t)lround((double)(y - ay) * s);
|
||||
}
|
||||
|
||||
static int ws_shared_ui_packet(void) {
|
||||
@@ -2924,21 +2960,24 @@ static int ws_axis_aligned_quad(const int32_t vx[4], const int32_t vy[4]) {
|
||||
/* A widget part that is not an axis-aligned rectangle (a triangle, a rotated
|
||||
* 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) {
|
||||
static int ws_auto_ui_transform_part(int32_t *vx, int32_t *vy, 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;
|
||||
for (int i = 0; i < n; i++)
|
||||
vx[i] = ws_scale_about(vx[i], anchor) + ws_shared_ui_delta();
|
||||
const WsUiPrepassItem *it = ws_auto_ui_item();
|
||||
if (!it) return 0;
|
||||
const double s = ws_auto_ui_size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
vx[i] = ws_ui_x(vx[i], it->group.anchor, s) + ws_shared_ui_delta();
|
||||
vy[i] = ws_ui_y(vy[i], it->anchor_y, s);
|
||||
}
|
||||
ws_auto_ui_transform_count++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws_auto_ui_transform_quad(int32_t vx[4], const int32_t vy[4]) {
|
||||
static int ws_auto_ui_transform_quad(int32_t vx[4], 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);
|
||||
if (!ws_axis_aligned_quad(vx, vy)) return ws_auto_ui_transform_part(vx, vy, 4);
|
||||
|
||||
int32_t min_x = vx[0], max_x = vx[0], min_y = vy[0], max_y = vy[0];
|
||||
for (int i = 1; i < 4; i++) {
|
||||
@@ -2953,16 +2992,19 @@ static int ws_auto_ui_transform_quad(int32_t vx[4], const int32_t vy[4]) {
|
||||
(width > W / 2 && height > H / 4))
|
||||
return 0;
|
||||
|
||||
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) + ws_shared_ui_delta();
|
||||
const WsUiPrepassItem *it = ws_auto_ui_item();
|
||||
if (!it) return 0;
|
||||
const double s = ws_auto_ui_size();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vx[i] = ws_ui_x(vx[i], it->group.anchor, s) + ws_shared_ui_delta();
|
||||
vy[i] = ws_ui_y(vy[i], it->anchor_y, s);
|
||||
}
|
||||
ws_auto_ui_transform_count++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws_auto_ui_transform_rect(int32_t *x, int32_t y, int *w, int h) {
|
||||
if (!x || !w || *w <= 0) return 0;
|
||||
static int ws_auto_ui_transform_rect(int32_t *x, int32_t *y, int *w, int *h) {
|
||||
if (!x || !y || !w || !h || *w <= 0) return 0;
|
||||
int32_t explicit_anchor;
|
||||
if (ws_projection_hud_anchor(&explicit_anchor)) {
|
||||
*x = ws_scale_about(*x, explicit_anchor);
|
||||
@@ -2972,13 +3014,26 @@ static int ws_auto_ui_transform_rect(int32_t *x, int32_t y, int *w, int h) {
|
||||
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) ||
|
||||
(*w > W / 2 && h > H / 4))
|
||||
if ((*x <= X && *x + *w >= X + W && *y <= 0 && *y + *h >= H) ||
|
||||
(*w > W / 2 && *h > H / 4))
|
||||
return 0;
|
||||
int32_t anchor;
|
||||
if (!ws_auto_ui_anchor(&anchor)) return 0;
|
||||
*x = ws_scale_about(*x, anchor) + ws_shared_ui_delta();
|
||||
*w = (int)ws_scale_len(*w);
|
||||
const WsUiPrepassItem *it = ws_auto_ui_item();
|
||||
if (!it) return 0;
|
||||
const double s = ws_auto_ui_size();
|
||||
if (s >= 1.0) {
|
||||
*x = ws_scale_about(*x, it->group.anchor) + ws_shared_ui_delta();
|
||||
*w = (int)ws_scale_len(*w);
|
||||
} else {
|
||||
/* Scale both edges so neighbouring parts of a widget stay butted. */
|
||||
const int32_t x0 = ws_ui_x(*x, it->group.anchor, s);
|
||||
const int32_t x1 = ws_ui_x(*x + *w, it->group.anchor, s);
|
||||
const int32_t y0 = ws_ui_y(*y, it->anchor_y, s);
|
||||
const int32_t y1 = ws_ui_y(*y + *h, it->anchor_y, s);
|
||||
*x = x0 + ws_shared_ui_delta();
|
||||
*w = x1 - x0 < 1 ? 1 : (int)(x1 - x0);
|
||||
*y = y0;
|
||||
*h = y1 - y0 < 1 ? 1 : (int)(y1 - y0);
|
||||
}
|
||||
ws_auto_ui_transform_count++;
|
||||
return 1;
|
||||
}
|
||||
@@ -2986,7 +3041,7 @@ static int ws_auto_ui_transform_rect(int32_t *x, int32_t y, int *w, int h) {
|
||||
/* Shared transform for fixed-size textured sprites (8x8 / 16x16 / 1x1 dot):
|
||||
* squash *x0 in place (around the tagged anchor, else the HUD pivot) and
|
||||
* return the squashed draw width, or 0 = no change. */
|
||||
static int ws_sprt_fixed_transform(int32_t *x0, int32_t y0, int w) {
|
||||
static int ws_sprt_fixed_transform(int32_t *x0, int32_t *y0, int w, int *h) {
|
||||
if (!ws_active()) return 0;
|
||||
int32_t ax;
|
||||
if (ws_tagged_anchor(&ax)) {
|
||||
@@ -2994,7 +3049,7 @@ static int ws_sprt_fixed_transform(int32_t *x0, int32_t y0, int w) {
|
||||
return (int)ws_scale_len(w);
|
||||
}
|
||||
int auto_w = w;
|
||||
if (ws_auto_ui_transform_rect(x0, y0, &auto_w, w))
|
||||
if (ws_auto_ui_transform_rect(x0, y0, &auto_w, h))
|
||||
return auto_w;
|
||||
if (ws_hud_sprt) {
|
||||
*x0 = ws_scale_about(*x0, ws_hud_pivot(*x0, w));
|
||||
@@ -4715,7 +4770,7 @@ static void gp0_exec_mono_tri(void) {
|
||||
parse_vertex(gp0_cmd_buf[1 + i], &vx[i], &vy[i]);
|
||||
}
|
||||
if (gpu_triangle_rejected(vx, vy, 0, 1, 2)) return;
|
||||
ws_auto_ui_transform_part(vx, 3);
|
||||
ws_auto_ui_transform_part(vx, vy, 3);
|
||||
ws_nw_hud_shift_vertices(vx, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vx[i] += draw_offset_x;
|
||||
@@ -4856,7 +4911,7 @@ static void gp0_exec_shaded_tri(void) {
|
||||
parse_vertex(gp0_cmd_buf[1 + i * 2], &vx[i], &vy[i]);
|
||||
}
|
||||
if (gpu_triangle_rejected(vx, vy, 0, 1, 2)) return;
|
||||
ws_auto_ui_transform_part(vx, 3);
|
||||
ws_auto_ui_transform_part(vx, vy, 3);
|
||||
ws_nw_hud_shift_vertices(vx, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vx[i] += draw_offset_x;
|
||||
@@ -5023,7 +5078,7 @@ static void gp0_exec_textured_tri(void) {
|
||||
uint16_t tpage = tpage_word & 0x1FF;
|
||||
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
|
||||
if (gpu_triangle_rejected(vx, vy, 0, 1, 2)) return;
|
||||
ws_auto_ui_transform_part(vx, 3);
|
||||
ws_auto_ui_transform_part(vx, vy, 3);
|
||||
|
||||
ws_nw_hud_shift_vertices(vx, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -5171,7 +5226,7 @@ static void gp0_exec_shaded_textured_tri(void) {
|
||||
uint16_t tpage = tpage_word & 0x1FF;
|
||||
set_tpage_from_poly(tpage_word); /* latches even for size-rejected polys */
|
||||
if (gpu_triangle_rejected(vx, vy, 0, 1, 2)) return;
|
||||
ws_auto_ui_transform_part(vx, 3);
|
||||
ws_auto_ui_transform_part(vx, vy, 3);
|
||||
|
||||
ws_nw_hud_shift_vertices(vx, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -5350,9 +5405,11 @@ static void gp0_exec_mono_rect(void) {
|
||||
* behind in open screen. Untouched when the prepass did not admit this
|
||||
* primitive, and rects never carry GTE output. */
|
||||
if (ws_active() && w > 0) {
|
||||
int corrected_w = w;
|
||||
if (ws_auto_ui_transform_rect(&x0, y0, &corrected_w, h))
|
||||
int corrected_w = w, corrected_h = h;
|
||||
if (ws_auto_ui_transform_rect(&x0, &y0, &corrected_w, &corrected_h)) {
|
||||
w = corrected_w;
|
||||
h = corrected_h;
|
||||
}
|
||||
}
|
||||
x0 += ws_nw_hud_shift(x0, w); /* native-wide HUD corner re-anchor (no-op else) */
|
||||
x0 += draw_offset_x; y0 += draw_offset_y;
|
||||
@@ -5408,7 +5465,7 @@ static void gp0_exec_textured_rect(void) {
|
||||
/* Widescreen: tagged sprite parts squash around their projected anchor;
|
||||
* untagged SPRTs are screen-space 2D (HUD/menus) and squash around the
|
||||
* display centre. Texels keep full coverage via the scaled-rect path. */
|
||||
int ws_w = 0;
|
||||
int ws_w = 0, ws_h = h;
|
||||
if (ws_active() && w > 0) {
|
||||
int32_t ws_ax;
|
||||
if (ws_tagged_anchor(&ws_ax)) {
|
||||
@@ -5416,7 +5473,7 @@ static void gp0_exec_textured_rect(void) {
|
||||
ws_w = (int)ws_scale_len(w);
|
||||
} else {
|
||||
int corrected_w = w;
|
||||
if (ws_auto_ui_transform_rect(&x0, y0, &corrected_w, h))
|
||||
if (ws_auto_ui_transform_rect(&x0, &y0, &corrected_w, &ws_h))
|
||||
ws_w = corrected_w;
|
||||
else if (ws_hud_sprt) {
|
||||
x0 = ws_scale_about(x0, ws_hud_pivot(x0, w));
|
||||
@@ -5427,13 +5484,11 @@ static void gp0_exec_textured_rect(void) {
|
||||
x0 += ws_nw_hud_shift(x0, w); /* native-wide HUD corner re-anchor (no-op else) */
|
||||
|
||||
x0 += draw_offset_x; y0 += draw_offset_y;
|
||||
{
|
||||
int dw = (ws_w && ws_w != w) ? ws_w : w;
|
||||
if (draw_area_out_rect(x0, y0, dw, h)) return;
|
||||
}
|
||||
const int dw = (ws_w && ws_w != w) ? ws_w : w;
|
||||
if (draw_area_out_rect(x0, y0, dw, ws_h)) return;
|
||||
setup_textured_draw(color24, semi_trans, raw_texture);
|
||||
if (ws_w && ws_w != w)
|
||||
gr_draw_textured_rect_scaled(x0, y0, ws_w, h, u0, v0, u0 + w, v0 + h,
|
||||
if (dw != w || ws_h != h)
|
||||
gr_draw_textured_rect_scaled(x0, y0, dw, ws_h, u0, v0, u0 + w, v0 + h,
|
||||
clut_x, clut_y, current_texpage());
|
||||
else
|
||||
gr_draw_textured_rect(x0, y0, w, h, u0, v0, clut_x, clut_y, current_texpage());
|
||||
@@ -5446,7 +5501,7 @@ static void gp0_exec_mono_dot(void) {
|
||||
uint16_t color = rgb888_to_rgb555(gp0_cmd_buf[0] & 0xFFFFFFu);
|
||||
int32_t x, y;
|
||||
parse_vertex(gp0_cmd_buf[1], &x, &y);
|
||||
ws_sprt_fixed_transform(&x, y, 1); /* auto_ui squash; no-op when unadmitted */
|
||||
{ int dot_h = 1; ws_sprt_fixed_transform(&x, &y, 1, &dot_h); } /* auto_ui squash; no-op when unadmitted */
|
||||
x += ws_nw_hud_shift(x, 1);
|
||||
x += draw_offset_x; y += draw_offset_y;
|
||||
if (draw_area_out_point(x, y)) return;
|
||||
@@ -5461,7 +5516,7 @@ static void gp0_exec_textured_dot(void) {
|
||||
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
|
||||
int32_t x0, y0;
|
||||
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
|
||||
(void)ws_sprt_fixed_transform(&x0, y0, 1); /* position only; 1px stays 1px */
|
||||
{ int dot_h = 1; (void)ws_sprt_fixed_transform(&x0, &y0, 1, &dot_h); } /* position only; 1px stays 1px */
|
||||
x0 += ws_nw_hud_shift(x0, 1);
|
||||
x0 += draw_offset_x; y0 += draw_offset_y;
|
||||
int u0 = gp0_cmd_buf[2] & 0xFF;
|
||||
@@ -5480,13 +5535,12 @@ static void gp0_exec_textured_8x8(void) {
|
||||
int raw_texture = (gp0_cmd_buf[0] >> 24) & 1;
|
||||
int32_t x0, y0;
|
||||
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
|
||||
int ws_w = ws_sprt_fixed_transform(&x0, y0, 8);
|
||||
int ws_h = 8;
|
||||
int ws_w = ws_sprt_fixed_transform(&x0, &y0, 8, &ws_h);
|
||||
x0 += ws_nw_hud_shift(x0, 8); /* native-wide HUD corner re-anchor (no-op else) */
|
||||
x0 += draw_offset_x; y0 += draw_offset_y;
|
||||
{
|
||||
int dw = (ws_w && ws_w != 8) ? ws_w : 8;
|
||||
if (draw_area_out_rect(x0, y0, dw, 8)) return;
|
||||
}
|
||||
const int dw = (ws_w && ws_w != 8) ? ws_w : 8;
|
||||
if (draw_area_out_rect(x0, y0, dw, ws_h)) return;
|
||||
int u0 = gp0_cmd_buf[2] & 0xFF;
|
||||
int v0 = (gp0_cmd_buf[2] >> 8) & 0xFF;
|
||||
uint16_t clut = (uint16_t)(gp0_cmd_buf[2] >> 16);
|
||||
@@ -5494,8 +5548,8 @@ static void gp0_exec_textured_8x8(void) {
|
||||
uint16_t clut_y = (clut >> 6) & 0x1FF;
|
||||
|
||||
setup_textured_draw(color24, semi_trans, raw_texture);
|
||||
if (ws_w && ws_w != 8)
|
||||
gr_draw_textured_rect_scaled(x0, y0, ws_w, 8, u0, v0, u0 + 8, v0 + 8,
|
||||
if (dw != 8 || ws_h != 8)
|
||||
gr_draw_textured_rect_scaled(x0, y0, dw, ws_h, u0, v0, u0 + 8, v0 + 8,
|
||||
clut_x, clut_y, current_texpage());
|
||||
else
|
||||
gr_draw_textured_rect(x0, y0, 8, 8, u0, v0, clut_x, clut_y, current_texpage());
|
||||
@@ -5507,14 +5561,15 @@ static void gp0_exec_mono_8x8(void) {
|
||||
uint16_t color = rgb888_to_rgb555(gp0_cmd_buf[0] & 0xFFFFFFu);
|
||||
int32_t x0, y0;
|
||||
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
|
||||
int ws_w = ws_sprt_fixed_transform(&x0, y0, 8);
|
||||
int ws_h = 8;
|
||||
int ws_w = ws_sprt_fixed_transform(&x0, &y0, 8, &ws_h);
|
||||
x0 += ws_nw_hud_shift(x0, 8);
|
||||
x0 += draw_offset_x; y0 += draw_offset_y;
|
||||
{
|
||||
int dw = (ws_w && ws_w != 8) ? ws_w : 8;
|
||||
if (draw_area_out_rect(x0, y0, dw, 8)) return;
|
||||
if (draw_area_out_rect(x0, y0, dw, ws_h)) return;
|
||||
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
|
||||
gr_draw_flat_rect(x0, y0, dw, 8, color);
|
||||
gr_draw_flat_rect(x0, y0, dw, ws_h, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5533,7 +5588,8 @@ static void gp0_exec_textured_16x16(void) {
|
||||
/* A pending full-width OT can outlive a resize back to 4:3. Keep its
|
||||
* central tiles; the normal draw area clips the extra columns. Dropping
|
||||
* every host packet here would blank that transition frame. */
|
||||
int ws_w = host_tile ? 0 : ws_sprt_fixed_transform(&x0, y0, 16);
|
||||
int ws_h = 16;
|
||||
int ws_w = host_tile ? 0 : ws_sprt_fixed_transform(&x0, &y0, 16, &ws_h);
|
||||
if (!host_tile) x0 += ws_nw_hud_shift(x0, 16);
|
||||
x0 += draw_offset_x; y0 += draw_offset_y;
|
||||
int u0 = gp0_cmd_buf[2] & 0xFF;
|
||||
@@ -5542,10 +5598,8 @@ static void gp0_exec_textured_16x16(void) {
|
||||
uint16_t clut_x = (clut & 0x3F) * 16;
|
||||
uint16_t clut_y = (clut >> 6) & 0x1FF;
|
||||
|
||||
{
|
||||
int dw = (ws_w && ws_w != 16) ? ws_w : 16;
|
||||
if (draw_area_out_rect(x0, y0, dw, 16)) return;
|
||||
}
|
||||
const int dw = (ws_w && ws_w != 16) ? ws_w : 16;
|
||||
if (draw_area_out_rect(x0, y0, dw, ws_h)) return;
|
||||
uint16_t bank = host_tile &&
|
||||
(psx_read_word(host_packet + 28u) & ~GPU_WS_BG2D_MIRROR_X) == GPU_WS_BG2D_BANK_PACKET_MAGIC ?
|
||||
(uint16_t)(psx_read_word(host_packet + 16u) >> 16) : 0;
|
||||
@@ -5555,8 +5609,8 @@ static void gp0_exec_textured_16x16(void) {
|
||||
if (host_tile && (psx_read_word(host_packet + 28u) & GPU_WS_BG2D_MIRROR_X))
|
||||
gr_draw_textured_rect_scaled(x0, y0, 16, 16, u0 + 15, v0, u0 - 1, v0 + 16,
|
||||
clut_x, clut_y, current_texpage());
|
||||
else if (ws_w && ws_w != 16)
|
||||
gr_draw_textured_rect_scaled(x0, y0, ws_w, 16, u0, v0, u0 + 16, v0 + 16,
|
||||
else if (dw != 16 || ws_h != 16)
|
||||
gr_draw_textured_rect_scaled(x0, y0, dw, ws_h, u0, v0, u0 + 16, v0 + 16,
|
||||
clut_x, clut_y, current_texpage());
|
||||
else
|
||||
gr_draw_textured_rect(x0, y0, 16, 16, u0, v0, clut_x, clut_y, current_texpage());
|
||||
@@ -6335,6 +6389,32 @@ void gpu_ws_prepass_linked_list(uint32_t start_addr) {
|
||||
ws_ui_prepass[i].group.anchor = group_origin + groups[i].anchor;
|
||||
ws_ui_prepass[i].group.root = groups[i].root;
|
||||
}
|
||||
/* Vertical anchor per run (auto_ui_size): its top edge in the upper half
|
||||
* of the display, its bottom edge in the lower half. */
|
||||
{
|
||||
static int32_t run_y0[WS_UI_PREPASS_MAX], run_y1[WS_UI_PREPASS_MAX];
|
||||
const int32_t half = ws_disp_h() / 2;
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++) {
|
||||
run_y0[i] = INT32_MAX;
|
||||
run_y1[i] = INT32_MIN;
|
||||
}
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++) {
|
||||
const uint32_t r = (uint32_t)groups[i].root;
|
||||
if (r >= ws_ui_prepass_count) continue;
|
||||
if (groups[i].y < run_y0[r]) run_y0[r] = groups[i].y;
|
||||
if (groups[i].y + groups[i].height > run_y1[r])
|
||||
run_y1[r] = groups[i].y + groups[i].height;
|
||||
}
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++) {
|
||||
const uint32_t r = (uint32_t)groups[i].root;
|
||||
int32_t y0 = groups[i].y, y1 = groups[i].y + groups[i].height;
|
||||
if (r < ws_ui_prepass_count && run_y0[r] <= run_y1[r]) {
|
||||
y0 = run_y0[r];
|
||||
y1 = run_y1[r];
|
||||
}
|
||||
ws_ui_prepass[i].anchor_y = (y0 + y1) / 2 < half ? y0 : y1;
|
||||
}
|
||||
}
|
||||
|
||||
/* A shared counter can comprise many glyphs and shadow packets, none of
|
||||
* which individually crosses the split. Classify the complete admitted
|
||||
@@ -6841,11 +6921,12 @@ static void gp0_dispatch_command(uint8_t opcode) {
|
||||
uint16_t color = rgb888_to_rgb555(gp0_cmd_buf[0] & 0xFFFFFFu);
|
||||
int32_t x0, y0;
|
||||
parse_vertex(gp0_cmd_buf[1], &x0, &y0);
|
||||
int ws_w = ws_sprt_fixed_transform(&x0, y0, 16);
|
||||
int ws_h = 16;
|
||||
int ws_w = ws_sprt_fixed_transform(&x0, &y0, 16, &ws_h);
|
||||
x0 += ws_nw_hud_shift(x0, 16);
|
||||
x0 += draw_offset_x; y0 += draw_offset_y;
|
||||
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
|
||||
gr_draw_flat_rect(x0, y0, (ws_w && ws_w != 16) ? ws_w : 16, 16,
|
||||
gr_draw_flat_rect(x0, y0, (ws_w && ws_w != 16) ? ws_w : 16, ws_h,
|
||||
color);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1510,6 +1510,15 @@ static bool netplay_local_viewport_native_wide(void) {
|
||||
return g_netplay_local_viewport == 1 && !g_netplay_local_viewport_projection;
|
||||
}
|
||||
|
||||
/* [widescreen] auto_ui_size from game.toml; a mod may override it per session
|
||||
* (psx_mod_set_widescreen_hud_size), reset_mod_owned_presentation restores. */
|
||||
static bool g_ws_auto_ui_proportional_cfg = false;
|
||||
extern "C" int psx_mod_set_widescreen_hud_size(int proportional) {
|
||||
if (proportional != 0 && proportional != 1) return 0;
|
||||
gpu_ws_set_auto_ui_proportional(proportional);
|
||||
return 1;
|
||||
}
|
||||
|
||||
extern "C" int psx_mod_set_fixed_display_aspect(
|
||||
uint32_t numerator, uint32_t denominator) {
|
||||
if (numerator == 0 || denominator == 0 ||
|
||||
@@ -1618,6 +1627,7 @@ static void reset_mod_owned_presentation(void) {
|
||||
g_ws_adaptive_view = false;
|
||||
g_ws_adaptive_max_num = 16;
|
||||
g_ws_adaptive_max_den = 9;
|
||||
gpu_ws_set_auto_ui_proportional(g_ws_auto_ui_proportional_cfg ? 1 : 0);
|
||||
psx_mod_set_world_scene_predicate(nullptr);
|
||||
gpu_ws_set_native_scene_predicate(nullptr);
|
||||
psx_mod_set_retained_scene_predicate(nullptr);
|
||||
@@ -14699,6 +14709,8 @@ int main(int argc, char** argv) {
|
||||
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);
|
||||
g_ws_auto_ui_proportional_cfg = gc.ws_auto_ui_proportional;
|
||||
gpu_ws_set_auto_ui_proportional(g_ws_auto_ui_proportional_cfg ? 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. */
|
||||
|
||||
@@ -81,20 +81,21 @@ static int g_test_precise_triangles = 0;
|
||||
static int g_test_textured_triangles = 0;
|
||||
|
||||
/* Bounding box of every flat/gouraud triangle drawn since the last reset. */
|
||||
static struct { int calls, min_x, max_x; } gpu_exec_triangles;
|
||||
static struct { int calls, min_x, max_x, min_y, max_y; } gpu_exec_triangles;
|
||||
static inline void gpu_exec_reset_triangles(void) {
|
||||
gpu_exec_triangles.calls = 0;
|
||||
gpu_exec_triangles.min_x = 1 << 30;
|
||||
gpu_exec_triangles.max_x = -(1 << 30);
|
||||
gpu_exec_triangles.min_x = gpu_exec_triangles.min_y = 1 << 30;
|
||||
gpu_exec_triangles.max_x = gpu_exec_triangles.max_y = -(1 << 30);
|
||||
}
|
||||
static inline void gpu_exec_record_triangle(int x0, int y0, int x1, int y1,
|
||||
int x2, int y2) {
|
||||
const int xs[3] = {x0, x1, x2};
|
||||
(void)y0; (void)y1; (void)y2;
|
||||
const int xs[3] = {x0, x1, x2}, ys[3] = {y0, y1, y2};
|
||||
gpu_exec_triangles.calls++;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (xs[i] < gpu_exec_triangles.min_x) gpu_exec_triangles.min_x = xs[i];
|
||||
if (xs[i] > gpu_exec_triangles.max_x) gpu_exec_triangles.max_x = xs[i];
|
||||
if (ys[i] < gpu_exec_triangles.min_y) gpu_exec_triangles.min_y = ys[i];
|
||||
if (ys[i] > gpu_exec_triangles.max_y) gpu_exec_triangles.max_y = ys[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ static void reset_state(int in_place) {
|
||||
ws_full_2d = 0;
|
||||
gpu_ws_set_auto_ui_squash(1);
|
||||
gpu_ws_set_auto_ui_in_place(in_place);
|
||||
gpu_ws_set_auto_ui_proportional(0);
|
||||
}
|
||||
|
||||
/* Prepass the list and execute the two fills; returns their drawn X span. */
|
||||
@@ -306,6 +307,36 @@ int main(void) {
|
||||
assert(gmax == ws_scale_about(120, left));
|
||||
assert(fmin == gmin && fmax == gmax);
|
||||
|
||||
/* auto_ui_size proportional at 32:9 (squash 3/8): the HUD shrinks by
|
||||
* sqrt((4/3) * 3/8) = sqrt(1/2) on both axes about its anchors. The run
|
||||
* spans y 20..36 in the top half, so it keeps its top edge (y 20). */
|
||||
reset_state(0);
|
||||
gpu_ws_set_auto_ui_proportional(1);
|
||||
build_hud(60, 128, 64, 120);
|
||||
run_fills(&gmin, &gmax, &fmin, &fmax);
|
||||
{
|
||||
const double s = sqrt(0.5);
|
||||
const int32_t left = ws_disp_x();
|
||||
assert(gmin == left + (int32_t)lround((64 - left) * 3.0 / 8.0 * s));
|
||||
assert(gmax == left + (int32_t)lround((120 - left) * 3.0 / 8.0 * s));
|
||||
gpu_exec_reset_triangles();
|
||||
load_packet(NODE_FILL, 8);
|
||||
gp0_exec_shaded_quad();
|
||||
assert(gpu_exec_triangles.min_y == 20 + (int32_t)lround(6 * s));
|
||||
assert(gpu_exec_triangles.max_y == 20 + (int32_t)lround(10 * s));
|
||||
}
|
||||
/* At 16:9 (squash 3/4) proportional is identical to original. */
|
||||
reset_state(0);
|
||||
ws_cfg_num = 16; ws_cfg_den = 9; ws_xnum = 3; ws_xden = 4;
|
||||
gpu_ws_set_auto_ui_proportional(1);
|
||||
build_hud(60, 128, 64, 120);
|
||||
run_fills(&gmin, &gmax, &fmin, &fmax);
|
||||
assert(gmin == ws_scale_about(64, ws_disp_x()));
|
||||
gpu_exec_reset_triangles();
|
||||
load_packet(NODE_FILL, 8);
|
||||
gp0_exec_shaded_quad();
|
||||
assert(gpu_exec_triangles.min_y == 26 && gpu_exec_triangles.max_y == 30);
|
||||
|
||||
/* A non-axis-aligned untextured quad reaching outside every widget is
|
||||
* world geometry, never UI (one inside a widget is a part of it: see the
|
||||
* needle below). */
|
||||
|
||||
Reference in New Issue
Block a user