mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Scale widescreen cull margin to the display width; keep ratio sites on the 320 basis
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -583,3 +583,15 @@ 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`.
|
||||
|
||||
## Cull margin follows the display width (2026-10-24)
|
||||
|
||||
`psx_ws_x_margin()` on the squash path was `160 * (1/s - 1)`, correct only for
|
||||
320-pixel modes. The reveal is a fraction of the game's own half-width, so it
|
||||
is now `(W/2) * (1/s - 1)` with `W` the live horizontal display width (512 in
|
||||
Army Men's gameplay, 368 in Ape Escape): pixel-domain screen-X culls
|
||||
(`[widescreen.cull]`) get that margin. Ratio-style consumers (terrain angle,
|
||||
aspect cone, `cull_scale`, the gameplay-field scaler) read
|
||||
`psx_ws_x_margin_320()`, the same reveal on the 320 basis, and keep their
|
||||
behaviour in every mode. 320-wide titles are unchanged bit for bit.
|
||||
Test: `ws_cull_margin_width_test`.
|
||||
|
||||
@@ -1090,6 +1090,20 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
add_test(NAME ws_local_view_margin_test COMMAND ws_local_view_margin_test)
|
||||
|
||||
add_executable(ws_cull_margin_width_test
|
||||
tests/test_ws_cull_margin_width.c
|
||||
src/ws_ui_group.c
|
||||
src/psx_video_timing.c
|
||||
src/gpu_timeline.c)
|
||||
target_include_directories(ws_cull_margin_width_test PRIVATE include src)
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(ws_cull_margin_width_test PRIVATE
|
||||
-ffunction-sections -fdata-sections)
|
||||
target_link_options(ws_cull_margin_width_test PRIVATE
|
||||
$<IF:$<PLATFORM_ID:Darwin>,-Wl$<COMMA>-dead_strip,-Wl$<COMMA>--gc-sections>)
|
||||
endif()
|
||||
add_test(NAME ws_cull_margin_width_test COMMAND ws_cull_margin_width_test)
|
||||
|
||||
# Depth24 GP1(07h) present hold (#636): hold count, upload span and the
|
||||
# staged frame agree across windowed/headless/deferred presents, rollback
|
||||
# replay and disk loads; stale trailing FMV pixels never reach a frame.
|
||||
|
||||
@@ -316,6 +316,8 @@ int gpu_ws_present_native_43(void);
|
||||
/* Per-side X cull-margin (screen/world units) emitted into the game's draw-
|
||||
* cull immediates by the recompiler ([widescreen.cull]); 0 unless stretching. */
|
||||
int psx_ws_x_margin(void);
|
||||
/* The same reveal on the 320-pixel basis (ratio-style sites: angles, cones). */
|
||||
int psx_ws_x_margin_320(void);
|
||||
/* Netplay own-view mods: while on, psx_ws_x_margin() is 0 (the stock cull)
|
||||
* except inside a sandboxed local-view render (render_pass.c sets the scope). */
|
||||
void gpu_ws_set_local_view_only(int on);
|
||||
|
||||
+57
-15
@@ -716,6 +716,8 @@ int gpu_ws_netplay_local_viewport_width(void) {
|
||||
* unconditionally so a probe can sweep the cull margin live (0 = force 4:3
|
||||
* cull while still stretching; large = over-draw) at a fixed camera position.
|
||||
* -1 = normal computed margin. */
|
||||
static uint32_t gpu_h_display_width(void);
|
||||
static int ws_px_to_320(int px);
|
||||
static int ws_margin_override = -1;
|
||||
static int ws_cull_guard_pixels = 0;
|
||||
static int ws_activation_guard_pixels = 0;
|
||||
@@ -1169,7 +1171,7 @@ void gpu_ws_set_cull_scale_sites(const uint32_t *addresses,
|
||||
* (x < half*z/H) widens to the live view by scaling the bound by
|
||||
* (half + margin) / half. 64-bit intermediate; saturates to int32. */
|
||||
int32_t psx_ws_cull_scale(int32_t bound, int32_t half_extent) {
|
||||
const int32_t margin = psx_ws_x_margin();
|
||||
const int32_t margin = psx_ws_x_margin_320();
|
||||
if (margin <= 0 || half_extent <= 0) return bound;
|
||||
int64_t v = (int64_t)bound * (int64_t)(half_extent + margin) / half_extent;
|
||||
if (v > INT32_MAX) v = INT32_MAX;
|
||||
@@ -1225,7 +1227,7 @@ void gpu_ws_set_angle_sites(const uint32_t *addresses,
|
||||
|
||||
uint32_t psx_ws_angle_widen(uint32_t vanilla) {
|
||||
ws_angle_calls++;
|
||||
const int margin = psx_ws_x_margin();
|
||||
const int margin = psx_ws_x_margin_320();
|
||||
if (margin <= 0) {
|
||||
ws_angle_43_identity++;
|
||||
return vanilla;
|
||||
@@ -1448,7 +1450,7 @@ uint32_t psx_ws_aspect_cone_result(uint32_t site_address, uint32_t vanilla,
|
||||
|
||||
ws_aspect_cone_calls++;
|
||||
site->calls++;
|
||||
const int total_margin = psx_ws_x_margin();
|
||||
const int total_margin = psx_ws_x_margin_320();
|
||||
if (total_margin <= 0) {
|
||||
ws_aspect_cone_43_identity++;
|
||||
site->identity_43++;
|
||||
@@ -1473,8 +1475,8 @@ uint32_t psx_ws_aspect_cone_result(uint32_t site_address, uint32_t vanilla,
|
||||
}
|
||||
|
||||
const int visible_margin =
|
||||
(total_margin > ws_cull_guard_pixels)
|
||||
? total_margin - ws_cull_guard_pixels : 0;
|
||||
(total_margin > ws_px_to_320(ws_cull_guard_pixels))
|
||||
? total_margin - ws_px_to_320(ws_cull_guard_pixels) : 0;
|
||||
const int32_t fx =
|
||||
(int16_t)psx_read_half(ws_aspect_cone_forward_addr + 0u);
|
||||
const int32_t fz =
|
||||
@@ -1493,7 +1495,7 @@ uint32_t psx_ws_aspect_cone_result(uint32_t site_address, uint32_t vanilla,
|
||||
!in_visible && !in_guard && state->active &&
|
||||
psx_ws_aspect_cone_contains(
|
||||
x, z, y, fx, fz, fy, threshold,
|
||||
total_margin + (int)ws_aspect_cone_hysteresis_pixels);
|
||||
total_margin + ws_px_to_320((int)ws_aspect_cone_hysteresis_pixels));
|
||||
|
||||
if (!in_visible && !in_guard && !in_hysteresis) {
|
||||
state->active = 0;
|
||||
@@ -1546,6 +1548,18 @@ void gpu_ws_set_local_view_only(int on) { ws_local_view_only = on ? 1 : 0; }
|
||||
void gpu_ws_set_local_view_scope(int on) { ws_local_view_scope = on ? 1 : 0; }
|
||||
int gpu_ws_local_view_only(void) { return ws_local_view_only; }
|
||||
|
||||
/* Half of the game's current horizontal display width (160 for 320 modes). */
|
||||
static int ws_cull_half_width(void) {
|
||||
int src_w = 0;
|
||||
uint32_t w;
|
||||
if (ws_local_viewport_layout(NULL, &src_w, NULL, NULL))
|
||||
w = (uint32_t)src_w;
|
||||
else
|
||||
w = gpu_h_display_width();
|
||||
if (w < 64u) w = 320u;
|
||||
return (int)(w / 2u);
|
||||
}
|
||||
|
||||
int psx_ws_x_margin(void) {
|
||||
if (ws_local_view_only && !ws_local_view_scope) return 0;
|
||||
if (ws_margin_override >= 0) return ws_margin_override;
|
||||
@@ -1564,10 +1578,28 @@ int psx_ws_x_margin(void) {
|
||||
return ws_nw_configured_offset() * (ws_view_camera_addr ? 2 : 1)
|
||||
+ ws_cull_guard_pixels;
|
||||
if (!ws_active()) return 0;
|
||||
return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum
|
||||
/* The reveal is a fraction of the game's own half-width: a 512-wide mode
|
||||
* (Army Men in-game) hides 256*(1/s-1) px per side, not the 160*(1/s-1)
|
||||
* a 320 mode does. The screen-X compares live in that mode's pixels. */
|
||||
return (ws_cull_half_width() * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum
|
||||
+ ws_cull_guard_pixels;
|
||||
}
|
||||
|
||||
/* A pixel quantity of the current display mode on the 320-pixel basis. */
|
||||
static int ws_px_to_320(int px) {
|
||||
const int half = ws_cull_half_width();
|
||||
return half == 160 ? px : (int)(((int64_t)px * 160 + half / 2) / half);
|
||||
}
|
||||
|
||||
/* The same margin on the 320-pixel basis. Camera-space frustum angles, aspect
|
||||
* cones, scaled bounds and gameplay-field scalers are ratios authored against
|
||||
* a 160-pixel half-screen; they must not grow with the display mode. */
|
||||
int psx_ws_x_margin_320(void) {
|
||||
const int m = psx_ws_x_margin();
|
||||
const int half = ws_cull_half_width();
|
||||
return half == 160 ? m : (int)(((int64_t)m * 160 + half / 2) / half);
|
||||
}
|
||||
|
||||
int psx_ws_activation_margin(void) {
|
||||
const int margin = psx_ws_x_margin();
|
||||
return margin > 0 ? margin + ws_activation_guard_pixels : 0;
|
||||
@@ -1575,7 +1607,7 @@ int psx_ws_activation_margin(void) {
|
||||
|
||||
int32_t psx_ws_player_x_bound(int32_t vanilla)
|
||||
{
|
||||
const int margin = psx_ws_x_margin();
|
||||
const int margin = psx_ws_x_margin_320();
|
||||
if (margin <= 0) return vanilla;
|
||||
return (int32_t)(((int64_t)vanilla * (320 + 2 * margin)) / 320);
|
||||
}
|
||||
@@ -4536,12 +4568,10 @@ int gpu_display_is_depth24(void) {
|
||||
return (int)(display_depth & 1u);
|
||||
}
|
||||
|
||||
void gpu_get_display_info(GpuDisplayInfo* out) {
|
||||
out->display_x = display_area_x;
|
||||
out->display_y = display_area_y;
|
||||
out->depth24 = (int)(display_depth & 1u);
|
||||
out->disabled = (int)display_disabled;
|
||||
|
||||
/* Visible horizontal pixel width (GP1(08h) mode + GP1(06h) range). Shared by
|
||||
* gpu_get_display_info and the widescreen cull margin, which is called per
|
||||
* vertex and must not pay for the vertical layout. */
|
||||
static uint32_t gpu_h_display_width(void) {
|
||||
/* Dot-clock divider from GP1(08h) hres (PSX-SPX graphicsprocessingunitgpu,
|
||||
* "Horizontal Timings", Dotclocks: /10, /8, /7, /5, /4). */
|
||||
uint32_t cycles;
|
||||
@@ -4565,6 +4595,18 @@ void gpu_get_display_info(GpuDisplayInfo* out) {
|
||||
if (w == 0u) w = 4u;
|
||||
}
|
||||
|
||||
if (w > 640) w = 640;
|
||||
return w;
|
||||
}
|
||||
|
||||
void gpu_get_display_info(GpuDisplayInfo* out) {
|
||||
out->display_x = display_area_x;
|
||||
out->display_y = display_area_y;
|
||||
out->depth24 = (int)(display_depth & 1u);
|
||||
out->disabled = (int)display_disabled;
|
||||
|
||||
uint32_t w = gpu_h_display_width();
|
||||
|
||||
/* DuckStation GetFullDisplayResolution: clamp Y1/Y2 to the broadcast
|
||||
* active region before taking the difference. Unclamped Y2 past the
|
||||
* active end (common overscan programming) includes a flickering junk
|
||||
@@ -4590,7 +4632,7 @@ void gpu_get_display_info(GpuDisplayInfo* out) {
|
||||
* of the video — do not reintroduce it. Right-edge junk is a separate
|
||||
* present/filter issue, not a reason to shrink CRTC width. */
|
||||
|
||||
/* Clamp to sane maximums */
|
||||
/* Clamp to sane maximums (gpu_h_display_width clamps too) */
|
||||
if (w > 640) w = 640;
|
||||
if (out->depth24) {
|
||||
if (screen_h > PSX_DISPLAY_PRESENT_MAX_HEIGHT)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
/* The squash path reveals a fraction of the game's own half-width, so the
|
||||
* per-side cull margin follows the display mode: 320 px keeps the historical
|
||||
* 160*(1/s-1), a 512 px mode (Army Men in-game) hides 256*(1/s-1) px per side.
|
||||
* Ratio-style sites (angles, cones, scaled bounds) read the 320-basis margin and
|
||||
* never change with the mode. */
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "../src/gpu.c"
|
||||
#include "gpu_exec_stubs.inc"
|
||||
|
||||
static void set_mode(int hres, uint32_t x2) {
|
||||
hres1 = (uint32_t)hres; hres2 = 0; video_mode = 0;
|
||||
h_display_x1 = 0x260; h_display_x2 = x2;
|
||||
v_display_y1 = 0x10; v_display_y2 = 0x100;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
ws_mode = 1;
|
||||
ws_gte_game_mode_cfg = 1; /* game frame: squash is active */
|
||||
ws_last_gte_stamp = (uint32_t)s_frame_count;
|
||||
ws_cfg_num = 16; ws_cfg_den = 9;
|
||||
ws_xnum = 3; ws_xden = 4; /* 16:9 squash */
|
||||
|
||||
set_mode(1, 0xC60); /* 320 wide */
|
||||
assert(gpu_h_display_width() == 320);
|
||||
assert(psx_ws_x_margin() == 53);
|
||||
assert(psx_ws_x_margin_320() == 53);
|
||||
|
||||
set_mode(2, 0xC60); /* 512 wide */
|
||||
assert(gpu_h_display_width() == 512);
|
||||
assert(psx_ws_x_margin() == 85); /* 256 * (4/3 - 1) */
|
||||
assert(psx_ws_x_margin_320() == 53); /* ratio sites unchanged */
|
||||
assert(psx_ws_player_x_bound(160) == 160 * (320 + 2 * 53) / 320);
|
||||
|
||||
ws_xnum = 8; ws_xden = 15; /* 2.5:1 */
|
||||
assert(psx_ws_x_margin() == 224); /* 256 * (15/8 - 1) */
|
||||
assert(psx_ws_x_margin_320() == 140);
|
||||
|
||||
gpu_ws_set_cull_guard_pixels(32);
|
||||
assert(psx_ws_x_margin() == 256);
|
||||
gpu_ws_set_cull_guard_pixels(0);
|
||||
|
||||
ws_xnum = ws_xden = 1; /* true 4:3 stays identity */
|
||||
assert(psx_ws_x_margin() == 0);
|
||||
assert(psx_ws_x_margin_320() == 0);
|
||||
puts("ws_cull_margin_width_test: ok");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user