widescreen: auto_ui_squash corrects untextured HUD quads with their frames

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-10-01 11:19:45 -07:00
parent 85c88d269c
commit 4ddb7b3ca9
6 changed files with 503 additions and 299 deletions
+4 -2
View File
@@ -140,8 +140,10 @@ the RTPS preamble stores the anchor SXY to scratchpad `0x1F800070`.
`auto_ui_squash` is a runtime-only opt-in and does not require regenerated game
code. It applies only on the projection-and-stretch path: eligible
axis-aligned textured quads/rectangles in the front populated ordering-table
layer are grouped by texture and screen row before any command is transformed.
axis-aligned quads (textured, flat or gouraud) and rectangles in the front
populated ordering-table layer are grouped by texture and screen row before any
command is transformed. Untextured quads are included because HUD fills (health
gradients, meter bars) sit inside textured frames and must squash with them.
Depth-sorted world packets, full-frame backdrops, and true 4:3 frames remain
untouched.
+17
View File
@@ -417,6 +417,23 @@ if(BUILD_TESTING)
add_test(NAME gpu_textured_dot_nw_shift_exec_test
COMMAND gpu_textured_dot_nw_shift_exec_test)
# auto_ui_squash corrects untextured HUD fills with their textured frames.
add_executable(ws_auto_ui_untextured_exec_test
tests/test_ws_auto_ui_untextured_exec.c
src/ws_ui_group.c
src/psx_video_timing.c)
target_include_directories(ws_auto_ui_untextured_exec_test PRIVATE
include
src)
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(ws_auto_ui_untextured_exec_test PRIVATE
-ffunction-sections -fdata-sections)
target_link_options(ws_auto_ui_untextured_exec_test PRIVATE
-Wl,--gc-sections)
endif()
add_test(NAME ws_auto_ui_untextured_exec_test
COMMAND ws_auto_ui_untextured_exec_test)
add_executable(ws_view_anchor_test tests/test_ws_view_anchor.c)
add_executable(ws_view_anchor_gpu_test tests/test_ws_view_anchor_gpu.c
src/psx_video_timing.c)
+18 -5
View File
@@ -2518,7 +2518,8 @@ static int ws_auto_ui_anchor(int32_t *out_anchor) {
static uint32_t ws_auto_ui_group_key_words(const uint32_t *words,
uint32_t op,
int32_t y, int32_t h) {
uint32_t clut = words[2] >> 16;
/* An untextured polygon's word 2 is a vertex or colour, not a CLUT. */
uint32_t clut = (op < 0x60u && !(op & 0x04u)) ? 0u : words[2] >> 16;
uint32_t tpage = 0;
if (op >= 0x20u && op <= 0x3Fu && (op & 0x04u)) {
int tp_index = (op & 0x10u) ? 5 : 4;
@@ -4164,6 +4165,7 @@ static void gp0_exec_mono_quad(void) {
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
if (rej_a && rej_b) return;
ws_auto_ui_transform_quad(vx, vy);
/* Each added band is outside the original screen and executes in the
* mask's own OT position/blend mode. Canonical VRAM clips it away. */
@@ -4286,6 +4288,7 @@ static void gp0_exec_shaded_quad(void) {
int rej_a = psx_gpu_triangle_oversize(vx, vy, 0, 1, 2);
int rej_b = psx_gpu_triangle_oversize(vx, vy, 2, 1, 3);
if (rej_a && rej_b) return;
ws_auto_ui_transform_quad(vx, vy);
ws_nw_backdrop_stretch_quad(vx, vy); /* full-frame 2D backdrop stretch (sky gradient; no-op else) */
ws_nw_hud_shift_vertices(vx, 4);
for (int i = 0; i < 4; i++) {
@@ -5267,15 +5270,25 @@ static void ws_ui_prepass_add(const uint32_t *words, uint32_t word_count,
uint32_t op = words[0] >> 24;
int32_t min_x, max_x, min_y, max_y;
if (op >= 0x20u && op <= 0x3Fu && (op & 0x04u) &&
(op & 0x08u)) {
if (op >= 0x20u && op <= 0x3Fu && (op & 0x08u)) {
/* Any 4-vertex polygon. Untextured HUD fills (health gradients,
* meter bars) sit in the same front layer as the textured frames
* around them; leaving them out kept their raw 4:3 X while the frame
* squashed, so the fill overhung it at wide aspects (Spider-Man's
* health and webbing meters). Same rank + axis-aligned gate as the
* textured quads. */
const int textured = (op & 0x04u) != 0;
const int shaded = (op & 0x10u) != 0;
int indices[4];
if (op & 0x10u) {
if (textured && shaded) {
indices[0] = 1; indices[1] = 4;
indices[2] = 7; indices[3] = 10;
} else {
} else if (textured || shaded) {
indices[0] = 1; indices[1] = 3;
indices[2] = 5; indices[3] = 7;
} else {
indices[0] = 1; indices[1] = 2;
indices[2] = 3; indices[3] = 4;
}
int32_t vx[4], vy[4];
for (int i = 0; i < 4; i++)
+319
View File
@@ -0,0 +1,319 @@
/* Shared link stubs for tests that compile runtime/src/gpu.c directly and
* execute GP0 packets without a GL context, mod bank or emulator core.
* Include right after "../src/gpu.c". Define GPU_EXEC_REAL_UI_GROUP before
* including to link the real ws_ui_group.c instead of the grouping stub.
* Draw calls that tests observe are recorded below. */
uint64_t s_frame_count;
int g_exec_phase;
uint32_t g_debug_current_func_addr;
uint32_t g_debug_last_store_pc;
CPUState *debug_cpu_ptr;
int g_psx_vram_dirty_tracking;
uint32_t g_psx_ram_size = 0x00200000u;
uint32_t g_psx_ram_mask = 0x001FFFFFu;
/* This fixture exercises stock GPU packets without a GL context or mod bank.
* Keep the gpu.c dependencies explicit, including on linkers that retain
* additional exported command handlers despite --gc-sections. */
GrBackend gr_backend(void) { return GR_BACKEND_SOFTWARE; }
int gl_renderer_texture_banks_supported(void) { return 0; }
int gl_renderer_select_texture_bank(uint16_t id) { return id == 0; }
int gl_renderer_select_texture_bank_live_clut(uint16_t id) { return id == 0; }
uint16_t mod_texture_packet_bank(uint32_t source, const uint32_t *words,
uint32_t count) {
(void)source; (void)words; (void)count;
return 0;
}
int mod_texture_packet_precision(uint32_t source, float q[3], float xy[6]) {
(void)source; (void)q; (void)xy;
return 0;
}
static uint32_t test_ram[0x00200000u / 4u];
static struct {
int calls;
int x, y, w, h;
int u, v;
uint16_t clut_x, clut_y, texpage;
} last_textured_rect;
static struct { int calls, x, y, w, h, u0, v0, u1, v1; } last_scaled_rect;
/* Bounding box of every flat/gouraud triangle drawn since the last reset. */
static struct { int calls, min_x, max_x; } gpu_exec_triangles;
static inline void gpu_exec_reset_triangles(void) {
gpu_exec_triangles.calls = 0;
gpu_exec_triangles.min_x = 1 << 30;
gpu_exec_triangles.max_x = -(1 << 30);
}
static inline void gpu_exec_record_triangle(int x0, int y0, int x1, int y1,
int x2, int y2) {
const int xs[3] = {x0, x1, x2};
(void)y0; (void)y1; (void)y2;
gpu_exec_triangles.calls++;
for (int i = 0; i < 3; i++) {
if (xs[i] < gpu_exec_triangles.min_x) gpu_exec_triangles.min_x = xs[i];
if (xs[i] > gpu_exec_triangles.max_x) gpu_exec_triangles.max_x = xs[i];
}
}
uint32_t psx_read_word(uint32_t addr) {
uint32_t phys = addr & 0x1FFFFFFFu;
if (phys <= 0x00200000u - 4u)
return test_ram[phys / 4u];
return 0;
}
uint16_t psx_read_half(uint32_t addr) {
uint32_t word = psx_read_word(addr & ~3u);
return (uint16_t)(word >> ((addr & 2u) ? 16 : 0));
}
uint8_t psx_read_byte(uint32_t addr) {
uint32_t word = psx_read_word(addr & ~3u);
return (uint8_t)(word >> ((addr & 3u) * 8u));
}
void psx_write_half(uint32_t addr, uint16_t val) {
(void)addr;
(void)val;
}
void psx_host_write_half(uint32_t addr, uint16_t val) {
psx_write_half(addr, val);
}
int mdec_recently_active(uint32_t within_frames) {
(void)within_frames;
return 0;
}
void psx_fatal_halt(const char *reason) {
(void)reason;
assert(!"psx_fatal_halt called");
}
void pgxp_set_enabled(int enabled) { (void)enabled; }
int gte_geometry_correction_enabled(void) { return 0; }
int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word,
int32_t int_x, int32_t int_y,
int32_t *x16, int32_t *y16, uint16_t *sz) {
(void)addr; (void)packet_word; (void)int_x; (void)int_y;
(void)x16; (void)y16; (void)sz;
return PGXP_SRC_NATIVE;
}
int gte_precision_load_word(uint32_t addr, uint32_t packed,
int32_t *x16, int32_t *y16, uint16_t *z) {
(void)addr; (void)packed; (void)x16; (void)y16; (void)z;
return 0;
}
uint32_t sw_perspective_triangle_count(void) { return 0; }
uint32_t psx_ws_widen_angle_q12(uint32_t vanilla, int extent_pixels) {
(void)extent_pixels;
return vanilla;
}
int psx_ws_aspect_cone_contains(int32_t x, int32_t z, int32_t y,
int32_t fx, int32_t fz, int32_t fy,
uint32_t threshold, int extent_pixels) {
(void)x; (void)z; (void)y; (void)fx; (void)fz; (void)fy;
(void)threshold; (void)extent_pixels;
return 0;
}
#ifndef GPU_EXEC_REAL_UI_GROUP
void ws_ui_group_assign(WsUiGroupItem *items, size_t count,
int32_t display_width, int dense_menu, int in_place) {
(void)items; (void)count; (void)display_width; (void)dense_menu;
(void)in_place;
}
int32_t ws_ui_anchor_for_bounds(int32_t x, int32_t width,
int32_t display_width) {
(void)width;
if (x * 3 < display_width) return -1;
if (x * 3 > display_width * 2) return 1;
return 0;
}
#endif
void gpu_vram_dirty_mark_all(void) {}
void gpu_vram_dirty_mark_row_impl(uint32_t y) { (void)y; }
void psx_irq_raise(uint32_t bit, uint32_t detail) { (void)bit; (void)detail; }
void event_ring_record_aux(uint16_t kind, uint8_t detail, uint32_t aux) {
(void)kind; (void)detail; (void)aux;
}
int psx_get_in_exception(void) { return 0; }
int psx_netplay_active(void) { return 0; }
int sio_hold_present_for_card(void) { return 0; }
uint32_t psx_compiled_irq_resume_pc(void) { return 0; }
uint32_t psx_last_irq_check_pc(void) { return 0; }
uint32_t psx_netplay_rb_sticky_bb_pc(void) { return 0; }
void mod_runtime_on_vblank(void) {}
bool screen_kind_from_name(const char *name, ScreenKind *out) {
(void)name;
if (out) *out = SCREEN_RAW;
return true;
}
ColorLut *color_lut_create(const ColorSettings *settings) {
(void)settings;
return NULL;
}
void color_lut_destroy(ColorLut *lut) { (void)lut; }
bool color_lut_is_passthrough(const ColorLut *lut) {
(void)lut;
return true;
}
void color_lut_map555(const ColorLut *lut, uint16_t bgr555,
uint8_t *r, uint8_t *g, uint8_t *b) {
(void)lut;
if (r) *r = (uint8_t)((bgr555 & 0x1Fu) << 3);
if (g) *g = (uint8_t)(((bgr555 >> 5) & 0x1Fu) << 3);
if (b) *b = (uint8_t)(((bgr555 >> 10) & 0x1Fu) << 3);
}
uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address) { return address; }
void text_xlate_vram_upload(int x, int y, int w, int h) {
(void)x; (void)y; (void)w; (void)h;
}
uint32_t debug_guest_ra(void) { return 0; }
uint32_t debug_guest_sp(void) { return 0; }
uint8_t *memory_get_ram_ptr(void) { return (uint8_t *)test_ram; }
void gr_init(uint16_t *vram_ptr) { (void)vram_ptr; }
void gr_set_scale(int scale) { (void)scale; }
int gr_scale(void) { return 1; }
void gr_set_texture_filter(int bilinear) { (void)bilinear; }
int gr_texture_filter(void) { return 0; }
void gr_set_semi_transparency(int enabled, int mode) { (void)enabled; (void)mode; }
void gr_set_mask_bits(int set_bit, int check_bit) { (void)set_bit; (void)check_bit; }
void gr_set_texture_window(uint32_t raw) { (void)raw; }
void gr_set_color_modulation(int r, int g, int b, int raw_texture) {
(void)r; (void)g; (void)b; (void)raw_texture;
}
void gr_set_precise_triangle(int enabled, int32_t x0, int32_t y0,
int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
(void)enabled; (void)x0; (void)y0; (void)x1; (void)y1; (void)x2; (void)y2;
}
void gr_set_perspective_triangle(int enabled, float q0, float q1, float q2) {
(void)enabled; (void)q0; (void)q1; (void)q2;
}
void gr_fill_rect(int x, int y, int w, int h, uint16_t color) {
(void)x; (void)y; (void)w; (void)h; (void)color;
}
void gr_copy_rect(int src_x, int src_y, int dst_x, int dst_y, int w, int h) {
(void)src_x; (void)src_y; (void)dst_x; (void)dst_y; (void)w; (void)h;
}
void gr_draw_flat_triangle(int x0, int y0, int x1, int y1, int x2, int y2,
uint16_t color) {
gpu_exec_record_triangle(x0, y0, x1, y1, x2, y2);
(void)color;
}
void gr_draw_gouraud_triangle(int x0, int y0, uint16_t c0,
int x1, int y1, uint16_t c1,
int x2, int y2, uint16_t c2) {
gpu_exec_record_triangle(x0, y0, x1, y1, x2, y2);
(void)c0; (void)c1; (void)c2;
}
void gr_draw_textured_triangle(int x0, int y0, int u0, int v0,
int x1, int y1, int u1, int v1,
int x2, int y2, int u2, int v2,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage) {
(void)x0; (void)y0; (void)u0; (void)v0; (void)x1; (void)y1; (void)u1;
(void)v1; (void)x2; (void)y2; (void)u2; (void)v2; (void)clut_x;
(void)clut_y; (void)texpage;
}
void gr_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0,
uint32_t color0, int x1, int y1,
int u1, int v1, uint32_t color1,
int x2, int y2, int u2, int v2,
uint32_t color2, uint16_t clut_x,
uint16_t clut_y, uint16_t texpage,
int raw_texture) {
(void)x0; (void)y0; (void)u0; (void)v0; (void)color0; (void)x1;
(void)y1; (void)u1; (void)v1; (void)color1; (void)x2; (void)y2;
(void)u2; (void)v2; (void)color2; (void)clut_x; (void)clut_y;
(void)texpage; (void)raw_texture;
}
void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t color) {
(void)x; (void)y; (void)w; (void)h; (void)color;
}
void gr_draw_textured_rect(int x, int y, int w, int h, int u, int v,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage) {
last_textured_rect.calls++;
last_textured_rect.x = x;
last_textured_rect.y = y;
last_textured_rect.w = w;
last_textured_rect.h = h;
last_textured_rect.u = u;
last_textured_rect.v = v;
last_textured_rect.clut_x = clut_x;
last_textured_rect.clut_y = clut_y;
last_textured_rect.texpage = texpage;
}
void gr_draw_textured_rect_scaled(int x, int y, int w, int h,
int u0, int v0, int u1, int v1,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage) {
last_scaled_rect.calls++;
last_scaled_rect.x=x; last_scaled_rect.y=y;
last_scaled_rect.w=w; last_scaled_rect.h=h;
last_scaled_rect.u0=u0; last_scaled_rect.v0=v0;
last_scaled_rect.u1=u1; last_scaled_rect.v1=v1;
(void)v1; (void)clut_x; (void)clut_y; (void)texpage;
}
void gr_draw_line(int x0, int y0, int x1, int y1, uint16_t color) {
(void)x0; (void)y0; (void)x1; (void)y1; (void)color;
}
void gr_draw_shaded_line(int x0, int y0, uint16_t c0,
int x1, int y1, uint16_t c1) {
(void)x0; (void)y0; (void)c0; (void)x1; (void)y1; (void)c1;
}
int gr_render_display(uint32_t *out, int pitch, int dx, int dy, int dw, int dh) {
(void)out; (void)pitch; (void)dx; (void)dy; (void)dw; (void)dh;
return 0;
}
int gr_render_display_hires(uint32_t *out, int pitch, int dx, int dy, int dw, int dh) {
(void)out; (void)pitch; (void)dx; (void)dy; (void)dw; (void)dh;
return 0;
}
void gr_vram_write(int x, int y, uint16_t pixel) { (void)x; (void)y; (void)pixel; }
uint16_t gr_vram_read(int x, int y) { (void)x; (void)y; return 0; }
void gr_vram_transfer_in(int x, int y, int w, int h, const uint16_t *data) {
(void)x; (void)y; (void)w; (void)h; (void)data;
}
void gr_vram_transfer_out(int x, int y, int w, int h, uint16_t *data) {
(void)x; (void)y; (void)w; (void)h; (void)data;
}
void gr_set_draw_area(int x1, int y1, int x2, int y2) {
(void)x1; (void)y1; (void)x2; (void)y2;
}
void gr_get_draw_area(int *x1, int *y1, int *x2, int *y2) {
if (x1) *x1 = (int)draw_area_left;
if (y1) *y1 = (int)draw_area_top;
if (x2) *x2 = (int)draw_area_right;
if (y2) *y2 = (int)draw_area_bottom;
}
void gr_set_draw_offset(int x, int y) { (void)x; (void)y; }
int gr_wide_supported(void) { return 1; }
void gr_wide_configure(int wide_w, int offset) { (void)wide_w; (void)offset; }
void gr_wide_set_view(int enabled, int shift, int left, int right) {
(void)enabled; (void)shift; (void)left; (void)right;
}
void gr_wide_set_target(int base_x) { (void)base_x; }
void gr_wide_disable_target(void) {}
void gr_wide_clear(int base_x, int y, int h, uint16_t color) {
(void)base_x; (void)y; (void)h; (void)color;
}
void gr_wide_clear_margins(int base_x, int y, int h, uint16_t color, int sides) {
(void)base_x; (void)y; (void)h; (void)color; (void)sides;
}
int gr_wide_dump_full(uint32_t *out, int cap_pixels, int *ow, int *oh, int base_x) {
(void)out; (void)cap_pixels; (void)ow; (void)oh; (void)base_x;
return 0;
}
int gr_render_wide_display(uint32_t *out, int pitch, int base_x,
int disp_y, int disp_h) {
(void)out; (void)pitch; (void)base_x; (void)disp_y; (void)disp_h;
return 0;
}
@@ -9,41 +9,7 @@
#include "../src/gpu.c"
uint64_t s_frame_count;
int g_exec_phase;
uint32_t g_debug_current_func_addr;
uint32_t g_debug_last_store_pc;
CPUState *debug_cpu_ptr;
int g_psx_vram_dirty_tracking;
uint32_t g_psx_ram_size = 0x00200000u;
uint32_t g_psx_ram_mask = 0x001FFFFFu;
/* This fixture exercises stock GPU packets without a GL context or mod bank.
* Keep the gpu.c dependencies explicit, including on linkers that retain
* additional exported command handlers despite --gc-sections. */
GrBackend gr_backend(void) { return GR_BACKEND_SOFTWARE; }
int gl_renderer_texture_banks_supported(void) { return 0; }
int gl_renderer_select_texture_bank(uint16_t id) { return id == 0; }
int gl_renderer_select_texture_bank_live_clut(uint16_t id) { return id == 0; }
uint16_t mod_texture_packet_bank(uint32_t source, const uint32_t *words,
uint32_t count) {
(void)source; (void)words; (void)count;
return 0;
}
int mod_texture_packet_precision(uint32_t source, float q[3], float xy[6]) {
(void)source; (void)q; (void)xy;
return 0;
}
static uint32_t test_ram[0x00200000u / 4u];
static struct {
int calls;
int x, y, w, h;
int u, v;
uint16_t clut_x, clut_y, texpage;
} last_textured_rect;
static struct { int calls, x, y, w, h, u0, v0, u1, v1; } last_scaled_rect;
#include "gpu_exec_stubs.inc"
static uint32_t pack_vertex(int16_t x, int16_t y) {
return (uint16_t)x | ((uint32_t)(uint16_t)y << 16);
@@ -143,260 +109,3 @@ int main(void) {
puts("gpu_textured_dot_nw_shift_exec_test: PASS");
return 0;
}
uint32_t psx_read_word(uint32_t addr) {
uint32_t phys = addr & 0x1FFFFFFFu;
if (phys <= 0x00200000u - 4u)
return test_ram[phys / 4u];
return 0;
}
uint16_t psx_read_half(uint32_t addr) {
uint32_t word = psx_read_word(addr & ~3u);
return (uint16_t)(word >> ((addr & 2u) ? 16 : 0));
}
uint8_t psx_read_byte(uint32_t addr) {
uint32_t word = psx_read_word(addr & ~3u);
return (uint8_t)(word >> ((addr & 3u) * 8u));
}
void psx_write_half(uint32_t addr, uint16_t val) {
(void)addr;
(void)val;
}
void psx_host_write_half(uint32_t addr, uint16_t val) {
psx_write_half(addr, val);
}
int mdec_recently_active(uint32_t within_frames) {
(void)within_frames;
return 0;
}
void psx_fatal_halt(const char *reason) {
(void)reason;
assert(!"psx_fatal_halt called");
}
void pgxp_set_enabled(int enabled) { (void)enabled; }
int gte_geometry_correction_enabled(void) { return 0; }
int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word,
int32_t int_x, int32_t int_y,
int32_t *x16, int32_t *y16, uint16_t *sz) {
(void)addr; (void)packet_word; (void)int_x; (void)int_y;
(void)x16; (void)y16; (void)sz;
return PGXP_SRC_NATIVE;
}
int gte_precision_load_word(uint32_t addr, uint32_t packed,
int32_t *x16, int32_t *y16, uint16_t *z) {
(void)addr; (void)packed; (void)x16; (void)y16; (void)z;
return 0;
}
uint32_t sw_perspective_triangle_count(void) { return 0; }
uint32_t psx_ws_widen_angle_q12(uint32_t vanilla, int extent_pixels) {
(void)extent_pixels;
return vanilla;
}
int psx_ws_aspect_cone_contains(int32_t x, int32_t z, int32_t y,
int32_t fx, int32_t fz, int32_t fy,
uint32_t threshold, int extent_pixels) {
(void)x; (void)z; (void)y; (void)fx; (void)fz; (void)fy;
(void)threshold; (void)extent_pixels;
return 0;
}
void ws_ui_group_assign(WsUiGroupItem *items, size_t count,
int32_t display_width, int dense_menu, int in_place) {
(void)items; (void)count; (void)display_width; (void)dense_menu;
(void)in_place;
}
int32_t ws_ui_anchor_for_bounds(int32_t x, int32_t width,
int32_t display_width) {
(void)width;
if (x * 3 < display_width) return -1;
if (x * 3 > display_width * 2) return 1;
return 0;
}
void gpu_vram_dirty_mark_all(void) {}
void gpu_vram_dirty_mark_row_impl(uint32_t y) { (void)y; }
void psx_irq_raise(uint32_t bit, uint32_t detail) { (void)bit; (void)detail; }
void event_ring_record_aux(uint16_t kind, uint8_t detail, uint32_t aux) {
(void)kind; (void)detail; (void)aux;
}
int psx_get_in_exception(void) { return 0; }
int psx_netplay_active(void) { return 0; }
int sio_hold_present_for_card(void) { return 0; }
uint32_t psx_compiled_irq_resume_pc(void) { return 0; }
uint32_t psx_last_irq_check_pc(void) { return 0; }
uint32_t psx_netplay_rb_sticky_bb_pc(void) { return 0; }
void mod_runtime_on_vblank(void) {}
bool screen_kind_from_name(const char *name, ScreenKind *out) {
(void)name;
if (out) *out = SCREEN_RAW;
return true;
}
ColorLut *color_lut_create(const ColorSettings *settings) {
(void)settings;
return NULL;
}
void color_lut_destroy(ColorLut *lut) { (void)lut; }
bool color_lut_is_passthrough(const ColorLut *lut) {
(void)lut;
return true;
}
void color_lut_map555(const ColorLut *lut, uint16_t bgr555,
uint8_t *r, uint8_t *g, uint8_t *b) {
(void)lut;
if (r) *r = (uint8_t)((bgr555 & 0x1Fu) << 3);
if (g) *g = (uint8_t)(((bgr555 >> 5) & 0x1Fu) << 3);
if (b) *b = (uint8_t)(((bgr555 >> 10) & 0x1Fu) << 3);
}
uint32_t psx_mod_gpu_dma_resolve_address(uint32_t address) { return address; }
void text_xlate_vram_upload(int x, int y, int w, int h) {
(void)x; (void)y; (void)w; (void)h;
}
uint32_t debug_guest_ra(void) { return 0; }
uint32_t debug_guest_sp(void) { return 0; }
uint8_t *memory_get_ram_ptr(void) { return (uint8_t *)test_ram; }
void gr_init(uint16_t *vram_ptr) { (void)vram_ptr; }
void gr_set_scale(int scale) { (void)scale; }
int gr_scale(void) { return 1; }
void gr_set_texture_filter(int bilinear) { (void)bilinear; }
int gr_texture_filter(void) { return 0; }
void gr_set_semi_transparency(int enabled, int mode) { (void)enabled; (void)mode; }
void gr_set_mask_bits(int set_bit, int check_bit) { (void)set_bit; (void)check_bit; }
void gr_set_texture_window(uint32_t raw) { (void)raw; }
void gr_set_color_modulation(int r, int g, int b, int raw_texture) {
(void)r; (void)g; (void)b; (void)raw_texture;
}
void gr_set_precise_triangle(int enabled, int32_t x0, int32_t y0,
int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
(void)enabled; (void)x0; (void)y0; (void)x1; (void)y1; (void)x2; (void)y2;
}
void gr_set_perspective_triangle(int enabled, float q0, float q1, float q2) {
(void)enabled; (void)q0; (void)q1; (void)q2;
}
void gr_fill_rect(int x, int y, int w, int h, uint16_t color) {
(void)x; (void)y; (void)w; (void)h; (void)color;
}
void gr_copy_rect(int src_x, int src_y, int dst_x, int dst_y, int w, int h) {
(void)src_x; (void)src_y; (void)dst_x; (void)dst_y; (void)w; (void)h;
}
void gr_draw_flat_triangle(int x0, int y0, int x1, int y1, int x2, int y2,
uint16_t color) {
(void)x0; (void)y0; (void)x1; (void)y1; (void)x2; (void)y2; (void)color;
}
void gr_draw_gouraud_triangle(int x0, int y0, uint16_t c0,
int x1, int y1, uint16_t c1,
int x2, int y2, uint16_t c2) {
(void)x0; (void)y0; (void)c0; (void)x1; (void)y1; (void)c1;
(void)x2; (void)y2; (void)c2;
}
void gr_draw_textured_triangle(int x0, int y0, int u0, int v0,
int x1, int y1, int u1, int v1,
int x2, int y2, int u2, int v2,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage) {
(void)x0; (void)y0; (void)u0; (void)v0; (void)x1; (void)y1; (void)u1;
(void)v1; (void)x2; (void)y2; (void)u2; (void)v2; (void)clut_x;
(void)clut_y; (void)texpage;
}
void gr_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0,
uint32_t color0, int x1, int y1,
int u1, int v1, uint32_t color1,
int x2, int y2, int u2, int v2,
uint32_t color2, uint16_t clut_x,
uint16_t clut_y, uint16_t texpage,
int raw_texture) {
(void)x0; (void)y0; (void)u0; (void)v0; (void)color0; (void)x1;
(void)y1; (void)u1; (void)v1; (void)color1; (void)x2; (void)y2;
(void)u2; (void)v2; (void)color2; (void)clut_x; (void)clut_y;
(void)texpage; (void)raw_texture;
}
void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t color) {
(void)x; (void)y; (void)w; (void)h; (void)color;
}
void gr_draw_textured_rect(int x, int y, int w, int h, int u, int v,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage) {
last_textured_rect.calls++;
last_textured_rect.x = x;
last_textured_rect.y = y;
last_textured_rect.w = w;
last_textured_rect.h = h;
last_textured_rect.u = u;
last_textured_rect.v = v;
last_textured_rect.clut_x = clut_x;
last_textured_rect.clut_y = clut_y;
last_textured_rect.texpage = texpage;
}
void gr_draw_textured_rect_scaled(int x, int y, int w, int h,
int u0, int v0, int u1, int v1,
uint16_t clut_x, uint16_t clut_y,
uint16_t texpage) {
last_scaled_rect.calls++;
last_scaled_rect.x=x; last_scaled_rect.y=y;
last_scaled_rect.w=w; last_scaled_rect.h=h;
last_scaled_rect.u0=u0; last_scaled_rect.v0=v0;
last_scaled_rect.u1=u1; last_scaled_rect.v1=v1;
(void)v1; (void)clut_x; (void)clut_y; (void)texpage;
}
void gr_draw_line(int x0, int y0, int x1, int y1, uint16_t color) {
(void)x0; (void)y0; (void)x1; (void)y1; (void)color;
}
void gr_draw_shaded_line(int x0, int y0, uint16_t c0,
int x1, int y1, uint16_t c1) {
(void)x0; (void)y0; (void)c0; (void)x1; (void)y1; (void)c1;
}
int gr_render_display(uint32_t *out, int pitch, int dx, int dy, int dw, int dh) {
(void)out; (void)pitch; (void)dx; (void)dy; (void)dw; (void)dh;
return 0;
}
int gr_render_display_hires(uint32_t *out, int pitch, int dx, int dy, int dw, int dh) {
(void)out; (void)pitch; (void)dx; (void)dy; (void)dw; (void)dh;
return 0;
}
void gr_vram_write(int x, int y, uint16_t pixel) { (void)x; (void)y; (void)pixel; }
uint16_t gr_vram_read(int x, int y) { (void)x; (void)y; return 0; }
void gr_vram_transfer_in(int x, int y, int w, int h, const uint16_t *data) {
(void)x; (void)y; (void)w; (void)h; (void)data;
}
void gr_vram_transfer_out(int x, int y, int w, int h, uint16_t *data) {
(void)x; (void)y; (void)w; (void)h; (void)data;
}
void gr_set_draw_area(int x1, int y1, int x2, int y2) {
(void)x1; (void)y1; (void)x2; (void)y2;
}
void gr_get_draw_area(int *x1, int *y1, int *x2, int *y2) {
if (x1) *x1 = (int)draw_area_left;
if (y1) *y1 = (int)draw_area_top;
if (x2) *x2 = (int)draw_area_right;
if (y2) *y2 = (int)draw_area_bottom;
}
void gr_set_draw_offset(int x, int y) { (void)x; (void)y; }
int gr_wide_supported(void) { return 1; }
void gr_wide_configure(int wide_w, int offset) { (void)wide_w; (void)offset; }
void gr_wide_set_view(int enabled, int shift, int left, int right) {
(void)enabled; (void)shift; (void)left; (void)right;
}
void gr_wide_set_target(int base_x) { (void)base_x; }
void gr_wide_disable_target(void) {}
void gr_wide_clear(int base_x, int y, int h, uint16_t color) {
(void)base_x; (void)y; (void)h; (void)color;
}
void gr_wide_clear_margins(int base_x, int y, int h, uint16_t color, int sides) {
(void)base_x; (void)y; (void)h; (void)color; (void)sides;
}
int gr_wide_dump_full(uint32_t *out, int cap_pixels, int *ow, int *oh, int base_x) {
(void)out; (void)cap_pixels; (void)ow; (void)oh; (void)base_x;
return 0;
}
int gr_render_wide_display(uint32_t *out, int pitch, int base_x,
int disp_y, int disp_h) {
(void)out; (void)pitch; (void)base_x; (void)disp_y; (void)disp_h;
return 0;
}
@@ -0,0 +1,144 @@
#ifdef NDEBUG
#undef NDEBUG
#endif
/* auto_ui_squash must correct untextured HUD fills together with the textured
* frame around them. Spider-Man's health bar is a gouraud quad (GP0 0x38) and
* its webbing meter a flat quad (0x28) inside textured frames (0x2C); while
* only textured quads were admitted, the fills kept their raw 4:3 X and
* overhung the squashed frames at 21:9 and 32:9.
*
* Drives the real path: an ordering-table linked list in guest RAM, the UI
* prepass, run grouping (real ws_ui_group.c), then GP0 execution. */
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define GPU_EXEC_REAL_UI_GROUP
#include "../src/gpu.c"
#include "gpu_exec_stubs.inc"
#define OT_HEAD 0x00010000u
#define NODE_RING 0x00010100u
#define NODE_FILL 0x00010200u
#define NODE_FLAT 0x00010300u
static uint32_t pack_vertex(int16_t x, int16_t y) {
return (uint16_t)x | ((uint32_t)(uint16_t)y << 16);
}
static void put_node(uint32_t addr, uint32_t next, const uint32_t *words,
uint32_t count) {
test_ram[addr / 4u] = (count << 24) | (next & 0xFFFFFFu);
for (uint32_t i = 0; i < count; i++)
test_ram[addr / 4u + 1u + i] = words[i];
}
/* Frame: textured quad over [x0, x1). Fills inside it, both untextured. */
static void build_hud(int16_t x0, int16_t x1, int16_t fx0, int16_t fx1) {
memset(test_ram, 0, sizeof(test_ram));
const uint32_t frame[9] = {
0x2C808080u, pack_vertex(x0, 20), 0x00000000u,
pack_vertex(x1, 20), 0x00080000u,
pack_vertex(x0, 36), 0x00001000u,
pack_vertex(x1, 36), 0x00001010u,
};
const uint32_t gouraud[8] = {
0x3800FF00u, pack_vertex(fx0, 26),
0x0000FF80u, pack_vertex(fx1, 26),
0x0000FF00u, pack_vertex(fx0, 30),
0x0000FF80u, pack_vertex(fx1, 30),
};
const uint32_t flat[5] = {
0x28802080u, pack_vertex(fx0, 31), pack_vertex(fx1, 31),
pack_vertex(fx0, 34), pack_vertex(fx1, 34),
};
test_ram[OT_HEAD / 4u] = NODE_RING; /* empty OT entry: rank 0 */
put_node(NODE_RING, NODE_FILL, frame, 9);
put_node(NODE_FILL, NODE_FLAT, gouraud, 8);
put_node(NODE_FLAT, 0xFFFFFFu, flat, 5);
}
static void load_packet(uint32_t node, uint32_t count) {
for (uint32_t i = 0; i < count; i++)
gp0_cmd_buf[i] = test_ram[node / 4u + 1u + i];
gp0_cmd_source_addr = node + 4u;
gp0_words_needed = (int)count;
}
static void reset_state(int in_place) {
s_frame_count = 100;
draw_offset_x = draw_offset_y = 0;
draw_area_left = 0; draw_area_top = 0;
draw_area_right = 1023; draw_area_bottom = 511;
hres1 = 1; hres2 = 0; video_mode = 0; display_depth = 0;
display_disabled = 0; display_area_x = 0; display_area_y = 0;
h_display_x1 = 0x200; h_display_x2 = 0xC00;
v_display_y1 = 0x010; v_display_y2 = 0x100;
/* Projection-and-stretch path at 32:9: X squash 3/8, not native-wide. */
ws_mode = 0;
ws_cfg_num = 32; ws_cfg_den = 9;
ws_xnum = 3; ws_xden = 8;
ws_full_2d = 0;
gpu_ws_set_auto_ui_squash(1);
gpu_ws_set_auto_ui_in_place(in_place);
}
/* Prepass the list and execute the two fills; returns their drawn X span. */
static void run_fills(int *gouraud_min, int *gouraud_max,
int *flat_min, int *flat_max) {
gpu_ws_prepass_linked_list(OT_HEAD);
assert(ws_ui_prepass_count == 3); /* frame + both untextured fills */
gpu_exec_reset_triangles();
load_packet(NODE_FILL, 8);
gp0_exec_shaded_quad();
assert(gpu_exec_triangles.calls == 2);
*gouraud_min = gpu_exec_triangles.min_x;
*gouraud_max = gpu_exec_triangles.max_x;
gpu_exec_reset_triangles();
load_packet(NODE_FLAT, 5);
gp0_exec_mono_quad();
assert(gpu_exec_triangles.calls >= 1);
*flat_min = gpu_exec_triangles.min_x;
*flat_max = gpu_exec_triangles.max_x;
}
int main(void) {
int gmin, gmax, fmin, fmax;
/* in_place: the fill and its frame share the run's own centre. */
reset_state(1);
build_hud(60, 128, 64, 120);
run_fills(&gmin, &gmax, &fmin, &fmax);
const int32_t centre = 60 + (128 - 60) / 2;
assert(gmin == ws_scale_about(64, centre));
assert(gmax == ws_scale_about(120, centre));
assert(fmin == gmin && fmax == gmax);
/* Inside the squashed frame, not overhanging it. */
assert(gmin >= ws_scale_about(60, centre));
assert(gmax <= ws_scale_about(128, centre));
/* edges (default): a left-third run pins to the display's left edge. */
reset_state(0);
build_hud(60, 128, 64, 120);
run_fills(&gmin, &gmax, &fmin, &fmax);
const int32_t left = ws_disp_x();
assert(gmin == ws_scale_about(64, left));
assert(gmax == ws_scale_about(120, left));
assert(fmin == gmin && fmax == gmax);
/* A non-axis-aligned untextured quad is world geometry, never UI. */
reset_state(1);
build_hud(60, 128, 64, 120);
test_ram[NODE_FILL / 4u + 1u + 3u] = pack_vertex(121, 25); /* skew v1 */
gpu_ws_prepass_linked_list(OT_HEAD);
assert(ws_ui_prepass_count == 2);
puts("ws_auto_ui_untextured_exec_test: PASS");
return 0;
}