diff --git a/runtime/include/gpu.h b/runtime/include/gpu.h index 40ad1091..e4eb546d 100644 --- a/runtime/include/gpu.h +++ b/runtime/include/gpu.h @@ -177,6 +177,17 @@ void gpu_ws_set_view_anchor(uint32_t camera, uint32_t min, uint32_t max, uint32_ * their own map edges; linked/foreground layers retain the world origin. */ void gpu_ws_bg2d_begin_view_layer(unsigned layer, uint32_t packet, unsigned independent_mask); void gpu_ws_bg2d_end_view_layer(unsigned layer, uint32_t packet); +/* Opt-in host-produced SPRT_16 packets, 32-byte slots in enhancement DMA RAM. + * The 16-byte standard packet is followed by signed view shift, left/right + * padding and GPU_WS_BG2D_PACKET_MAGIC, optionally ORed with MIRROR_X. + * Coordinates use signed 16-bit X; ordinary + * packets retain PS1 signed 11-bit coordinates. The guest tile loop/ring stays + * native while this arena is registered. Allocate once during mod activation. */ +void gpu_ws_bg2d_set_host_arena(uint32_t base, uint32_t size); +#define GPU_WS_BG2D_PACKET_MAGIC 0x58364247u +#define GPU_WS_BG2D_MIRROR_X 0x80000000u +struct WsViewAnchor; +int gpu_ws_bg2d_get_view(unsigned layer, struct WsViewAnchor *view); void gpu_ws_set_auto_ui_squash(int on); /* [widescreen.bg2d] Capcom 2D background tile-loop widen — hooked at the renderer's * column-count / start-tile-col / start-screen-x instructions. Identity at 4:3 @@ -470,6 +481,7 @@ typedef struct { int nw_extra; /* native-wide frame growth (display px), 0 if off */ int view_anchor, view_left, view_right, view_shift, view_pad_left, view_pad_right; uint64_t cur_frame; + uint32_t bg2d_generations; /* Game background submissions, independent of vblank. */ uint32_t last_tag_frame; /* frame of newest tagged prim */ uint32_t last_3d_frame; /* frame of newest shaded prim (diagnostic) */ uint32_t gte_verts; /* RTPS/RTPT verts in the last completed frame */ diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index d7152aec..7c5e2b3d 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -5361,6 +5361,7 @@ static void handle_gpu_state(int id, const char *json) "\"mode\":%d,\"nw_extra\":%d," "\"view_anchor\":%d,\"view_left\":%d,\"view_right\":%d," "\"view_shift\":%d,\"view_padding\":[%d,%d]," + "\"bg2d_generations\":%u," "\"cur_frame\":%llu,\"last_tag_frame\":%u,\"last_3d_frame\":%u," "\"gte_verts\":%u,\"last_world3d_frame\":%u," "\"ovh_prims\":%u,\"last_ovh_frame\":%u," @@ -5393,6 +5394,7 @@ static void handle_gpu_state(int id, const char *json) ws.mode, ws.nw_extra, ws.view_anchor, ws.view_left, ws.view_right, ws.view_shift, ws.view_pad_left, ws.view_pad_right, + ws.bg2d_generations, (unsigned long long)ws.cur_frame, ws.last_tag_frame, ws.last_3d_frame, ws.gte_verts, ws.last_world3d_frame, ws.ovh_prims, ws.last_ovh_frame, diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 8c35ff5a..2bdc1517 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -1207,6 +1207,23 @@ static uint32_t g_bg2d_layer_struct_stride = 0x54; static uint32_t g_bg2d_packet_cap = 1000; static int g_bg2d_native_cols = 21; static int g_bg2d_parent_links = 1; +static uint32_t g_bg2d_host_base, g_bg2d_host_size; +static uint32_t g_bg2d_host_generation; + +void gpu_ws_bg2d_set_host_arena(uint32_t base, uint32_t size) { + g_bg2d_host_base = base & 0x1fffffffu; + g_bg2d_host_size = size >= 32u && !(base & 31u) ? size : 0u; +} + +static uint32_t ws_bg2d_host_packet(void) { + uint32_t command = gp0_cmd_source_addr & 0x1fffffffu; + if (!g_bg2d_host_size || command < g_bg2d_host_base + 4u) return 0; + uint32_t offset = command - g_bg2d_host_base - 4u; + if ((offset & 31u) || offset > g_bg2d_host_size - 32u) return 0; + uint32_t packet = command - 4u; + return (psx_read_word(packet + 28u) & ~GPU_WS_BG2D_MIRROR_X) == + GPU_WS_BG2D_PACKET_MAGIC ? packet : 0; +} #define WS_VIEW_LAYERS 8 static int ws_view_layer = -1; static unsigned ws_view_independent_mask; @@ -1237,11 +1254,22 @@ static WsViewAnchor ws_bg2d_layer_view(int layer) { v.shift = origin - extra; v.left = origin; v.right = 2 * extra - origin; + } else if (g_bg2d_host_size && hi > lo && hi - lo < frame_width) { + int pad = frame_width - (hi - lo); + v.pad_left = pad / 2; + v.pad_right = pad - v.pad_left; + v.shift = sx - lo + v.pad_left - extra; + v.left = sx > lo ? sx - lo : 0; + v.right = hi > sx + width ? hi - sx - width : 0; } return v; } void gpu_ws_bg2d_begin_view_layer(unsigned layer, uint32_t packet, unsigned independent_mask) { + if (g_bg2d_host_size) { + ws_view_sample(); + if (layer == 0) ++g_bg2d_host_generation; + } if (!ws_view_enabled() || layer >= WS_VIEW_LAYERS) return; ws_view_independent_mask = independent_mask; ws_view_layer = (int)layer; @@ -1249,6 +1277,11 @@ void gpu_ws_bg2d_begin_view_layer(unsigned layer, uint32_t packet, unsigned inde ws_view_layers[layer].end = ws_view_layers[layer].begin; ws_view_layers[layer].frame = (uint32_t)s_frame_count; } +int gpu_ws_bg2d_get_view(unsigned layer, WsViewAnchor *view) { + if (!view || !ws_native_wide_active() || ws_disp_w() > 384) return 0; + *view = ws_bg2d_layer_view((int)layer); + return ws_disp_w(); +} void gpu_ws_bg2d_end_view_layer(unsigned layer, uint32_t packet) { if (!ws_view_enabled() || layer >= WS_VIEW_LAYERS) return; ws_view_layers[layer].end = packet & 0x1fffffffu; @@ -1256,6 +1289,16 @@ void gpu_ws_bg2d_end_view_layer(unsigned layer, uint32_t packet) { ws_view_layer = -1; } static WsViewAnchor ws_view_packet(void) { + uint32_t packet = ws_bg2d_host_packet(); + if (packet) { + WsViewAnchor v = ws_view_current(); + v.shift = (int32_t)psx_read_word(packet + 16u); + v.pad_left = (int32_t)psx_read_word(packet + 20u); + v.pad_right = (int32_t)psx_read_word(packet + 24u); + v.left = ws_nw_offset() + v.shift - v.pad_left; + v.right = 2 * ws_nw_offset() - v.left - v.pad_left - v.pad_right; + return v; + } if (!ws_view_enabled()) return ws_view_current(); uint32_t addr = gp0_cmd_source_addr & 0x1fffffffu; for (unsigned i = 0; i < WS_VIEW_LAYERS; i++) @@ -1284,6 +1327,7 @@ void gpu_ws_bg2d_set_parent_links(int on) { } static int ws_bg2d_left_cols(void) { + if (g_bg2d_host_size) return 0; if (!ws_native_wide_active()) return 0; /* Only the ~320 gameplay mode; the engine's 512 hi-res mode (title) draws * its own 33 columns and centres itself — never double-shift it. */ @@ -1293,6 +1337,7 @@ static int ws_bg2d_left_cols(void) { return (off + 15) / 16; /* ceil to whole tile columns */ } static int ws_bg2d_right_cols(void) { + if (g_bg2d_host_size) return 0; if (!ws_native_wide_active() || ws_disp_w() > 384) return 0; return (ws_bg2d_layer_view(ws_view_layer).right + 15) / 16; } @@ -1507,6 +1552,7 @@ long gpu_ws_mmx6_refill_cols(void) { return g_mmx6_refill_cols; } * No-op at 4:3 / 512 hi-res (left==0). Idempotent over the native columns the engine * already streamed correctly, so re-streaming them is harmless. */ void psx_ws_mmx6_bg_refill_all(void) { + if (g_bg2d_host_size) return; if (!g_mmx6_freshfix) return; if (!ws_native_wide_active() || ws_disp_w() > 384) return; if (ws_nw_offset() <= 0) return; @@ -1924,6 +1970,7 @@ void gpu_ws_get_debug(GpuWsDebug* out) { out->view_shift = view.shift; out->view_pad_left = view.pad_left; out->view_pad_right = view.pad_right; out->cur_frame = s_frame_count; + out->bg2d_generations = g_bg2d_host_generation; out->last_tag_frame = ws_last_tag_stamp; out->last_3d_frame = ws_last_3d_stamp; out->gte_verts = ws_gte_prev_verts; @@ -2519,6 +2566,7 @@ static int ws_nw_left_hud_packet(void) { } static int32_t ws_nw_hud_shift(int32_t x, int32_t w) { if (!ws_native_wide_active()) return 0; + if (g_bg2d_host_size && ws_tagged_world_primitive()) return 0; int32_t off = ws_nw_offset(); if (off <= 0) return 0; int hud_edge = ws_tagged_hud_edge(); @@ -3669,7 +3717,9 @@ static int32_t sign_extend(uint32_t val, int bits) { /* Parse a vertex position word: signed 11-bit X and Y */ static void parse_vertex(uint32_t word, int32_t* x, int32_t* y) { - *x = sign_extend(word & 0x7FFu, 11); + int extended = ws_bg2d_host_packet() || (g_bg2d_host_size && + ws_native_wide_active() && ws_tagged_world_primitive()); + *x = extended ? (int16_t)word : sign_extend(word & 0x7FFu, 11); *y = sign_extend((word >> 16) & 0x7FFu, 11); } @@ -4668,8 +4718,11 @@ static void gp0_exec_textured_16x16(void) { /* Never suppress MMX6 BG packets at a guessed finite-map boundary. The * classifier cannot distinguish an authored layer entering the reveal from * a stale ring slot; suppressing here caused the stage-start black flicker. */ - int ws_w = ws_sprt_fixed_transform(&x0, y0, 16); - x0 += ws_nw_hud_shift(x0, 16); /* native-wide HUD corner re-anchor (no-op else) */ + uint32_t host_packet = ws_bg2d_host_packet(); + int host_tile = host_packet != 0; + if (host_tile && !ws_native_wide_active()) return; + int ws_w = host_tile ? 0 : ws_sprt_fixed_transform(&x0, y0, 16); + if (!host_tile) x0 += ws_nw_hud_shift(x0, 16); x0 += draw_offset_x; y0 += draw_offset_y; int u0 = gp0_cmd_buf[2] & 0xFF; int v0 = (gp0_cmd_buf[2] >> 8) & 0xFF; @@ -4682,7 +4735,10 @@ static void gp0_exec_textured_16x16(void) { if (draw_area_out_rect(x0, y0, dw, 16)) return; } setup_textured_draw(color24, semi_trans, raw_texture); - if (ws_w && ws_w != 16) + if (host_tile && (psx_read_word(host_packet + 28u) & GPU_WS_BG2D_MIRROR_X)) + gr_draw_textured_rect_scaled(x0, y0, 16, 16, u0 + 15, v0, u0 - 1, v0 + 16, + clut_x, clut_y, current_texpage()); + else if (ws_w && ws_w != 16) gr_draw_textured_rect_scaled(x0, y0, ws_w, 16, u0, v0, u0 + 16, v0 + 16, clut_x, clut_y, current_texpage()); else @@ -5608,16 +5664,21 @@ static void gp0_execute_command(void) { gp0_ring_record(gp0_cmd_buf, gp0_words_needed); extern void ws_bg_phase_note(uint32_t op); ws_bg_phase_note(opcode); /* native-wide 2D-backdrop stretch: background-phase latch */ - if (ws_view_camera_addr) { + if (ws_view_camera_addr || g_bg2d_host_size) { WsViewAnchor v = ws_view_current(); /* Draw env packets select the back-buffer band after linked-list * begin. Clear narrow-room padding at its first actual draw. */ static uint32_t clear_frame[512]; - if (opcode >= 0x20 && opcode <= 0x7F && ws_view_enabled() && - (v.pad_left || v.pad_right) && draw_area_top < 512 && - clear_frame[draw_area_top] != (uint32_t)s_frame_count + 1u && + static uint32_t clear_generation[512]; + if (opcode >= 0x20 && opcode <= 0x7F && + (ws_view_enabled() || (g_bg2d_host_size && ws_native_wide_active() && ws_disp_w() <= 384)) && + (g_bg2d_host_size || v.pad_left || v.pad_right) && draw_area_top < 512 && + (g_bg2d_host_size + ? clear_generation[draw_area_top] != g_bg2d_host_generation + : clear_frame[draw_area_top] != (uint32_t)s_frame_count + 1u) && ws_is_fb_base(draw_area_left)) { clear_frame[draw_area_top] = (uint32_t)s_frame_count + 1u; + clear_generation[draw_area_top] = g_bg2d_host_generation; gr_wide_clear_margins((int)draw_area_left, (int)draw_area_top, (int)draw_area_bottom - (int)draw_area_top + 1, 0, 3); } @@ -5625,7 +5686,9 @@ static void gp0_execute_command(void) { int32_t hud_delta; /* Existing HUD placement stays fixed in the physical frame. Only the * wide world mirror receives the camera-edge origin and room clip. */ - if (ws_nw_left_hud_packet() || ws_nw_explicit_hud_delta(&hud_delta)) + if (ws_nw_left_hud_packet() || + (!(g_bg2d_host_size && ws_tagged_world_primitive()) && + ws_nw_explicit_hud_delta(&hud_delta))) v.shift = v.pad_left = v.pad_right = 0; gr_wide_set_view(ws_view_enabled(), v.shift, v.pad_left, v.pad_right); } diff --git a/runtime/tests/test_gl_readback_region.c b/runtime/tests/test_gl_readback_region.c index 73154344..24f9e7d1 100644 --- a/runtime/tests/test_gl_readback_region.c +++ b/runtime/tests/test_gl_readback_region.c @@ -71,6 +71,15 @@ int main(int argc,char **argv){ printf("driver=%s renderer=%s scale=%d\n",glGetString(GL_VERSION),glGetString(GL_RENDERER),scale); glb_set_draw_area(0,0,1023,511);glb_set_mask_bits(0,0);glb_set_semi_transparency(0,0);glb_set_color_modulation(128,128,128,1); verify("initial upload"); + /* Adaptive backdrop reflection must retain every edge texel at both native + * and supersampled scales; the ordinary rect stays forward-facing. */ + for(int x=0;x<16;++x) glb_vram_write(512+x,0,(uint16_t)(0x400+x+1)); + glb_draw_textured_rect(700,200,16,1,0,0,0,0,0x108); + glb_draw_textured_rect_scaled(720,200,16,1,15,0,-1,1,0,0,0x108); + for(int x=0;x<16;++x) { + check(glb_vram_read(700+x,200)==0x401+x,"forward panorama texel"); + check(glb_vram_read(720+x,200)==0x410-x,"reflected panorama texel"); + } glb_draw_flat_rect(1020,511,1,1,0x7fff); check(glb_vram_read(1020,511)==0x7fff,"test pixel value"); GlCohEvent event;int found=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 a6c33123..ffdc089f 100644 --- a/runtime/tests/test_gpu_textured_dot_nw_shift_exec.c +++ b/runtime/tests/test_gpu_textured_dot_nw_shift_exec.c @@ -15,6 +15,16 @@ uint32_t g_debug_current_func_addr; uint32_t g_debug_last_store_pc; CPUState *debug_cpu_ptr; int g_psx_vram_dirty_tracking; +/* This fixture uses captured draw calls, with no immutable texture banks. */ +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; } +uint16_t mod_texture_packet_bank(uint32_t p, const uint32_t *words, uint32_t n) { + (void)p; (void)words; (void)n; return 0; +} +int mod_texture_packet_precision(uint32_t p, float q[3], float xy[6]) { + (void)p; (void)q; (void)xy; return 0; +} static uint32_t test_ram[0x00200000u / 4u]; @@ -24,6 +34,7 @@ static struct { 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; static uint32_t pack_vertex(int16_t x, int16_t y) { return (uint16_t)x | ((uint32_t)(uint16_t)y << 16); @@ -313,7 +324,11 @@ 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) { - (void)x; (void)y; (void)w; (void)h; (void)u0; (void)v0; (void)u1; + 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) { diff --git a/runtime/tests/test_ws_view_anchor.c b/runtime/tests/test_ws_view_anchor.c index 0e1e6a52..8f78a422 100644 --- a/runtime/tests/test_ws_view_anchor.c +++ b/runtime/tests/test_ws_view_anchor.c @@ -42,6 +42,19 @@ static void draw(int shift, int pad_left, int pad_right) { } int main(void) { + /* Same framing at 32:9 / 64:9 and beyond; finite rooms keep symmetric + * padding while a full stage transfers the reveal to the available side. */ + const int extras[] = {267, 693, 1600}; + for (unsigned i = 0; i < sizeof extras / sizeof extras[0]; ++i) { + int e = extras[i]; + WsViewAnchor left = ws_view_anchor(e,0,0,5120); + WsViewAnchor right = ws_view_anchor(e,5120,0,5120); + WsViewAnchor room = ws_view_anchor(e,200,200,200); + assert(left.left==0 && left.right==2*e && left.shift==-e); + assert(right.left==2*e && right.right==0 && right.shift==e); + assert(room.left==0 && room.right==0 && room.shift==0); + assert(room.pad_left==e && room.pad_right==e); + } check(0, 0, 1000, 0, 106, -53); check(20, 0, 1000, 20, 86, -33); check(500, 0, 1000, 53, 53, 0); diff --git a/runtime/tests/test_ws_view_anchor_gpu.c b/runtime/tests/test_ws_view_anchor_gpu.c index 8206eb9a..3d0a81a9 100644 --- a/runtime/tests/test_ws_view_anchor_gpu.c +++ b/runtime/tests/test_ws_view_anchor_gpu.c @@ -63,6 +63,43 @@ int main(void) { ws_mode = 0; assert(psx_ws_bg2d_cols(21) == 21); assert(psx_ws_bg2d_startx(0) == 0); + /* The opt-in host arena keeps guest loops and the ring native, while + * its packet coordinates remain signed 16-bit beyond PS1's +/-1024. */ + configure_native_wide_16_9(); + gpu_ws_bg2d_set_host_arena(0x80100000u, 64u); /* mapped fixture memory */ + half_at(0x10001c, 0x4247u); half_at(0x10001e, 0x5836u); + gp0_cmd_source_addr = 0x00100004u; + int32_t px, py; + parse_vertex(pack_vertex(1400, 20), &px, &py); + assert(px == 1400 && py == 20); + /* Reflection reverses the exact 16 texels, without reading the adjacent + * tile's first column. Its metadata is confined to the registered arena. */ + half_at(0x10001e, 0xd836u); + gp0_cmd_buf[0]=0x7d808080u; + gp0_cmd_buf[1]=pack_vertex(400,20); + gp0_cmd_buf[2]=0x79804000u; + gp0_exec_textured_16x16(); + assert(last_scaled_rect.calls==1 && last_scaled_rect.x==400); + assert(last_scaled_rect.w==16 && last_scaled_rect.h==16); + assert(last_scaled_rect.u0==15 && last_scaled_rect.u1==-1); + half_at(0x10001e, 0x5836u); + gp0_cmd_source_addr = 0x00010004u; + parse_vertex(pack_vertex(1400, 20), &px, &py); + assert(px == -648 && py == 20); /* Ordinary hardware packet unchanged. */ + gpu_ws_tag_world_primitive(0x80010000u, 1); + parse_vertex(pack_vertex(1400, 20), &px, &py); + assert(px == 1400 && py == 20); + parse_vertex(pack_vertex(-1400, 20), &px, &py); + assert(px == -1400 && py == 20); + gpu_ws_tag_world_primitive(0x80010000u, 0); + parse_vertex(pack_vertex(1400, 20), &px, &py); + assert(px == -648 && py == 20); + assert(psx_ws_bg2d_cols(21) == 21); + assert(psx_ws_bg2d_startcol(3, 63) == 3); + assert(psx_ws_bg2d_startx(-5) == -5); + assert(psx_ws_bg2d_stream_left(100) == 100); + assert(psx_ws_bg2d_stream_right(436) == 436); + gpu_ws_bg2d_set_host_arena(0, 0); puts("ws_view_anchor_gpu: sampled camera, per-layer coverage, packet origin, disabled identity PASS"); return 0; }