mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
widescreen: opt-in per-frame world-scene predicate
[widescreen] scene_predicate_per_frame (PSX_WS_SCENE_CACHE=0/1) evaluates a mod's world-scene predicate once per VBlank instead of on every primitive and GTE call; reset, savestate load and a new predicate invalidate it. Default off (the answer may trail a scene change by one frame). R4 (release, M4, race, macOS sample): gpu_ws_present_native_43 2.4 % -> 0.3 % of the emulation thread (r4_in_race 2.2 % -> 0). frame_fingerprint A/B, off vs on: 9000 frames from boot (intro, title, attract) and 3600 race frames from a savestate, every column identical, 0 dispatch misses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -517,3 +517,13 @@ To enable 21:9 later: widen the backdrop generation, then promote the launcher's
|
||||
2-state toggle to a 3-way Off / 16:9 / 21:9 (the `cycle_aspect` callback already
|
||||
cycles `aspect_index` 0/1/2 and is the scaffold). Full feasibility analysis in
|
||||
the session memory `native_wide_fov_autocull.md`.
|
||||
|
||||
## `scene_predicate_per_frame` (opt-in)
|
||||
|
||||
`[widescreen] scene_predicate_per_frame = true` evaluates a mod's world-scene
|
||||
predicate (`psx_mod_set_world_scene_predicate`) once per VBlank instead of on
|
||||
every primitive and GTE call. `PSX_WS_SCENE_CACHE=0/1` overrides it for one
|
||||
run. The answer can trail a scene change by up to one frame, so turn it on per
|
||||
title only after a `frame_fingerprint` A/B across scene transitions. In R4 the
|
||||
predicate was 2.4 % of the emulation thread in races and drops to 0.3 %. Reset
|
||||
and savestate load invalidate the cached value.
|
||||
|
||||
@@ -2752,6 +2752,12 @@ GameConfig load_game_config(const fs::path& config_path_in) {
|
||||
loaded.ws_cull_masked_reject_sites = std::move(ws_cull_masked_reject_sites);
|
||||
loaded.draw_distance_clamp_sites =
|
||||
parse_draw_distance_clamps(cfg, config_path);
|
||||
if (cfg.contains("widescreen")) {
|
||||
const toml::value& wsv = toml::find(cfg, "widescreen");
|
||||
if (wsv.contains("scene_predicate_per_frame"))
|
||||
loaded.ws_scene_predicate_per_frame =
|
||||
toml::find<bool>(wsv, "scene_predicate_per_frame");
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
|
||||
@@ -1443,6 +1443,9 @@ struct GameConfig {
|
||||
// [[draw_distance.clamp]] -- opt-in "keep far geometry" clamps. Empty by
|
||||
// default; inert until a mod switches them on; regen required.
|
||||
std::vector<DrawDistanceClampSite> draw_distance_clamp_sites;
|
||||
// [widescreen] scene_predicate_per_frame -- evaluate a mod's world-scene
|
||||
// predicate once per VBlank (gpu.c). Opt-in; check with a fingerprint A/B.
|
||||
bool ws_scene_predicate_per_frame = false;
|
||||
};
|
||||
|
||||
// Effective clip_edge_width: explicit value, else screen_w_imms[0], else 320.
|
||||
|
||||
@@ -184,6 +184,8 @@ void gpu_ws_configure(int aspect_num, int aspect_den,
|
||||
/* [widescreen] full_2d: opt a pure-2D sprite game into the widescreen present
|
||||
* path (treat every in-game frame as gameplay, since it never tags 3D prims). */
|
||||
void gpu_ws_set_full_2d(int on);
|
||||
/* Opt-in: a mod world-scene predicate is evaluated once per VBlank (gpu.c). */
|
||||
void gpu_ws_set_scene_predicate_per_frame(int on);
|
||||
/* Signed 16-bit camera/min/max; active is a nonzero byte. Requires bg2d hooks.
|
||||
* Zero addresses disable the feature. No guest memory is written. */
|
||||
void gpu_ws_set_view_anchor(uint32_t camera, uint32_t min, uint32_t max, uint32_t active);
|
||||
|
||||
+28
-1
@@ -421,11 +421,37 @@ static int ws_full_2d_mode(void) {
|
||||
return ws_full_2d || env;
|
||||
}
|
||||
static PSXModWorldScenePredicate s_ws_world_scene_predicate;
|
||||
static int s_ws_scene_cache_valid;
|
||||
void psx_mod_set_world_scene_predicate(PSXModWorldScenePredicate predicate) {
|
||||
s_ws_world_scene_predicate = predicate;
|
||||
s_ws_scene_cache_valid = 0;
|
||||
}
|
||||
/* Opt-in ([widescreen] scene_predicate_per_frame, PSX_WS_SCENE_CACHE=0/1):
|
||||
* evaluate a mod's world-scene predicate once per VBlank instead of on every
|
||||
* primitive and GTE call. R4's predicate reads several guest words through the
|
||||
* full memory bus, 2-4 % of the emulation thread in races. The answer can then
|
||||
* lag a scene change by up to one frame, which is why it is opt-in and must be
|
||||
* checked per title with a fingerprint A/B across scene transitions. Reset and
|
||||
* savestate load invalidate it (ws_reset_scene_history). */
|
||||
static int s_ws_scene_cache_cfg = 0;
|
||||
static uint32_t s_ws_scene_cache_frame = 0;
|
||||
static int s_ws_scene_cache_val = 0;
|
||||
void gpu_ws_set_scene_predicate_per_frame(int on) { s_ws_scene_cache_cfg = on ? 1 : 0; s_ws_scene_cache_valid = 0; }
|
||||
static int ws_scene_cache_on(void) {
|
||||
static int env = -2;
|
||||
if (env == -2) { const char *e = getenv("PSX_WS_SCENE_CACHE"); env = e ? (e[0] == '1') : -1; }
|
||||
return env >= 0 ? env : s_ws_scene_cache_cfg;
|
||||
}
|
||||
static int ws_mod_world_scene(void) {
|
||||
return ws_mode == 2 && s_ws_world_scene_predicate && s_ws_world_scene_predicate();
|
||||
if (!(ws_mode == 2 && s_ws_world_scene_predicate)) return 0;
|
||||
if (!ws_scene_cache_on()) return s_ws_world_scene_predicate();
|
||||
const uint32_t f = (uint32_t)s_frame_count;
|
||||
if (!s_ws_scene_cache_valid || s_ws_scene_cache_frame != f) {
|
||||
s_ws_scene_cache_val = s_ws_world_scene_predicate() ? 1 : 0;
|
||||
s_ws_scene_cache_frame = f;
|
||||
s_ws_scene_cache_valid = 1;
|
||||
}
|
||||
return s_ws_scene_cache_val;
|
||||
}
|
||||
static int ws_game_mode(void) {
|
||||
if (ws_mod_world_scene()) return 1;
|
||||
@@ -488,6 +514,7 @@ static int ws_2d_only_scene(void) {
|
||||
/* Host-derived presentation evidence is not serialized guest state. Relearn it
|
||||
* after reset/load instead of carrying the old scene's classification over. */
|
||||
static void ws_reset_scene_history(void) {
|
||||
s_ws_scene_cache_valid = 0;
|
||||
uint32_t expired = (uint32_t)s_frame_count - 1000u;
|
||||
memset(&ws_scene_latch, 0, sizeof ws_scene_latch);
|
||||
ws_bdx_reset(&ws_bdx);
|
||||
|
||||
@@ -16410,6 +16410,7 @@ int main(int argc, char** argv) {
|
||||
* widescreen present path. Applied to the GPU layer up front so the
|
||||
* ws engage at game entry classifies every frame as gameplay. */
|
||||
gpu_ws_set_full_2d(gc.ws_full_2d ? 1 : 0);
|
||||
gpu_ws_set_scene_predicate_per_frame(gc.ws_scene_predicate_per_frame ? 1 : 0);
|
||||
/* [widescreen.bg2d] engine tile-ring layout for the freshness
|
||||
* refill shared by the MMX5/MMX6 2D background hook. */
|
||||
gpu_ws_bg2d_configure(gc.ws_bg2d_layer_base,
|
||||
|
||||
Reference in New Issue
Block a user