Add opt-in native-wide presentation hold for retained loading scenes

This commit is contained in:
Matthew Stanley
2026-09-13 19:11:21 -07:00
parent 9037e7bd23
commit e3beb365ba
6 changed files with 94 additions and 2 deletions
+22
View File
@@ -602,3 +602,25 @@ off, so the default presentation is unchanged: letterbox and pillarbox margins
remain black. Enabling the feature without choosing artwork is also a no-op.
The package supplies only the declaration and trusted plugin selection; archives
still cannot load native code.
### Retained-scene loading presentation (native-wide opt-in)
A trusted game plugin can register
`psx_mod_set_retained_scene_predicate(predicate)` from `mod_plugins.h` when the
game keeps displaying its previous framebuffer while loading. The cheap, pure
emulation-thread callback must return nonzero only while that same scene is
retained. It must not call presentation APIs recursively. Passing NULL removes
the opt-in. Without registration, existing presentation behavior is unchanged.
In native-wide mode this holds the previous wide/4:3 classification even if a
game-state flag or absent GTE activity would normally classify loading as 2D.
It does not force menus wide, stretch artwork, change guest rendering or memory,
or override the FMV veto. Release the predicate when a new scene replaces the
retained image, so ordinary classification resumes. Crash's experimental
adaptive feature uses its pending level transition and draw-skip globals for
this; these game-specific addresses do not belong in the framework.
GPU reset and savestate restore discard this host-only history. A save loaded
directly into a frozen loading frame cannot recreate wide reveal strips absent
from the canonical saved framebuffer. `ws_scene_hold_test` covers long holds,
menu release, retained 4:3 scenes, FMV priority and timeline reset.
+3
View File
@@ -23,6 +23,9 @@ psxrecomp_add_runtime_target(psx-runtime
# (docs/ENHANCEMENTS.md G1.10). Titles get their <exe>_pgxp the same way.
if(BUILD_TESTING)
add_executable(ws_scene_hold_test tests/test_ws_scene_hold.c)
target_include_directories(ws_scene_hold_test PRIVATE include)
add_test(NAME ws_scene_hold_test COMMAND ws_scene_hold_test)
add_executable(mod_texture_banks_test tests/test_mod_texture_banks.c src/mod_texture_banks.c)
target_include_directories(mod_texture_banks_test PRIVATE include)
add_test(NAME mod_texture_banks_test COMMAND mod_texture_banks_test)
+10
View File
@@ -104,6 +104,16 @@ uint32_t psx_mod_display_width(void);
/* Height companion to psx_mod_display_width(); same conventions. */
uint32_t psx_mod_display_height(void);
/* Opt-in presentation hold for a game that retains its previous framebuffer
* while loading. The pure, cheap emulation-thread predicate returns nonzero
* only while that SAME scene remains displayed; no GPU/API recursion allowed.
* Native-wide retains its prior wide/4:3 classification, never stretches art
* and never overrides FMV. NULL removes the opt-in. Host history is discarded
* on GPU reset/savestate restore, so loading a frozen scene cannot recreate
* missing widescreen strips. This does not change guest rendering or memory. */
typedef int (*PSXModRetainedScenePredicate)(void);
void psx_mod_set_retained_scene_predicate(PSXModRetainedScenePredicate predicate);
/*
* Read the committed value of one of this package's declared options, as the
* player left it in the launcher (or the manifest default when untouched).
+19
View File
@@ -0,0 +1,19 @@
#pragma once
/* Host presentation history, not guest hardware state. Never serialize it:
* a restored canonical framebuffer cannot reconstruct discarded wide strips. */
typedef struct WsSceneHold { int valid, native_43; } WsSceneHold;
static inline void ws_scene_hold_reset(WsSceneHold* state) {
state->valid = 0; state->native_43 = 1;
}
static inline int ws_scene_hold_classify(WsSceneHold* state, int hold,
int native_43, int fmv) {
/* FMV always wins, and cannot seed a later wide retained scene. */
if (fmv) {
state->valid = 1; state->native_43 = 1;
return 1;
}
if (hold && state->valid) return state->native_43;
state->valid = 1; state->native_43 = native_43 != 0;
return state->native_43;
}
+17 -2
View File
@@ -27,6 +27,8 @@
#include "event_ring.h"
#include "color_lut.h"
#include "mod_runtime.h"
#include "mod_plugins.h"
#include "ws_scene_hold.h"
#include "sio.h"
#include "ws_cull_detect.h"
#include "ws_aspect_cone_math.h"
@@ -374,17 +376,28 @@ static int ws_2d_only_scene(void) {
static uint32_t s_ws_fmv_frame_cache = 0xFFFFFFFFu;
static int s_ws_fmv_cached = 0;
static PSXModRetainedScenePredicate s_ws_retained_scene_predicate;
static WsSceneHold s_ws_scene_hold;
void psx_mod_set_retained_scene_predicate(PSXModRetainedScenePredicate predicate) {
s_ws_retained_scene_predicate = predicate;
ws_scene_hold_reset(&s_ws_scene_hold);
}
int gpu_ws_present_native_43(void) {
if (!ws_engaged()) return 0;
if (!ws_game_mode()) return 1; /* full-2D screen */
if (ws_2d_only_scene()) return 1; /* 2D-only gameplay scene */
int native_43 = !ws_game_mode() || ws_2d_only_scene();
int hold_enabled = ws_mode == 2 && s_ws_retained_scene_predicate != NULL;
if (!hold_enabled && native_43) return 1; /* unchanged default */
uint32_t f = (uint32_t)s_frame_count;
if (f != s_ws_fmv_frame_cache) {
s_ws_fmv_frame_cache = f;
GpuDisplayInfo di; gpu_get_display_info(&di);
s_ws_fmv_cached = di.depth24 || mdec_recently_active(WS_FMV_HYSTERESIS);
}
if (hold_enabled)
return ws_scene_hold_classify(&s_ws_scene_hold,
s_ws_retained_scene_predicate(), native_43, s_ws_fmv_cached);
return s_ws_fmv_cached;
}
@@ -2853,6 +2866,7 @@ static void gpu_reset_state(int clear_vram) {
gpustat_poll_count = 0;
s_ws_fmv_frame_cache = 0xFFFFFFFFu;
s_ws_fmv_cached = 0;
ws_scene_hold_reset(&s_ws_scene_hold);
s_d24_upload_x1 = 0;
s_d24_present_hold = 0;
s_d24_prev_disp_h = 0;
@@ -6123,6 +6137,7 @@ int gpu_snapshot_read(const uint8_t *p, uint32_t len) {
if (len != gpu_snapshot_bytes()) return 0;
pst_r_init(&r, p, len);
if (!gpu_snap_parse(&r)) return 0;
ws_scene_hold_reset(&s_ws_scene_hold);
ws_hud_anchor_clear(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_hud_anchor_clear(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_repeat_rect_tag_clear(ws_repeat_rect_tags);
+23
View File
@@ -0,0 +1,23 @@
#include "ws_scene_hold.h"
#undef NDEBUG /* the Release test target must execute its assertions */
#include <assert.h>
#include <stdio.h>
int main(void) {
WsSceneHold s;
ws_scene_hold_reset(&s);
assert(ws_scene_hold_classify(&s,0,0,0)==0); /* world */
for (int i=0;i<300;++i)
assert(ws_scene_hold_classify(&s,1,1,0)==0); /* no GTE during load */
assert(ws_scene_hold_classify(&s,0,1,0)==1); /* actual menu replaces it */
assert(ws_scene_hold_classify(&s,1,0,0)==1); /* never force a 2D scene wide */
assert(ws_scene_hold_classify(&s,0,0,0)==0);
assert(ws_scene_hold_classify(&s,1,0,1)==1); /* FMV veto */
assert(ws_scene_hold_classify(&s,1,0,0)==1);
assert(ws_scene_hold_classify(&s,0,0,0)==0);
ws_scene_hold_reset(&s); /* cannot reuse unrelated pre-restore margins */
assert(ws_scene_hold_classify(&s,1,1,0)==1);
assert(ws_scene_hold_classify(&s,0,0,0)==0);
puts("PASS retained scene, menu release, FMV veto and timeline reset");
return 0;
}