Merge pull request #624 from RetroPortingToolKit/fix/pgxp-qualified-defaults-20261010

Use dataflow-only PGXP defaults automatically in hook builds
This commit is contained in:
Matt Stanley
2026-10-10 09:52:57 -07:00
committed by GitHub
11 changed files with 182 additions and 57 deletions
+18 -9
View File
@@ -882,10 +882,11 @@ the formal Crash/Tomba2 A/B.
### G1.11 — PGXP as a title's default: hook build, mod at session start, dataflow only, exact projection (2026-10-01)
Ridge Racer Type 4 turns PGXP on by default. Getting there took framework
changes that are all opt-in. With PGXP off, nothing changes for any title.
With PGXP on, two changes apply without a new option: the
`psx.enhancement.pgxp` mod now actually arms PGXP (item 2), and fully precise
axis-aligned quads take the triangle path (item 4).
changes within the elective PGXP enhancement. With PGXP off, nothing changes
for any title. With PGXP on, its correctness fixes apply without a new option:
the mod arms PGXP (item 2), hook builds default to dataflow-only precision
without a tolerance clamp (item 3), and fully precise axis-aligned quads take
the triangle path (item 4).
**What R4 got from G1.10 as shipped: nothing.** Base flavor, PGXP mod on:
0% dataflow hits, 3.6% position-cache hits, 0 of 282k textured triangles with
@@ -921,12 +922,18 @@ sub-vertex with RTPT.
own manifest (default-on override, netplay clear with and without the
reset, the off switch, options, env overrides). R4's `r4_pgxp_boot`
boots the built runtime and fails if main.cpp stops calling the arming.
3. **Two `[video]` keys** (game.toml; live over the TCP `pgxp` verb):
3. **Automatic hook-build defaults and `[video]` overrides** (game.toml;
live over the TCP `pgxp` verb). A runtime built with `PSX_PGXP=1` selects
`pgxp_tolerance = -1.0` and `pgxp_position_fallback = false` when those keys
are absent. The runtime and overlay ABI enforce the matching hook flavor.
Base builds keep 0.5 and true for their limited dataflow coverage. Explicit
title tuning wins independently for each key, including Ape's validated
clamp. PGXP remains elective; exact-projection shadows remain separate:
```toml
[video]
pgxp_tolerance = -1.0 # existing key: no clamp
pgxp_position_fallback = false # default true
pgxp_tolerance = -1.0 # hook-build default: no clamp
pgxp_position_fallback = false # hook-build default: dataflow only
pgxp_preserve_projection = true # default false
```
@@ -957,8 +964,10 @@ sub-vertex with RTPT.
(the G1.10 Ape result). At hook-build coverage the clamp is harmful: on
R4 it keeps 26% of vertices, leaves 52% of drawn triangles mixed (a
4K capture shows a hairline across the road) and shakes more than stock
(tables below). The default stays 0.5 because changing it would change
every title that already arms PGXP through `[video]`.
(tables below). The old universal default stayed 0.5 to avoid changing
every title that already arms PGXP through `[video]`. This historical
default is now retained only in base builds; hook builds automatically
disable the clamp unless a title supplies an explicit value.
4. **Fully precise axis-aligned textured quads skip the 2D rectangle
shortcut.** `gp0_exec_textured_quad` draws a quad whose integer vertices
and UVs form an axis-aligned rectangle with `gr_draw_textured_rect`, at the
+1 -1
View File
@@ -1 +1 @@
e7c8c626e955b89839c5dda1d9990857d87a0042a1cdc1071bbdc42b754a4146
85d8625c20a9c7936292a085c0b85d6899dfa1011d8e8754167cdf5dcedbf330
+2
View File
@@ -812,10 +812,12 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
if (video.contains("pgxp_tolerance")) {
rt.video_pgxp_tolerance =
toml_number(video, "pgxp_tolerance");
rt.video_pgxp_tolerance_set = true;
}
if (video.contains("pgxp_position_fallback")) {
rt.video_pgxp_position_fallback =
toml::find<bool>(video, "pgxp_position_fallback");
rt.video_pgxp_position_fallback_set = true;
}
if (video.contains("pgxp_preserve_projection")) {
rt.video_pgxp_preserve_projection =
+8 -6
View File
@@ -529,22 +529,24 @@ struct RuntimeConfig {
// pgxp_tolerance: reject a corrected vertex whose sub-pixel offset from
// the native integer position exceeds this many pixels (the truncation-
// agreement check already bounds offsets to < 1px; this narrows them
// further). Default 0.5 — user-validated on Ape Escape (2026-08-15):
// further). Unspecified: off in the PGXP hook flavor, 0.5 in base builds.
// An explicit title value wins — user-validated on Ape Escape (2026-08-15):
// unclamped, sparse hairline background-bleed seams appear where a
// corrected triangle borders an uncorrected one; at 0.5 the seams are
// gone and only sub-half-pixel misalignment remains. Negative disables
// the clamp. Live-tunable over TCP (pgxp verb).
double video_pgxp_tolerance = 0.5;
bool video_pgxp_tolerance_set = false;
// pgxp_position_fallback: let a vertex with no validated dataflow shadow
// take the fraction of the projection last cached at its integer screen
// position (the G1.4 exact table). Default true (unchanged behaviour).
// A title built with the PGXP hooks (psxrecomp_add_game_runtime PGXP)
// reaches near-total dataflow coverage, so for it the cache only hands
// unrelated fractions to CPU-built 2D polygons; such a title sets false
// ("dataflow only", the reference implementations' default).
// position (the G1.4 exact table). Unspecified: false in the PGXP hook
// flavor, true in base builds. The hook flavor tracks full-word copies;
// the cache can assign unrelated 3D fractions to CPU-built 2D polygons.
// An explicit title value overrides the runtime's automatic default.
// Live-tunable over TCP (pgxp verb). docs/ENHANCEMENTS.md G1.11.
bool video_pgxp_position_fallback = true;
bool video_pgxp_position_fallback_set = false;
// pgxp_preserve_projection: shadow the exact projection of each vertex
// (from the GTE's unshifted MACs and a true divide) instead of the GTE's
@@ -72,6 +72,9 @@ static void test_defaults_off() {
"geometry_correction defaults OFF (known to crack meshes)");
check(!gc.runtime.video_perspective_texturing,
"perspective_texturing defaults OFF (faithful floor; opt-in)");
check(!gc.runtime.video_pgxp_tolerance_set &&
!gc.runtime.video_pgxp_position_fallback_set,
"absent PGXP tuning delegates defaults to the runtime flavor");
fs::remove(p);
}
@@ -427,16 +430,15 @@ static void test_frame_generation() {
fs::remove(q);
}
/* docs/ENHANCEMENTS.md G1.11: the PGXP title keys. Defaults keep the
* historical behaviour (tolerance 0.5, position cache consulted, IR-path
* shadows); a title built with the hooks sets all three. */
/* PGXP title tuning retains base values here; presence flags distinguish
* explicit title overrides from the runtime's hook-flavor defaults. */
static void test_pgxp_title_keys() {
fs::path p = write_game_toml("psxrecomp_pgxp_keys_default.toml", "");
auto gc = PSXRecompV4::load_game_config(p);
check(gc.runtime.video_pgxp_tolerance == 0.5,
"pgxp_tolerance defaults to 0.5");
"pgxp_tolerance retains the base-build value before runtime resolution");
check(gc.runtime.video_pgxp_position_fallback,
"pgxp_position_fallback defaults ON (unchanged behaviour)");
"pgxp_position_fallback retains the base value before runtime resolution");
check(!gc.runtime.video_pgxp_preserve_projection,
"pgxp_preserve_projection defaults OFF (unchanged behaviour)");
check(!gc.runtime.video_pgxp_mod_only,
@@ -465,8 +467,9 @@ static void test_pgxp_title_keys() {
"pgxp_tolerance = 1\n"
"scanline_strength = 1\n");
gc = PSXRecompV4::load_game_config(p);
check(gc.runtime.video_pgxp_depth_threshold == 0.0 && gc.runtime.video_pgxp_tolerance == 1.0,
"integer literals load for number keys");
check(gc.runtime.video_pgxp_depth_threshold == 0.0 && gc.runtime.video_pgxp_tolerance == 1.0 &&
gc.runtime.video_pgxp_tolerance_set && !gc.runtime.video_pgxp_position_fallback_set,
"explicit tolerance alone leaves fallback on its automatic default");
fs::remove(p);
p = write_game_toml("psxrecomp_pgxp_render_bad.toml",
@@ -487,8 +490,10 @@ static void test_pgxp_title_keys() {
"pgxp_mod_only = true\n");
gc = PSXRecompV4::load_game_config(p);
check(gc.runtime.video_pgxp_mod_only, "pgxp_mod_only = true is honoured");
check(gc.runtime.video_pgxp_tolerance < 0.0,
"pgxp_tolerance = -1.0 disables the clamp");
check(gc.runtime.video_pgxp_tolerance < 0.0 &&
gc.runtime.video_pgxp_tolerance_set &&
gc.runtime.video_pgxp_position_fallback_set,
"explicit PGXP tuning is marked as an override");
check(!gc.runtime.video_pgxp_position_fallback,
"pgxp_position_fallback = false is honoured");
check(gc.runtime.video_pgxp_preserve_projection,
+31 -25
View File
@@ -730,32 +730,38 @@ if(BUILD_TESTING)
# real mod runtime, the real builtin plugin and the builtin's own
# manifest: default-on, netplay clear, the off switch, options, env
# overrides and [video] pgxp_mod_only.
add_executable(pgxp_session_test tests/test_pgxp_session.cpp
src/pgxp_session.cpp src/mod_builtin_pgxp.c src/pgxp.cpp
src/psx_ram_geometry.c
src/mod_runtime.cpp
src/mod_packages.cpp
src/mod_media.cpp
src/disc_path.cpp
src/cue_sheet.cpp
src/iso_reader.cpp
src/crc32.c
src/psx_sha256.c)
target_include_directories(pgxp_session_test PRIVATE
include
../recompiler/include
../recompiler/lib/toml11)
target_compile_definitions(pgxp_session_test PRIVATE
PSX_BUILTIN_PGXP_MANIFEST="${CMAKE_CURRENT_SOURCE_DIR}/../mods/builtin/packages/psx.enhancement.pgxp/1.0.0/manifest.toml")
target_link_libraries(pgxp_session_test PRIVATE chdr-static)
if(WIN32)
if(MSVC)
target_link_options(pgxp_session_test PRIVATE /STACK:16777216)
else()
target_link_options(pgxp_session_test PRIVATE -Wl,--stack,16777216)
# Run the same session fixture in both real compilation flavors.
foreach(_pgxp_session_test IN ITEMS pgxp_session_test pgxp_session_hooks_test)
add_executable(${_pgxp_session_test} tests/test_pgxp_session.cpp
src/pgxp_session.cpp src/mod_builtin_pgxp.c src/pgxp.cpp
src/psx_ram_geometry.c
src/mod_runtime.cpp
src/mod_packages.cpp
src/mod_media.cpp
src/disc_path.cpp
src/cue_sheet.cpp
src/iso_reader.cpp
src/crc32.c
src/psx_sha256.c)
target_include_directories(${_pgxp_session_test} PRIVATE
include
../recompiler/include
../recompiler/lib/toml11)
target_compile_definitions(${_pgxp_session_test} PRIVATE
PSX_BUILTIN_PGXP_MANIFEST="${CMAKE_CURRENT_SOURCE_DIR}/../mods/builtin/packages/psx.enhancement.pgxp/1.0.0/manifest.toml")
target_link_libraries(${_pgxp_session_test} PRIVATE chdr-static)
if(WIN32)
if(MSVC)
target_link_options(${_pgxp_session_test} PRIVATE /STACK:16777216)
else()
target_link_options(${_pgxp_session_test} PRIVATE -Wl,--stack,16777216)
endif()
endif()
endif()
add_test(NAME pgxp_session_test COMMAND pgxp_session_test)
add_test(NAME ${_pgxp_session_test} COMMAND ${_pgxp_session_test})
if(_pgxp_session_test STREQUAL "pgxp_session_hooks_test")
target_compile_definitions(${_pgxp_session_test} PRIVATE PSX_PGXP=1)
endif()
endforeach()
add_executable(psx_instr_cost_test tests/test_psx_instr_cost.cpp)
target_include_directories(psx_instr_cost_test PRIVATE include)
+4
View File
@@ -105,6 +105,10 @@ typedef struct PSXPgxpSessionConfig {
int video_cpu_mode;
float tolerance;
int position_fallback;
/* Set only for explicit [video] values. Otherwise the runtime chooses
* no clamp / dataflow-only in hook builds, 0.5 / cache in base builds. */
int tolerance_set;
int position_fallback_set;
int preserve_projection;
int mod_only;
int netplay; /* this session is a netplay match: precise culling off */
+9 -2
View File
@@ -1278,8 +1278,10 @@ static int g_video_geometry_correction = 0;
static int g_video_perspective_texturing = 0;
static int g_video_pgxp_cpu_mode = 0;
static float g_video_pgxp_tolerance = 0.5f;
/* docs/ENHANCEMENTS.md G1.11: dataflow-only and exact-projection shadows.
* Defaults keep the historical behaviour. game.toml [video] only. */
/* game.toml [video] tuning. Session arming selects dataflow-only/no-clamp
* defaults in hook builds unless the title supplied an explicit value. */
static int g_video_pgxp_tolerance_set = 0;
static int g_video_pgxp_position_fallback_set = 0;
static int g_video_pgxp_position_fallback = 1;
static int g_video_pgxp_preserve_projection = 0;
/* PGXP renderer features (G1.14); PSX_PGXP_DEPTH / _COLOR / _SEAM override. */
@@ -16202,6 +16204,9 @@ int main(int argc, char** argv) {
gc.runtime.video_perspective_texturing ? 1 : 0;
g_video_pgxp_cpu_mode = gc.runtime.video_pgxp_cpu_mode ? 1 : 0;
g_video_pgxp_tolerance = (float)gc.runtime.video_pgxp_tolerance;
g_video_pgxp_tolerance_set = gc.runtime.video_pgxp_tolerance_set;
g_video_pgxp_position_fallback_set =
gc.runtime.video_pgxp_position_fallback_set;
g_video_pgxp_position_fallback =
gc.runtime.video_pgxp_position_fallback ? 1 : 0;
g_video_pgxp_preserve_projection =
@@ -18604,6 +18609,8 @@ session_reboot:
pgxp_cfg.video_cpu_mode = g_video_pgxp_cpu_mode;
pgxp_cfg.tolerance = g_video_pgxp_tolerance;
pgxp_cfg.position_fallback = g_video_pgxp_position_fallback;
pgxp_cfg.tolerance_set = g_video_pgxp_tolerance_set;
pgxp_cfg.position_fallback_set = g_video_pgxp_position_fallback_set;
pgxp_cfg.preserve_projection = g_video_pgxp_preserve_projection;
pgxp_cfg.mod_only = g_video_pgxp_mod_only;
/* Precise culling changes guest NCLIP results from host-only shadows; a
+2 -1
View File
@@ -7,7 +7,8 @@
* (the faithful floor, docs/ENHANCEMENTS.md G1.9); enabling arms geometry and
* texture correction on the value-propagation engine (pgxp.cpp), cpu-mode and
* precise culling (G1.12) per the mod options. The title's other [video] PGXP
* keys (the tolerance clamp, default 0.5px) still apply.
* keys still apply. Unspecified tuning is dataflow-only/no-clamp in hook
* builds, or position-cache/0.5px in base builds (pgxp_session.cpp).
*
* Activation only RECORDS the request (pgxp_mod_request). It runs before the
* renderer setup in main.cpp, whose session arming (pgxp_session.cpp) takes
+15 -2
View File
@@ -34,8 +34,21 @@ extern "C" PSXPgxpSessionArm psx_pgxp_session_arm(
in.mod_enabled = pgxp_mod_request_take(&in.mod_cpu_mode, &in.mod_culling);
const PSXPgxpSessionArm arm = psx_pgxp_session_resolve(&in);
pgxp_set_tolerance(config->tolerance);
pgxp_set_position_fallback(config->position_fallback);
/* The hook flavor tracks the full-word dataflow. A coordinate cache can
* inject unrelated world fractions into HUD vertices, and a tolerance
* clamp discards valid shadows, mixing precise/native triangle corners.
* Base builds lack that coverage and retain their validated defaults.
* Neither default enables the optional PGXP enhancement itself. */
#if defined(PSX_PGXP) && PSX_PGXP
const float default_tolerance = -1.0f;
const int default_position_fallback = 0;
#else
const float default_tolerance = 0.5f;
const int default_position_fallback = 1;
#endif
pgxp_set_tolerance(config->tolerance_set ? config->tolerance : default_tolerance);
pgxp_set_position_fallback(config->position_fallback_set ?
config->position_fallback : default_position_fallback);
pgxp_set_preserve_projection(config->preserve_projection);
pgxp_set_culling(arm.culling);
gte_geometry_correction_set(arm.geometry);
+78 -2
View File
@@ -29,6 +29,7 @@
#include "cpu_state.h"
#include "gpu.h"
#include "gpu_hd_textures.h"
#include <array>
#include <cstdio>
@@ -76,11 +77,22 @@ static int g_failures = 0;
static int g_geometry = -1;
static int g_texture = -1;
extern "C" int psx_netplay_active(void) { return 0; }
/* No external HD pack is configured by the PGXP session fixture. */
extern "C" void gpu_hd_textures_shutdown(void) {}
extern "C" int gpu_hd_textures_configure(const char*, int, int, char*, size_t) { return 0; }
extern "C" int gpu_hd_textures_reload(char*, size_t) { return 0; }
extern "C" void gpu_hd_textures_set_dump_enabled(int) {}
extern "C" void gpu_hd_textures_get_diag(GpuHdTextureDiag* out) { *out = GpuHdTextureDiag{}; }
extern "C" void gte_geometry_correction_set(int enabled) { g_geometry = enabled; }
extern "C" void gpu_texture_correction_set(int enabled) { g_texture = enabled; }
/* pgxp.cpp's position-cache tier lives in gte.cpp. */
extern "C" int gte_geometry_correction_lookup(uint32_t, int32_t*, int32_t*) {
return 0;
static bool g_cached_hud_vertex = false;
extern "C" int gte_geometry_correction_lookup(uint32_t, int32_t* x, int32_t* y) {
if (!g_cached_hud_vertex) return 0;
*x = (160 << 16) + 0x4000;
*y = (80 << 16) + 0x4000;
return 1;
}
/* The session harness has no GTE backend; pre-draw probes must link as a no-op. */
@@ -219,6 +231,70 @@ int main(void) {
"PSX_PGXP_CPU_MODE", "PSX_PGXP_CULLING"})
test_unsetenv(v);
g_cfg = PSXPgxpSessionConfig{};
/* No title tuning: session arming must choose the build's defaults,
* including resetting values left by a previous session/debug toggle. */
pgxp_set_tolerance(2.0f);
pgxp_set_position_fallback(0);
CHECK(armed(session(), 0, 0, 0, 0));
#if defined(PSX_PGXP) && PSX_PGXP
CHECK(pgxp_tolerance() < 0.0f && pgxp_position_fallback() == 0);
#else
CHECK(pgxp_tolerance() == 0.5f && pgxp_position_fallback() == 1);
#endif
CHECK(pgxp_preserve_projection() == 0);
/* Run those defaults through the real engine: a proven world vertex
* retains its 0.75px fraction, while a HUD coordinate sharing its integer
* position cannot borrow a different projection from the cache. */
pgxp_set_enabled(1);
const uint32_t packed = (80u << 16) | 160u;
const uint32_t addr = 0x80100000u;
const int32_t precise_x = (160 << 16) + 0xc000;
pgxp_gte_push_sxy(precise_x, 80 << 16, 100, packed);
psx_pgxp_cop2(nullptr, (0x3au << 26) | (14u << 16), packed, addr);
int32_t x, y;
uint16_t z;
const int world = pgxp_get_precise_vertex(addr, packed, 160, 80, &x, &y, &z);
#if defined(PSX_PGXP) && PSX_PGXP
CHECK(world == PGXP_SRC_DATAFLOW && x == precise_x && z == 100);
#else
CHECK(world == PGXP_SRC_NATIVE && x == (160 << 16) && z == 100);
#endif
g_cached_hud_vertex = true;
const int hud = pgxp_get_precise_vertex(0xffffffffu, packed, 160, 80, &x, &y, &z);
#if defined(PSX_PGXP) && PSX_PGXP
CHECK(hud == PGXP_SRC_NATIVE && x == (160 << 16) && z == 0);
#else
CHECK(hud == PGXP_SRC_FALLBACK && x == (160 << 16) + 0x4000 && z == 0);
#endif
g_cached_hud_vertex = false;
pgxp_set_enabled(0);
/* Each explicit compatibility override is independent of the other. */
g_cfg.tolerance = 0.25f;
g_cfg.tolerance_set = 1;
CHECK(armed(session(), 0, 0, 0, 0));
CHECK(pgxp_tolerance() == 0.25f);
#if defined(PSX_PGXP) && PSX_PGXP
CHECK(pgxp_position_fallback() == 0);
#else
CHECK(pgxp_position_fallback() == 1);
#endif
g_cfg.tolerance_set = 0;
g_cfg.position_fallback = 1;
g_cfg.position_fallback_set = 1;
CHECK(armed(session(), 0, 0, 0, 0));
#if defined(PSX_PGXP) && PSX_PGXP
CHECK(pgxp_tolerance() < 0.0f);
#else
CHECK(pgxp_tolerance() == 0.5f);
#endif
CHECK(pgxp_position_fallback() == 1);
/* Explicit compatibility tuning wins even in the hook flavor. */
g_cfg.tolerance = 0.5f;
g_cfg.tolerance_set = 1;
g_cfg.position_fallback = 1;
g_cfg.position_fallback_set = 1;
CHECK(armed(session(), 0, 0, 0, 0));
CHECK(pgxp_tolerance() == 0.5f && pgxp_position_fallback() == 1);
g_cfg.tolerance = -1.0f;
g_cfg.position_fallback = 0;
g_cfg.preserve_projection = 1;