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:
Matthew Stanley
2026-10-24 16:54:04 -07:00
parent effb75e63d
commit a3f36c8c02
5 changed files with 139 additions and 15 deletions
+14
View File
@@ -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.
+2
View File
@@ -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
View File
@@ -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)
+54
View File
@@ -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;
}