Support adaptive screen masks and native effect presentation

This commit is contained in:
Matthew Stanley
2026-09-20 12:01:32 -07:00
parent 86af4b94e2
commit 39ee7d7940
6 changed files with 113 additions and 1 deletions
+3
View File
@@ -288,6 +288,9 @@ if(BUILD_TESTING)
add_executable(ws_repeat_rect_test tests/test_ws_repeat_rect.c)
target_include_directories(ws_repeat_rect_test PRIVATE include)
add_test(NAME ws_repeat_rect_test COMMAND ws_repeat_rect_test)
add_executable(ws_screen_mask_test tests/test_ws_screen_mask.c)
target_include_directories(ws_screen_mask_test PRIVATE include)
add_test(NAME ws_screen_mask_test COMMAND ws_screen_mask_test)
add_executable(mod_gpu_dma_aperture_test
tests/test_mod_gpu_dma_aperture.c)
+8
View File
@@ -393,6 +393,14 @@ void gpu_ws_tag_black_reveal_rect(uint32_t prim);
* reveal margins only. The caller verifies the composite's source period.
* Original UVs, texel density, and canonical VRAM writes are unchanged. */
void gpu_ws_tag_repeat_rect(uint32_t prim, int32_t period);
/* Title-identified flat screen mask: extend its off-screen vertical boundary
* to the adaptive edge, preserving the interior opening and canonical image.
* Packet guarded, cleared on reset/load, inert outside native-wide gameplay. */
void gpu_ws_tag_screen_mask_quad(uint32_t prim);
/* Pure emulation-thread predicate for a known native-only effect/scene.
* Nonzero forces native 4:3; zero defers. No GPU calls or guest writes inside.
* Host configuration survives reset/load; classification reads live state. */
void gpu_ws_set_native_scene_predicate(int (*predicate)(void));
/* Targeted alternative for sprite-heavy 2D games: corner-anchor only primitives
* whose ordering-table packet lives in the configured half-open RAM range. */
void gpu_ws_set_nw_left_hud_packet_range(uint32_t lo, uint32_t hi);
+27
View File
@@ -0,0 +1,27 @@
#ifndef PSXRECOMP_WS_SCREEN_MASK_H
#define PSXRECOMP_WS_SCREEN_MASK_H
#include <stdint.h>
/* Extend only an off-screen vertical boundary of a title-identified mask.
* The interior/slanted light boundary stays at its original coordinates.
* Result is wholly outside canonical VRAM; no triangle size limit applies. */
typedef struct { int x, y, w, h; } WsScreenMaskBand;
static inline int ws_screen_mask_band(const int32_t x[4], const int32_t y[4],
int width, int height, int margin,
WsScreenMaskBand *out) {
if (!out || width <= 0 || height <= 0 || margin <= 0 ||
y[0] != y[1] || y[2] != y[3] || y[0] != 0 || y[2] != height)
return 0;
if (x[0] == x[2] && x[0] <= 0 && x[1] > x[0] && x[3] > x[0] &&
x[0] > -margin) {
*out = (WsScreenMaskBand){-margin, 0, margin + x[0], height};
return 1;
}
if (x[1] == x[3] && x[1] >= width && x[0] < x[1] && x[2] < x[1] &&
x[1] < width + margin) {
*out = (WsScreenMaskBand){x[1], 0, width + margin - x[1], height};
return 1;
}
return 0;
}
#endif
+38
View File
@@ -40,6 +40,7 @@
#include "ws_prepass_guard.h"
#include "ws_hud_anchor.h"
#include "ws_repeat_rect.h"
#include "ws_screen_mask.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -176,6 +177,7 @@ typedef struct { uint32_t key; uint32_t stamp; int32_t anchor_x; } WsTag;
static WsTag ws_tags[WS_TAG_BUCKETS];
static WsHudAnchorTag ws_hud_anchor_tags[WS_HUD_ANCHOR_TABLE_SIZE];
static WsHudAnchorTag ws_reveal_clear_tags[WS_HUD_ANCHOR_TABLE_SIZE];
static WsHudAnchorTag ws_screen_mask_tags[WS_HUD_ANCHOR_TABLE_SIZE];
static WsRepeatRectTag ws_repeat_rect_tags[WS_REPEAT_RECT_TAG_TABLE_SIZE];
static uint32_t ws_last_tag_stamp = (uint32_t)-1000; /* frame of newest tag */
static uint32_t ws_last_3d_stamp = (uint32_t)-1000; /* frame of newest shaded prim (diagnostic) */
@@ -407,6 +409,7 @@ static void ws_reset_scene_history(void) {
static uint32_t s_ws_fmv_frame_cache = 0xFFFFFFFFu;
static int s_ws_fmv_cached = 0;
static PSXModRetainedScenePredicate s_ws_retained_scene_predicate;
static int (*s_ws_native_scene_predicate)(void);
static WsSceneHold s_ws_scene_hold;
static uint32_t ws_display_origin(void);
@@ -415,8 +418,14 @@ void psx_mod_set_retained_scene_predicate(PSXModRetainedScenePredicate predicate
ws_scene_hold_reset(&s_ws_scene_hold);
}
void gpu_ws_set_native_scene_predicate(int (*predicate)(void)) {
s_ws_native_scene_predicate = predicate;
}
int gpu_ws_present_native_43(void) {
if (!ws_engaged()) return 0;
if (ws_mode == 2 && s_ws_native_scene_predicate && s_ws_native_scene_predicate())
return 1;
int game_mode = ws_game_mode();
if (!game_mode) ws_scene_latch.confirmed = 0;
int native_43 = !game_mode || ws_2d_only_scene();
@@ -2147,6 +2156,18 @@ void gpu_ws_tag_repeat_rect(uint32_t prim, int32_t period) {
period, &guard, (uint32_t)s_frame_count);
}
void gpu_ws_tag_screen_mask_quad(uint32_t prim) {
if (!ws_native_wide_configured() || (prim & 3u) || prim > UINT32_MAX - 4u)
return;
uint32_t words[12], count = 0;
if (!ws_hud_command_words(prim + 4u, words, &count) || count != 5u ||
((words[0] >> 24) & 0xfdu) != 0x28u) return;
WsPrepassPacketGuard guard = ws_prepass_packet_guard(words, count);
ws_hud_anchor_insert(ws_screen_mask_tags, WS_HUD_ANCHOR_TABLE_SIZE,
(prim + 4u) & 0x1ffffcu, 0, &guard,
(uint32_t)s_frame_count);
}
static int ws_nw_explicit_hud_delta(int32_t *out_delta) {
if (out_delta) *out_delta = 0;
if (!ws_native_wide_active() || gp0_cmd_source_addr == 0xFFFFFFFFu)
@@ -3066,6 +3087,7 @@ static void gpu_reset_state(int clear_vram) {
gp0_cmd_source_addr = 0xFFFFFFFFu;
ws_hud_anchor_clear(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_hud_anchor_clear(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_hud_anchor_clear(ws_screen_mask_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_repeat_rect_tag_clear(ws_repeat_rect_tags);
polyline_color = 0;
polyline_prev_x = polyline_prev_y = 0;
@@ -4064,6 +4086,21 @@ static void gp0_exec_mono_quad(void) {
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
if (rej_a && rej_b) return;
/* Each added band is outside the original screen and executes in the
* mask's own OT position/blend mode. Canonical VRAM clips it away. */
if (ws_native_wide_active() && gp0_cmd_source_addr != UINT32_MAX &&
ws_hud_anchor_lookup(ws_screen_mask_tags, WS_HUD_ANCHOR_TABLE_SIZE,
gp0_cmd_source_addr & 0x1ffffcu, gp0_cmd_buf, 5u,
(uint32_t)s_frame_count, NULL)) {
WsScreenMaskBand band;
if (ws_screen_mask_band(vx, vy, ws_disp_w(), ws_disp_h(),
draw_area_wide_x_margin(), &band)) {
gr_set_semi_transparency(semi_trans, (int)semi_transparency);
gr_draw_flat_rect(band.x + draw_offset_x, band.y + draw_offset_y,
band.w, band.h, color);
}
}
/* Full-screen filters are commonly encoded as an axis-aligned quad. Drawing
* a semi-transparent quad as two independent triangles blends their shared
* diagonal twice; render the equivalent rectangle once to avoid that seam.
@@ -6467,6 +6504,7 @@ int gpu_snapshot_read(const uint8_t *p, uint32_t len) {
ws_scene_hold_reset(&s_ws_scene_hold);
ws_hud_anchor_clear(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_hud_anchor_clear(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_hud_anchor_clear(ws_screen_mask_tags, WS_HUD_ANCHOR_TABLE_SIZE);
ws_repeat_rect_tag_clear(ws_repeat_rect_tags);
/* Sync renderer clip/scissor to restored GP0(E3/E4); vars alone leave GL
* on a stale draw area after savestate load. */
+29
View File
@@ -0,0 +1,29 @@
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <assert.h>
#include "ws_screen_mask.h"
#include "ws_hud_anchor.h"
int main(void) {
int32_t x[4]={-96,160,-96,68},y[4]={0,0,240,240};
WsScreenMaskBand b;
for(int margin=0;margin<16000;margin+=53) {
int result=ws_screen_mask_band(x,y,320,240,margin,&b);
assert(result==(margin>96));
if(result) assert(b.x==-margin && b.x+b.w==-96 && b.y==0 && b.h==240);
}
x[0]=160;x[1]=416;x[2]=308;x[3]=416;
assert(ws_screen_mask_band(x,y,320,240,694,&b));
assert(b.x==416 && b.w==598);
x[3]=415; assert(!ws_screen_mask_band(x,y,320,240,694,&b));
x[3]=416;y[3]=239;assert(!ws_screen_mask_band(x,y,320,240,694,&b));
/* Same packet address with different geometry/colour must lose its tag. */
WsHudAnchorTag tags[WS_HUD_ANCHOR_TABLE_SIZE]={0};
uint32_t words[5]={0x2a7b7b7b,0x0000ffa0,0xa0,0xf0ffa0,0xf00044};
WsPrepassPacketGuard guard=ws_prepass_packet_guard(words,5);
ws_hud_anchor_insert(tags,WS_HUD_ANCHOR_TABLE_SIZE,0xa68b4,0,&guard,20);
assert(ws_hud_anchor_lookup(tags,WS_HUD_ANCHOR_TABLE_SIZE,0xa68b4,words,5,20,0));
words[2]++;assert(!ws_hud_anchor_lookup(tags,WS_HUD_ANCHOR_TABLE_SIZE,0xa68b4,words,5,20,0));
words[2]--;ws_hud_anchor_clear(tags,WS_HUD_ANCHOR_TABLE_SIZE);
assert(!ws_hud_anchor_lookup(tags,WS_HUD_ANCHOR_TABLE_SIZE,0xa68b4,words,5,20,0));
}
+8 -1
View File
@@ -5,11 +5,12 @@
uint64_t s_frame_count;
static uint32_t state_word;
static int world, calls, fmv, hold;
static int world, calls, fmv, hold, native_effect;
uint32_t psx_read_word(uint32_t address) { (void)address; return state_word; }
int mdec_recently_active(uint32_t frames) { (void)frames; return fmv; }
static int world_scene(void) { ++calls; return world; }
static int retained_scene(void) { return hold; }
static int native_scene(void) { return native_effect; }
static int native_43(void) { ++s_frame_count; return gpu_ws_present_native_43(); }
int main(void) {
@@ -38,6 +39,12 @@ int main(void) {
assert(native_43() == 1); /* 24-bit FMV veto */
display_depth = 0;
assert(native_43() == 0);
gpu_ws_set_native_scene_predicate(native_scene);
native_effect=1; assert(native_43()==1); /* Native attack overrides world. */
native_effect=0; assert(native_43()==0); /* Recovery restores adaptive. */
native_effect=1; ws_mode=0; assert(native_43()==0); /* Off stays inert. */
ws_mode=2; gpu_ws_set_native_scene_predicate(NULL);
assert(native_43()==0); /* No policy left behind after unregister. */
psx_mod_set_retained_scene_predicate(retained_scene);
hold = 0;
assert(native_43() == 0);