mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
8630d3a6f8
With every drawn vertex precise, a title can still show cracks where the game
culled a face on its integer winding. R4's road emitter runs NCLIP on each
far row and skips rows whose native height rounds to 0. Natively their
neighbours meet at the same integer y. With PGXP they sit at their exact y,
and the skipped row becomes a strip of sky across the road beyond the start
gantry, at 4K and in 2P. tri_mixed cannot see this: the missing face draws
no triangle.
Precise culling (DuckStation's "PGXP culling", minimal footprint):
- While geometry correction is armed, gte_nclip computes the exact
determinant of the three SXY register shadows (pgxp_gte_nclip_precise:
live, current words, the GPU's own truncation agreement and tolerance).
When its sign is nonzero and differs from the integer MAC0's, MAC0 takes
that sign, with the rounded exact doubled area (at least 1) as magnitude.
FLAG keeps the integer overflow bits.
- It changes guest-visible MAC0, so it is a `culling` option of
psx.enhancement.pgxp, false in the builtin, with no [video] key, so netplay
(which clears every mod) never runs it. It needs geometry correction.
It is held off in every pass compared against another execution: the
speculative and replay passes, and the whole overlay shadow diff
(psx_overlay_shadow_diff_active), interpreter pass included.
- Counters whenever geometry correction is armed: nclip_precise,
nclip_disagree (exact sign differs: the culled-face crack exposure),
nclip_corrected. TCP pgxp {"culling":0|1}; env PSX_PGXP_CULLING.
Session arming in one place (src/pgxp_session.cpp, psx_pgxp_session_arm):
main.cpp's renderer setup now calls the same code the test drives. The arming
takes the mod request (reads and clears it), so a session with an empty plan
cannot inherit an earlier session's request even if the reset were skipped.
pgxp_session_test now links the real mod runtime, the builtin plugin and the
builtin manifest, and covers:
- a default-on override;
- a netplay clear, with and without the reset;
- the off switch;
- the options;
- env overrides;
- pgxp_mod_only.
[video] pgxp_mod_only (game.toml, default false): for a title that ships
PGXP through the mod, the mod is the one switch. The [video]
geometry_correction / perspective_texturing / pgxp_cpu_mode values are not
applied, from game.toml or the player's settings.toml. The launcher also
hides its Perspective textures row (has_geometry_precision = 0). That row
showed "off" while a default-on mod corrected textures, and it could keep
texture correction on after the player switched the mod off.
geom_correction also reports texture_correction and the culling switch.
R4, Helter Skelter: 28.7% of NCLIPs disagree in sign. With culling, the
ladder is gone in same-frame 4K captures (1P straight and crest, 2P VS), and
the game emits about 24% more triangles. Its packet buffers peak at 60%.
fp_identity over 12000 frames: stock, PGXP off, and PGXP on with culling off
are IDENTICAL; with culling on, the RAM writes differ from the attract race
on, while cyc/mmio/mc agree throughout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
4.7 KiB
C
123 lines
4.7 KiB
C
#ifndef PSXRECOMP_PGXP_SESSION_H
|
|
#define PSXRECOMP_PGXP_SESSION_H
|
|
|
|
/*
|
|
* Which PGXP corrections a session arms (docs/ENHANCEMENTS.md G1.11/G1.12).
|
|
*
|
|
* main.cpp drives the whole lifecycle through the two functions at the end of
|
|
* this header, compiled from src/pgxp_session.cpp, which
|
|
* runtime/tests/test_pgxp_session.cpp links and drives the same way:
|
|
*
|
|
* reset_mod_owned_presentation() -> psx_pgxp_session_reset()
|
|
* mod_runtime_activate_plugins() -> psx.enhancement.pgxp's activation
|
|
* records pgxp_mod_request(...)
|
|
* renderer setup (session_reboot) -> psx_pgxp_session_arm(&config, ...)
|
|
*
|
|
* The arm TAKES the mod request (reads and clears it), so a request lives only
|
|
* from one session's activation to that session's arming. A later session
|
|
* with an empty plan -- netplay clears it, or the player disabled the mod --
|
|
* cannot inherit it, even if the reset were skipped.
|
|
*
|
|
* Inputs, in order of precedence:
|
|
* 1. env_*: the validation overrides PSX_GEOMETRY_CORRECTION,
|
|
* PSX_PERSPECTIVE_TEXTURING, PSX_PGXP_CPU_MODE and PSX_PGXP_CULLING (-1
|
|
* when unset, else 0 or 1). They win over everything, so an A/B run can
|
|
* switch PGXP off under an enabled mod.
|
|
* 2. video_* OR mod_*: the [video] baseline (game.toml, then settings.toml
|
|
* and the launcher) and the psx.enhancement.pgxp request this session's
|
|
* activation recorded. Either one arms a correction. With mod_only
|
|
* ([video] pgxp_mod_only, a title that ships PGXP through the mod) the
|
|
* baseline is ignored and the mod is the one switch.
|
|
*
|
|
* The mod arms both geometry and texture correction; its CPU-mode option adds
|
|
* tier-2 propagation and its culling option precise culling. Precise culling
|
|
* changes guest-visible NCLIP results, so it has no [video] key (netplay does
|
|
* not clear [video]); it needs geometry correction.
|
|
*/
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef struct PSXPgxpSessionInputs {
|
|
int video_geometry;
|
|
int video_texture;
|
|
int video_cpu_mode;
|
|
int env_geometry;
|
|
int env_texture;
|
|
int env_cpu_mode;
|
|
int env_culling;
|
|
int mod_enabled;
|
|
int mod_cpu_mode;
|
|
int mod_culling;
|
|
int mod_only;
|
|
} PSXPgxpSessionInputs;
|
|
|
|
typedef struct PSXPgxpSessionArm {
|
|
int geometry;
|
|
int texture;
|
|
int cpu_mode;
|
|
int culling;
|
|
} PSXPgxpSessionArm;
|
|
|
|
static inline int psx_pgxp_session_pick(int env, int baseline, int mod) {
|
|
if (env >= 0) return env ? 1 : 0;
|
|
return (baseline || mod) ? 1 : 0;
|
|
}
|
|
|
|
static inline PSXPgxpSessionArm psx_pgxp_session_resolve(
|
|
const PSXPgxpSessionInputs* in) {
|
|
PSXPgxpSessionArm arm;
|
|
const int use_video = in->mod_only ? 0 : 1;
|
|
arm.geometry = psx_pgxp_session_pick(in->env_geometry,
|
|
use_video && in->video_geometry,
|
|
in->mod_enabled);
|
|
arm.texture = psx_pgxp_session_pick(in->env_texture,
|
|
use_video && in->video_texture,
|
|
in->mod_enabled);
|
|
arm.cpu_mode = psx_pgxp_session_pick(in->env_cpu_mode,
|
|
use_video && in->video_cpu_mode,
|
|
in->mod_enabled && in->mod_cpu_mode);
|
|
arm.culling = arm.geometry &&
|
|
psx_pgxp_session_pick(in->env_culling, 0,
|
|
in->mod_enabled && in->mod_culling);
|
|
return arm;
|
|
}
|
|
|
|
/* Parse one of the env overrides: unset -> -1, "0" or empty -> 0, else 1
|
|
* (the rule the runtime has always used for them). */
|
|
static inline int psx_pgxp_session_env_flag(const char* value) {
|
|
if (!value) return -1;
|
|
return (*value && *value != '0') ? 1 : 0;
|
|
}
|
|
|
|
/* The [video] PGXP settings main.cpp hands the arming: the player's baseline
|
|
* (game.toml, then settings.toml and the launcher) and the title's tuning
|
|
* keys. */
|
|
typedef struct PSXPgxpSessionConfig {
|
|
int video_geometry;
|
|
int video_texture;
|
|
int video_cpu_mode;
|
|
float tolerance;
|
|
int position_fallback;
|
|
int preserve_projection;
|
|
int mod_only;
|
|
} PSXPgxpSessionConfig;
|
|
|
|
/* Session start, before plugin activation: forget any mod request. */
|
|
void psx_pgxp_session_reset(void);
|
|
|
|
/* The renderer setup's arming, once per session after activation: reads the
|
|
* env overrides, takes the mod request, resolves, and applies the result to
|
|
* the engine (gte_geometry_correction_set, gpu_texture_correction_set,
|
|
* pgxp_set_cpu_mode / _culling) along with the tuning keys. `used` (may be
|
|
* NULL) receives the inputs it resolved from. */
|
|
PSXPgxpSessionArm psx_pgxp_session_arm(const PSXPgxpSessionConfig* config,
|
|
PSXPgxpSessionInputs* used);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif
|