diff --git a/docs/WIDESCREEN.md b/docs/WIDESCREEN.md index e5a16fee..3658696b 100644 --- a/docs/WIDESCREEN.md +++ b/docs/WIDESCREEN.md @@ -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. diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index b1c969fa..e0e17da8 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -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) diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 0a969285..cc7a6312 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -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++) diff --git a/runtime/tests/gpu_exec_stubs.inc b/runtime/tests/gpu_exec_stubs.inc new file mode 100644 index 00000000..72d6fb72 --- /dev/null +++ b/runtime/tests/gpu_exec_stubs.inc @@ -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; +} diff --git a/runtime/tests/test_gpu_textured_dot_nw_shift_exec.c b/runtime/tests/test_gpu_textured_dot_nw_shift_exec.c index 49dfb9a0..cb582140 100644 --- a/runtime/tests/test_gpu_textured_dot_nw_shift_exec.c +++ b/runtime/tests/test_gpu_textured_dot_nw_shift_exec.c @@ -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; -} diff --git a/runtime/tests/test_ws_auto_ui_untextured_exec.c b/runtime/tests/test_ws_auto_ui_untextured_exec.c new file mode 100644 index 00000000..4a2f541d --- /dev/null +++ b/runtime/tests/test_ws_auto_ui_untextured_exec.c @@ -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 +#include +#include +#include + +#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; +}