mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Revert PR #327 interlaced row submission to restore retail BIOS boot
Matched Ape Escape retail SCPH1001 LLE builds identify0661ce77as the visible boot regression: parent155e269breaches game code, while0661ce77stalls near BIOS frame 375. Revert its four dependent follow-ups too; retain PR #326 readback bounds and PR #328 Vulkan aspect changes. The row-by-row raster dispatcher amplifies an existing malformed DMA list into massive GL work. Keep the broader CPU/DMA arbitration experiment separate for compatibility testing. Document attribution, validation, and the temporarily restored interlace limitation. Validation: full Ape target reaches title/menu and frame 11669 on retail LLE; OpenBIOS control reaches frame 3701. Eight focused tests pass; retained GL readback fixture passes 71 checks at both 1x and 4x after linking local SDL with iconv. Beads: beads-iisy; DMA follow-up: beads-eio.3.122.
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
# Interlaced primitive drawing
|
||||
|
||||
The GPU preserves one row parity while drawing in 480-line interlaced mode
|
||||
when GP0(E1h) prohibits drawing to the display area. The display origin and
|
||||
current field select that parity. Progressive modes and the draw-to-display
|
||||
override draw both parities. Fills, copies and VRAM transfers keep both fields.
|
||||
|
||||
`gpu_raster_skipped_row()` reads current GPU state for each primitive. The
|
||||
renderer facade applies the field mask with native-row clip rectangles and
|
||||
restores the original clip. This also preserves the corresponding rows in
|
||||
scaled surfaces. Each clipped triangle retains its precision and perspective
|
||||
metadata, which hardware backends otherwise consume on the first submission.
|
||||
The facade counts each original perspective triangle once; repeated backend
|
||||
metadata setup does not add diagnostic hits. No title address or title detection
|
||||
selects this behavior.
|
||||
|
||||
Pro Pinball: Timeshock! USA SLUS-00639 repeatedly draws and reads a test pixel
|
||||
at (1020,511) during startup. Frozen source 63446a28 always returned the drawn
|
||||
pixel and stayed black. This correction lets the guest's existing field test
|
||||
finish. The same disc, BIOS and settings then reach the table.
|
||||
|
||||
The field latch uses the runtime's existing vertical-blank model. This change
|
||||
does not claim scanline-accurate timing. Row clipping can increase submissions
|
||||
for large interlaced primitives; no general performance claim is made.
|
||||
|
||||
## Verification
|
||||
|
||||
Configure `runtime/` with `BUILD_TESTING=ON`, then run
|
||||
`ctest --test-dir <build> -R gpu_interlace_render_test --output-on-failure`.
|
||||
The fixture covers the mode predicate, origin/field parity, all nine primitive
|
||||
families, clip restoration, transfers, mode changes, scaled output and
|
||||
triangle metadata consumed by a backend. The original renderer fails the
|
||||
same row-preservation assertions.
|
||||
|
||||
## Prior art
|
||||
|
||||
- [PSX-SPX GPU specification](https://psx-spx.consoledev.net/graphicsprocessingunitgpu/)
|
||||
describes interlace affecting draw commands.
|
||||
- [DuckStation difficult games](https://github.com/stenzek/duckstation/wiki/Difficult-to-Emulate-Games)
|
||||
identifies required line skipping for Pro Pinball startup.
|
||||
|
||||
These sources supplied behavioral evidence. The implementation and regression
|
||||
fixture were written independently. Exact title run receipts and route limits
|
||||
are maintained by the Timeshock correction project.
|
||||
|
||||
Precise vertices use signed 16.16 positions. Row dispatch covers the union of
|
||||
integer fallback bounds and precise vertex bounds, with one row for rounding.
|
||||
Tests compare native and enhanced masked pixels with unmasked images when
|
||||
precise positions cross the integer bounds, including negative coordinates.
|
||||
A tiny precise triangle must submit three row clips rather than 256.
|
||||
|
||||
Flat overlay rectangles also submit one-row geometry while field masking is
|
||||
active. This keeps the mask when a wide backend intentionally replaces its
|
||||
scissor. Non-interlaced overlays retain the original geometry and policy.
|
||||
A backend fixture that ignores the scissor tests both field parities, original
|
||||
progressive behavior, and clip restoration. This is not Vulkan driver testing.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Retail BIOS freeze: PR #327 rollback
|
||||
|
||||
Issue: `beads-iisy`. Investigation and validation: 2026-09-07.
|
||||
|
||||
## Confirmed introduction
|
||||
|
||||
The visible retail SCPH1001 boot regression starts at
|
||||
`0661ce7773c3b82ea441ac9c82b0df08b4ea1563`, "Preserve active display field
|
||||
during interlaced raster drawing", authored by Alexbeav in
|
||||
[PR #327](https://github.com/mstan/psxrecomp/pull/327).
|
||||
It reached master through [PR #334](https://github.com/mstan/psxrecomp/pull/334),
|
||||
merge `421f58ec8c2a07f2cd608c1afa507404076e9882`.
|
||||
|
||||
Matched historical builds used unchanged runtime sources, the same generated
|
||||
Ape Escape and SCPH1001 code, BIOS/disc, game config, and OpenGL 2x settings.
|
||||
A minimal CMake wrapper adapted the current game's build declaration to the
|
||||
older runtime helper. All runs explicitly set `PSX_BIOS_HLE=0`; logs confirmed
|
||||
`bios_backend=LLE`, `bios_boot=LLE (real intro)`, and `image=SCPH-1001`.
|
||||
|
||||
| Runtime revision | Result |
|
||||
| --- | --- |
|
||||
| `47bda817`, parent of original DMA slicing | Game code; normal close at frame 3979 |
|
||||
| `8af48ae9`, PR #277 DMA slicing | Game code; normal close at frame 2959 |
|
||||
| `99f775c8`, PR #304 before word-cost change | Game code; normal close at frame 3468 |
|
||||
| `bc88c06c`, PR #304 word-cost change | Game code; normal close at frame 3845 |
|
||||
| `155e269b`, exact parent of `0661ce77` | Game code; normal close at frame 2192 |
|
||||
| `0661ce77`, first PR #327 commit | Severe Sony-screen stall around frames 375-376 |
|
||||
| `39ebb06f`, master immediately before #334 | Game code; normal close at frame 2652 |
|
||||
| `421f58ec`, merged #334 | Same severe Sony-screen stall around frames 374-376 |
|
||||
|
||||
This rules out naming #277 or #304 as the demonstrated introduction of the
|
||||
visible freeze merely because they introduced/refined asynchronous DMA.
|
||||
|
||||
## Mechanism and scope
|
||||
|
||||
`GR_RASTER_ROWS` resubmits a primitive once per permitted scanline, changing
|
||||
the backend draw area each time. That multiplies backend submissions and
|
||||
flushes in the retail BIOS's interlaced display mode.
|
||||
|
||||
A separate DMA defect makes this especially costly: the CPU can overwrite a
|
||||
primitive header before the asynchronous DMA walker consumes it. On master,
|
||||
BIOS memcpy at `BFC188D8` clears packet `00138CA8` before DMA node 1023 reads
|
||||
its link, sending the walker into a malformed `0 -> 1A0000 -> 0` list. The
|
||||
per-row renderer magnifies the resulting bogus primitives into massive GL
|
||||
work. The apparent freeze can advance roughly a frame over many seconds;
|
||||
headless progress alone misses the user-visible failure.
|
||||
|
||||
The immediate fix rolls back only #327's five dependent commits:
|
||||
`0661ce77`, `43909718`, `7c4b0300`, `5aee9c83`, and `6fcfb8ad`.
|
||||
PR #326's bounded GL readback and #328's Vulkan aspect-ratio changes remain.
|
||||
DMA scheduling, BIOS code, generated code, and launcher settings are unchanged.
|
||||
|
||||
This restores the previous interlaced raster behavior, including its missing
|
||||
active-field preservation. A replacement should avoid per-row backend draw
|
||||
submission and validate real retail BIOS boot as well as field correctness.
|
||||
CPU/DMA arbitration is a separate follow-up, `beads-eio.3.122`; its prototype
|
||||
needs Vampire Hunter D/Spot Goes to Hollywood compatibility testing before
|
||||
integration. Do not mix that wider timing change into this rollback.
|
||||
|
||||
## Rollback validation
|
||||
|
||||
The full Ape game target (SDL3, launcher compiled in, OpenGL, Vulkan compiled
|
||||
in) passes retail intro/disc handoff, visibly reaches the title/menu, and
|
||||
closes normally at frame 11669. The same rollback executable also passes the
|
||||
OpenBIOS LLE control run and closes normally at frame 3701.
|
||||
Use the owned SCPH1001 BIOS and Ape disc with:
|
||||
|
||||
```powershell
|
||||
$env:PSX_BIOS_HLE = '0'
|
||||
& .\ApeEscapeRecomp.exe --no-launcher --bios <SCPH1001.BIN> --disc <Ape.cue>
|
||||
```
|
||||
|
||||
Ensure the game's `game.toml` is staged beside the validation executable.
|
||||
Judge actual windowed boot, not just a headless frame threshold.
|
||||
|
||||
Passed: `gl_readback_runner_test`, `psx_cyc_batch_test`,
|
||||
`dma_gpu_linked_list_timing_test`, `psx_cycle_event_boundaries_test`,
|
||||
`ws_prepass_guard_test`, `gpu_primitive_reject_test`, `gpu_sw_edges_test`,
|
||||
and `gpu_vk_upload_alignment_test`.
|
||||
|
||||
The real hidden-GL readback fixture passes `checks=71 failures=0` at both 1x
|
||||
and 4x on NVIDIA 616.86. Its stock runner initially failed to link because
|
||||
the local MSYS2 SDL static library depends on iconv; rerunning the recorded
|
||||
link command with `-liconv` allowed both unmodified fixture runs to pass.
|
||||
@@ -467,15 +467,6 @@ if(BUILD_TESTING)
|
||||
target_include_directories(gpu_sw_edges_test PRIVATE src)
|
||||
add_test(NAME gpu_sw_edges_test COMMAND gpu_sw_edges_test)
|
||||
|
||||
add_executable(gpu_interlace_render_test
|
||||
tests/test_gpu_interlace_render.c src/gpu_render.c
|
||||
src/gpu_sw_renderer.c src/gpu_vram_dirty.c)
|
||||
target_include_directories(gpu_interlace_render_test PRIVATE include)
|
||||
if(NOT MSVC)
|
||||
target_link_libraries(gpu_interlace_render_test PRIVATE m)
|
||||
endif()
|
||||
add_test(NAME gpu_interlace_render_test COMMAND gpu_interlace_render_test)
|
||||
|
||||
add_executable(gpu_vk_upload_alignment_test
|
||||
tests/test_gpu_vk_upload_alignment.c)
|
||||
target_include_directories(gpu_vk_upload_alignment_test PRIVATE src)
|
||||
|
||||
@@ -17,7 +17,6 @@ extern "C" {
|
||||
|
||||
void gpu_init(void);
|
||||
uint32_t gpu_read_gpustat(void); /* 0x1F801814 read */
|
||||
int gpu_raster_skipped_row(void); /* -1: all rows; otherwise preserved field parity */
|
||||
uint32_t gpu_read_gpuread(void); /* 0x1F801810 read */
|
||||
void gpu_write_gp0(uint32_t val); /* 0x1F801810 write */
|
||||
void gpu_write_gp1(uint32_t val); /* 0x1F801814 write */
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef PSX_GPU_INTERLACE_H
|
||||
#define PSX_GPU_INTERLACE_H
|
||||
|
||||
/* GP0 raster primitives skip the active field only in 480i mode with
|
||||
* drawing-to-display prohibited. VRAM transfer commands do not use this rule.
|
||||
* Return -1 for progressive drawing, otherwise the native VRAM row parity
|
||||
* that must remain unchanged. The display origin participates in that parity. */
|
||||
static inline int psx_gpu_raster_skipped_row(unsigned interlace,
|
||||
unsigned high_vertical_resolution, unsigned draw_to_display,
|
||||
unsigned display_y, unsigned field) {
|
||||
if (!interlace || !high_vertical_resolution || draw_to_display)
|
||||
return -1;
|
||||
return (int)((display_y + field) & 1u);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -54,8 +54,6 @@ void gr_set_precise_triangle(int enabled,
|
||||
* perspective_texturing). q[i] is the normalized 1/z homogeneous weight at
|
||||
* vertex i. enabled == 0 restores the PS1's affine UV interpolation. */
|
||||
void gr_set_perspective_triangle(int enabled, float q0, float q1, float q2);
|
||||
/* Original perspective-qualified triangles since process startup. */
|
||||
uint32_t gr_perspective_triangle_count(void);
|
||||
|
||||
/* Primitives */
|
||||
void gr_fill_rect(int x, int y, int w, int h, uint16_t color);
|
||||
|
||||
@@ -34,6 +34,7 @@ void sw_set_precise_triangle(int enabled,
|
||||
* homogeneous interpolation weights associated with each screen vertex. */
|
||||
void sw_set_perspective_triangle(int enabled,
|
||||
float q0, float q1, float q2);
|
||||
uint32_t sw_perspective_triangle_count(void);
|
||||
|
||||
/* Texture filtering. 0 = nearest (native PSX look, default), 1 = bilinear
|
||||
* (smooths textures/2D backgrounds; blends in RGB after the CLUT lookup,
|
||||
|
||||
+1
-10
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
#include "gpu.h"
|
||||
#include "gpu_interlace.h"
|
||||
#include "display_scanout.h"
|
||||
#include "pgxp.h"
|
||||
#include "mod_memory.h"
|
||||
@@ -2733,14 +2732,6 @@ void gpu_init(void) {
|
||||
|
||||
/* ---- GPUSTAT read (0x1F801814) ---- */
|
||||
|
||||
/* Primitive rasterization must leave the active display field untouched in
|
||||
* 480-line interlaced mode unless GP0(E1h).10 permits drawing to it.
|
||||
* Transfers, fills, and copies are not raster primitives. */
|
||||
int gpu_raster_skipped_row(void) {
|
||||
return psx_gpu_raster_skipped_row(vertical_interlace, vres,
|
||||
draw_to_display, display_area_y, lcf);
|
||||
}
|
||||
|
||||
uint32_t gpu_read_gpustat(void) {
|
||||
/* Advance vblank when polled enough times from within a single function.
|
||||
* This handles BIOS VSYNC wait loops that poll LCF in tight loops.
|
||||
@@ -3362,7 +3353,7 @@ int gpu_texture_correction_enabled(void) {
|
||||
}
|
||||
|
||||
uint32_t gpu_texture_correction_hits(void) {
|
||||
return gr_perspective_triangle_count();
|
||||
return sw_perspective_triangle_count();
|
||||
}
|
||||
|
||||
/* Per-vertex precise positions (PGXP, docs/ENHANCEMENTS.md G1). Each of the three
|
||||
|
||||
+13
-92
@@ -7,11 +7,10 @@
|
||||
* gl_backend_get(); if that returns NULL (GL unavailable / init failed) we
|
||||
* stay on software so a misconfigured [video] renderer never bricks boot.
|
||||
*
|
||||
* Primitive dispatch also applies the GPU's interlaced field mask through
|
||||
* the backend clip, at native VRAM row granularity. */
|
||||
* The software backend table points straight at the existing sw_* functions,
|
||||
* so the software path is byte-for-byte unchanged. */
|
||||
|
||||
#include "gpu_render.h"
|
||||
#include "gpu.h"
|
||||
#include "gpu_sw_renderer.h"
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -65,11 +64,6 @@ extern const GpuRenderBackend *vk_backend_get(void);
|
||||
|
||||
static const GpuRenderBackend *g_b = &SW_BACKEND;
|
||||
static GrBackend g_effective = GR_BACKEND_SOFTWARE;
|
||||
/* Backends consume triangle metadata on submission. Re-arm it for each
|
||||
* clipped row belonging to the same interlaced primitive. */
|
||||
static int g_pc_enabled, g_pq_enabled;
|
||||
static int32_t g_pc[6];
|
||||
static float g_pq[3];
|
||||
|
||||
void gr_set_backend(GrBackend backend) {
|
||||
if (backend == GR_BACKEND_OPENGL) {
|
||||
@@ -111,121 +105,48 @@ void gr_set_texture_window(uint32_t raw) { g_b->set_texture_window(r
|
||||
void gr_set_color_modulation(int r, int g, int b, int raw) { g_b->set_color_modulation(r, g, b, raw); }
|
||||
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) {
|
||||
g_pc_enabled = enabled;
|
||||
g_pc[0]=x0; g_pc[1]=y0; g_pc[2]=x1; g_pc[3]=y1; g_pc[4]=x2; g_pc[5]=y2;
|
||||
if (g_b->set_precise_triangle)
|
||||
g_b->set_precise_triangle(enabled, x0, y0, x1, y1, x2, y2);
|
||||
}
|
||||
static uint32_t g_perspective_triangles;
|
||||
uint32_t gr_perspective_triangle_count(void) { return g_perspective_triangles; }
|
||||
void gr_set_perspective_triangle(int enabled, float q0, float q1, float q2) {
|
||||
/* Count the original triangle, not per-row backend metadata re-arms. */
|
||||
if (g_b->set_perspective_triangle && enabled && q0 > 0.0f && q1 > 0.0f && q2 > 0.0f)
|
||||
++g_perspective_triangles;
|
||||
g_pq_enabled=enabled; g_pq[0]=q0; g_pq[1]=q1; g_pq[2]=q2;
|
||||
if (g_b->set_perspective_triangle)
|
||||
g_b->set_perspective_triangle(enabled, q0, q1, q2);
|
||||
}
|
||||
void gr_fill_rect(int x, int y, int w, int h, uint16_t c) { g_b->fill_rect(x, y, w, h, c); }
|
||||
void gr_copy_rect(int sx, int sy, int dx, int dy, int w, int h) { g_b->copy_rect(sx, sy, dx, dy, w, h); }
|
||||
/* Apply field masking through each backend's native-coordinate clip. This
|
||||
* preserves the same rows in canonical VRAM and supersampled surfaces, and
|
||||
* leaves non-raster transfers untouched. Bounds are already offset by gpu.c.
|
||||
* Query live GPU state per primitive so reset/restore/mode changes cannot
|
||||
* leave a stale renderer-side field latch. */
|
||||
static int gr_min(int a, int b) { return a < b ? a : b; }
|
||||
static int gr_max(int a, int b) { return a > b ? a : b; }
|
||||
static void gr_rearm_triangle(void) {
|
||||
if (g_b->set_precise_triangle)
|
||||
g_b->set_precise_triangle(g_pc_enabled,g_pc[0],g_pc[1],g_pc[2],g_pc[3],g_pc[4],g_pc[5]);
|
||||
if (g_b->set_perspective_triangle)
|
||||
g_b->set_perspective_triangle(g_pq_enabled,g_pq[0],g_pq[1],g_pq[2]);
|
||||
}
|
||||
/* Keep both integer fallback rows and enhanced fixed-point rows. The one-row
|
||||
* margin covers edge rounding without submitting a full VRAM-height strip. */
|
||||
static void gr_precise_row_bounds(int *lo, int *hi) {
|
||||
for (int i = 1; i < 6; i += 2) {
|
||||
int y = g_pc[i] / 65536;
|
||||
int rem = g_pc[i] % 65536;
|
||||
int floor_y = y - (rem < 0);
|
||||
int ceil_y = y + (rem > 0);
|
||||
*lo = gr_min(*lo, floor_y - 1);
|
||||
*hi = gr_max(*hi, ceil_y + 1);
|
||||
}
|
||||
}
|
||||
#define GR_TRIANGLE_ROWS(ylo, yhi, draw_call) do { \
|
||||
/* Precise positions can cross the integer primitive bounds. */ \
|
||||
int lo = (ylo), hi = (yhi); \
|
||||
if (g_pc_enabled) gr_precise_row_bounds(&lo, &hi); \
|
||||
GR_RASTER_ROWS(lo, hi, (gr_rearm_triangle(), draw_call)); \
|
||||
g_pc_enabled = g_pq_enabled = 0; \
|
||||
gr_rearm_triangle(); \
|
||||
} while (0)
|
||||
#define GR_RASTER_ROWS(ylo, yhi, draw_call) do { \
|
||||
int skip = gpu_raster_skipped_row(); \
|
||||
if (skip < 0) { draw_call; } else { \
|
||||
int cx1, cy1, cx2, cy2; \
|
||||
g_b->get_draw_area(&cx1, &cy1, &cx2, &cy2); \
|
||||
int first = gr_max(gr_max((ylo), cy1), 0); \
|
||||
int last = gr_min(gr_min((yhi), cy2), 511); \
|
||||
for (int row = first; row <= last; ++row) { \
|
||||
if ((row & 1) == skip) continue; \
|
||||
g_b->set_draw_area(cx1, row, cx2, row); \
|
||||
draw_call; \
|
||||
} \
|
||||
g_b->set_draw_area(cx1, cy1, cx2, cy2); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void gr_draw_flat_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint16_t c) {
|
||||
GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_flat_triangle(x0, y0, x1, y1, x2, y2, c));
|
||||
g_b->draw_flat_triangle(x0, y0, x1, y1, x2, y2, c);
|
||||
}
|
||||
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) {
|
||||
GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_gouraud_triangle(x0, y0, c0, x1, y1, c1, x2, y2, c2));
|
||||
g_b->draw_gouraud_triangle(x0, y0, c0, x1, y1, c1, x2, y2, 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) {
|
||||
GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_textured_triangle(x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2,
|
||||
clut_x, clut_y, texpage));
|
||||
g_b->draw_textured_triangle(x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2,
|
||||
clut_x, clut_y, texpage);
|
||||
}
|
||||
void gr_draw_shaded_textured_triangle(int x0, int y0, int u0, int v0, uint32_t c0,
|
||||
int x1, int y1, int u1, int v1, uint32_t c1,
|
||||
int x2, int y2, int u2, int v2, uint32_t c2,
|
||||
uint16_t clut_x, uint16_t clut_y,
|
||||
uint16_t texpage, int raw) {
|
||||
GR_TRIANGLE_ROWS(gr_min(y0, gr_min(y1, y2)), gr_max(y0, gr_max(y1, y2)), g_b->draw_shaded_textured_triangle(x0, y0, u0, v0, c0, x1, y1, u1, v1, c1,
|
||||
x2, y2, u2, v2, c2, clut_x, clut_y, texpage, raw));
|
||||
}
|
||||
/* Wide flat-overlay backends intentionally replace the clip with a full
|
||||
* surface scissor. Submit row-height geometry too, so the field mask survives
|
||||
* that path while ordinary full-screen overlays keep their existing policy. */
|
||||
static void gr_draw_flat_rect_row(int x, int w, uint16_t c) {
|
||||
int x1, y1, x2, y2;
|
||||
g_b->get_draw_area(&x1, &y1, &x2, &y2);
|
||||
g_b->draw_flat_rect(x, y1, w, 1, c);
|
||||
}
|
||||
void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t c) {
|
||||
if (gpu_raster_skipped_row() < 0) {
|
||||
g_b->draw_flat_rect(x, y, w, h, c);
|
||||
return;
|
||||
}
|
||||
GR_RASTER_ROWS(y, y + h - 1, gr_draw_flat_rect_row(x, w, c));
|
||||
g_b->draw_shaded_textured_triangle(x0, y0, u0, v0, c0, x1, y1, u1, v1, c1,
|
||||
x2, y2, u2, v2, c2, clut_x, clut_y, texpage, raw);
|
||||
}
|
||||
void gr_draw_flat_rect(int x, int y, int w, int h, uint16_t c) { g_b->draw_flat_rect(x, y, w, h, c); }
|
||||
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) {
|
||||
GR_RASTER_ROWS(y, y + h - 1, g_b->draw_textured_rect(x, y, w, h, u, v, clut_x, clut_y, texpage));
|
||||
g_b->draw_textured_rect(x, y, w, h, u, v, clut_x, clut_y, 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) {
|
||||
GR_RASTER_ROWS(y, y + h - 1, g_b->draw_textured_rect_scaled(x, y, w, h, u0, v0, u1, v1, clut_x, clut_y, texpage));
|
||||
}
|
||||
void gr_draw_line(int x0, int y0, int x1, int y1, uint16_t c) {
|
||||
GR_RASTER_ROWS(gr_min(y0, y1), gr_max(y0, y1), g_b->draw_line(x0, y0, x1, y1, c));
|
||||
g_b->draw_textured_rect_scaled(x, y, w, h, u0, v0, u1, v1, clut_x, clut_y, texpage);
|
||||
}
|
||||
void gr_draw_line(int x0, int y0, int x1, int y1, uint16_t c) { g_b->draw_line(x0, y0, x1, y1, c); }
|
||||
void gr_draw_shaded_line(int x0, int y0, uint16_t c0, int x1, int y1, uint16_t c1) {
|
||||
GR_RASTER_ROWS(gr_min(y0, y1), gr_max(y0, y1), g_b->draw_shaded_line(x0, y0, c0, x1, y1, c1));
|
||||
g_b->draw_shaded_line(x0, y0, c0, x1, y1, c1);
|
||||
}
|
||||
int gr_render_display(uint32_t *o, int p, int dx, int dy, int dw, int dh) {
|
||||
return g_b->render_display(o, p, dx, dy, dw, dh);
|
||||
|
||||
@@ -56,6 +56,7 @@ static int g_precise_valid = 0;
|
||||
static int32_t g_precise_x16[3], g_precise_y16[3];
|
||||
static int g_perspective_valid = 0;
|
||||
static float g_perspective_q[3];
|
||||
static uint32_t g_perspective_triangles = 0;
|
||||
|
||||
/* ---- Native-wide compositor (separate present surfaces) ------------------
|
||||
* Canonical VRAM stays faithful. For an opted-in wide game we ADDITIONALLY
|
||||
@@ -521,6 +522,11 @@ void sw_set_perspective_triangle(int enabled,
|
||||
g_perspective_q[0] = q0;
|
||||
g_perspective_q[1] = q1;
|
||||
g_perspective_q[2] = q2;
|
||||
if (g_perspective_valid) g_perspective_triangles++;
|
||||
}
|
||||
|
||||
uint32_t sw_perspective_triangle_count(void) {
|
||||
return g_perspective_triangles;
|
||||
}
|
||||
|
||||
static inline int precise_scaled(int axis, int vertex, int fallback, int scale) {
|
||||
|
||||
@@ -2771,7 +2771,9 @@ static void vkb_set_precise_triangle(int enabled,
|
||||
static void vkb_set_perspective_triangle(int enabled, float q0, float q1, float q2) {
|
||||
s_pq_valid = (enabled && q0 > 0.0f && q1 > 0.0f && q2 > 0.0f) ? 1 : 0;
|
||||
s_pq[0] = q0; s_pq[1] = q1; s_pq[2] = q2;
|
||||
/* Keep fallback software metadata in sync; the facade counts triangles. */
|
||||
/* The corrected-triangle tally lives in the software rasterizer and backs
|
||||
* gpu_texture_correction_hits() — the "is this doing anything on this
|
||||
* title" counter. Forward so it reads the same on every backend. */
|
||||
sw_set_perspective_triangle(enabled, q0, q1, q2);
|
||||
}
|
||||
/* The override describes exactly one triangle; drop it once submitted so a
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
#include "gpu_render.h"
|
||||
#include "gpu_interlace.h"
|
||||
#include "gpu_sw_renderer.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static uint16_t vram[1024 * 512];
|
||||
static uint32_t hires[32*4*32*4], reference_hires[32*4*32*4];
|
||||
static uint16_t reference_vram[32*32];
|
||||
static int skipped_row;
|
||||
static int failures;
|
||||
static int mock_enabled, mock_pc, mock_pq, mock_submissions, mock_bad;
|
||||
static void mock_precise(int e,int32_t x0,int32_t y0,int32_t x1,int32_t y1,int32_t x2,int32_t y2) {
|
||||
(void)x0;(void)y0;(void)x1;(void)y1;(void)x2;(void)y2;mock_pc=e;
|
||||
}
|
||||
static void mock_perspective(int e,float q0,float q1,float q2) {
|
||||
(void)q0;(void)q1;(void)q2;mock_pq=e;
|
||||
}
|
||||
static void mock_triangle(int x0,int y0,int x1,int y1,int x2,int y2,uint16_t c) {
|
||||
(void)x0;(void)y0;(void)x1;(void)y1;(void)x2;(void)y2;(void)c;
|
||||
++mock_submissions;if(!mock_pc || !mock_pq)++mock_bad;
|
||||
mock_pc=mock_pq=0; /* GPU backends consume these per submission. */
|
||||
}
|
||||
/* Wide overlay paths can deliberately ignore the draw-area scissor. */
|
||||
static unsigned overlay_rows[32];
|
||||
static void mock_flat_overlay(int x,int y,int w,int h,uint16_t c) {
|
||||
(void)x;(void)w;(void)c;
|
||||
for (int row=y; row<y+h && row<32; ++row)
|
||||
if (row>=0) ++overlay_rows[row];
|
||||
}
|
||||
static const GpuRenderBackend mock_backend={
|
||||
.name="metadata fixture",.get_draw_area=sw_get_draw_area,.set_draw_area=sw_set_draw_area,
|
||||
.set_precise_triangle=mock_precise,.set_perspective_triangle=mock_perspective,
|
||||
.draw_flat_triangle=mock_triangle,.draw_flat_rect=mock_flat_overlay
|
||||
};
|
||||
/* This fixture keeps optional widescreen presentation disabled. */
|
||||
int g_ws_bd_stretch_pct, g_ws_bd_stretch_on;
|
||||
int psx_ws_prim_in_backdrop(void) { return 0; }
|
||||
int gpu_raster_skipped_row(void) { return skipped_row; }
|
||||
const GpuRenderBackend *gl_backend_get(void) { return mock_enabled ? &mock_backend : NULL; }
|
||||
const GpuRenderBackend *vk_backend_get(void) { return NULL; }
|
||||
#define CHECK(c) do { if (!(c)) { \
|
||||
fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #c); ++failures; \
|
||||
} } while (0)
|
||||
|
||||
static void reset_canvas(void) {
|
||||
skipped_row = -1;
|
||||
gr_fill_rect(0, 0, 32, 32, 0);
|
||||
gr_set_draw_area(0, 0, 31, 31);
|
||||
gr_set_semi_transparency(0, 0);
|
||||
gr_set_mask_bits(0, 0);
|
||||
gr_set_color_modulation(128, 128, 128, 1);
|
||||
}
|
||||
|
||||
static void draw_kind(int kind) {
|
||||
switch (kind) {
|
||||
case 0: gr_draw_flat_rect(2, 2, 8, 8, 0x7FFF); break;
|
||||
case 1: gr_draw_flat_triangle(2, 2, 10, 2, 2, 10, 0x7FFF); break;
|
||||
case 2: gr_draw_gouraud_triangle(2, 2, 0x7FFF, 10, 2, 0x7FFF, 2, 10, 0x7FFF); break;
|
||||
case 3: gr_draw_line(2, 2, 2, 9, 0x7FFF); break;
|
||||
case 4: gr_draw_shaded_line(2, 2, 0x7FFF, 2, 9, 0x7FFF); break;
|
||||
case 5: gr_draw_textured_rect(2, 2, 8, 8, 0, 0, 0, 0, 0x104); break;
|
||||
case 6: gr_draw_textured_rect_scaled(2, 2, 8, 8, 0, 0, 8, 8, 0, 0, 0x104); break;
|
||||
case 7: gr_draw_textured_triangle(2, 2, 0, 0, 10, 2, 8, 0, 2, 10, 0, 8, 0, 0, 0x104); break;
|
||||
case 8: gr_draw_shaded_textured_triangle(2, 2, 0, 0, 0x808080, 10, 2, 8, 0, 0x808080,
|
||||
2, 10, 0, 8, 0x808080, 0, 0, 0x104, 1); break;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
for (unsigned bits = 0; bits < 32; ++bits) {
|
||||
unsigned i=bits&1, h=(bits>>1)&1, allow=(bits>>2)&1;
|
||||
unsigned y=(bits>>3)&1, field=(bits>>4)&1;
|
||||
CHECK(psx_gpu_raster_skipped_row(i,h,allow,y,field) ==
|
||||
((i && h && !allow) ? (int)(y ^ field) : -1));
|
||||
}
|
||||
for (int scale = 1; scale <= 4; scale *= 4) {
|
||||
gr_init(vram); gr_set_scale(scale);
|
||||
gr_fill_rect(256, 0, 16, 16, 0x7FFF);
|
||||
for (int kind = 0; kind < 9; ++kind) {
|
||||
for (int skip = -1; skip <= 1; ++skip) {
|
||||
reset_canvas(); skipped_row=skip; draw_kind(kind);
|
||||
for (int y=2; y<9; ++y) {
|
||||
CHECK((vram[y*1024+2] != 0) == (skip < 0 || (y&1) != skip));
|
||||
}
|
||||
int x1,y1,x2,y2; gr_get_draw_area(&x1,&y1,&x2,&y2);
|
||||
CHECK(x1==0 && y1==0 && x2==31 && y2==31);
|
||||
if (kind == 0) {
|
||||
gr_render_display_hires(hires,32*scale*4,0,0,32,32);
|
||||
for (int y=2*scale;y<9*scale;++y)
|
||||
CHECK(((hires[y*32*scale+3*scale]&0xFFFFFF)!=0) ==
|
||||
(skip<0 || ((y/scale)&1)!=skip));
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Enhanced vertices can lie above/below integer fallback vertices.
|
||||
* Compare each masked surface with its own unmasked pixel oracle. */
|
||||
for (int shift = -10; shift <= 10; shift += 20) {
|
||||
reset_canvas();
|
||||
gr_set_precise_triangle(1,2*65536,(8+shift)*65536+16384,
|
||||
10*65536,(8+shift)*65536+16384,2*65536,(16+shift)*65536+16384);
|
||||
gr_draw_flat_triangle(2,8,10,8,2,16,0x7FFF);
|
||||
for (int y=0; y<32; ++y)
|
||||
memcpy(reference_vram+y*32,vram+y*1024,32*sizeof(uint16_t));
|
||||
gr_render_display_hires(reference_hires,32*scale*4,0,0,32,32);
|
||||
for (int skip=0; skip<=1; ++skip) {
|
||||
reset_canvas(); skipped_row=skip;
|
||||
gr_set_precise_triangle(1,2*65536,(8+shift)*65536+16384,
|
||||
10*65536,(8+shift)*65536+16384,2*65536,(16+shift)*65536+16384);
|
||||
gr_draw_flat_triangle(2,8,10,8,2,16,0x7FFF);
|
||||
for (int y=0; y<32; ++y) for (int x=0; x<32; ++x)
|
||||
CHECK(vram[y*1024+x] == ((y&1)==skip ? 0 : reference_vram[y*32+x]));
|
||||
gr_render_display_hires(hires,32*scale*4,0,0,32,32);
|
||||
for (int y=0; y<32*scale; ++y) for (int x=0; x<32*scale; ++x)
|
||||
CHECK((hires[y*32*scale+x]&0xFFFFFF) == (((y/scale)&1)==skip ? 0 : (reference_hires[y*32*scale+x]&0xFFFFFF)));
|
||||
}
|
||||
}
|
||||
reset_canvas(); skipped_row=1;
|
||||
gr_set_draw_area(3,3,8,8); gr_draw_flat_rect(0,0,10,10,0x7FFF);
|
||||
CHECK(vram[4*1024+3]==0x7FFF && vram[3*1024+3]==0 && vram[4*1024+2]==0);
|
||||
/* Fill/copy/upload/readback must retain BOTH fields. */
|
||||
gr_fill_rect(0,0,16,16,0x1234);
|
||||
gr_copy_rect(0,0,16,0,16,16);
|
||||
uint16_t in[4]={1,2,3,4},out[4]={0};
|
||||
gr_vram_transfer_in(0,0,2,2,in); gr_vram_transfer_out(0,0,2,2,out);
|
||||
CHECK(memcmp(in,out,sizeof in)==0 && vram[17]==0x1234 && vram[1024+17]==0x1234);
|
||||
/* A mode switch restores drawing to both fields immediately. */
|
||||
reset_canvas(); skipped_row=0; gr_draw_flat_rect(2,2,1,2,0x7FFF);
|
||||
skipped_row=-1; gr_draw_flat_rect(2,2,1,2,0x4321);
|
||||
CHECK(vram[2*1024+2]==0x4321 && vram[3*1024+2]==0x4321);
|
||||
}
|
||||
/* One logical perspective triangle stays one hit despite row re-arms. */
|
||||
for (int skip = -1; skip <= 1; ++skip) {
|
||||
for (int clipped = 0; clipped <= 1; ++clipped) {
|
||||
reset_canvas(); skipped_row = skip;
|
||||
if (clipped) gr_set_draw_area(20,20,21,21);
|
||||
uint32_t before = gr_perspective_triangle_count();
|
||||
gr_set_perspective_triangle(1,1.0f,0.5f,0.25f);
|
||||
gr_draw_textured_triangle(2,2,0,0,10,2,8,0,2,10,0,8,0,0,0x104);
|
||||
CHECK(gr_perspective_triangle_count() == before + 1);
|
||||
gr_set_perspective_triangle(1,0.5f,0.25f,0.125f);
|
||||
gr_draw_textured_triangle(2,2,0,0,10,2,8,0,2,10,0,8,0,0,0x104);
|
||||
CHECK(gr_perspective_triangle_count() == before + 2);
|
||||
gr_set_perspective_triangle(0,1.0f,1.0f,1.0f);
|
||||
gr_set_perspective_triangle(1,0.0f,1.0f,1.0f);
|
||||
gr_draw_textured_triangle(2,2,0,0,10,2,8,0,2,10,0,8,0,0,0x104);
|
||||
CHECK(gr_perspective_triangle_count() == before + 2);
|
||||
}
|
||||
}
|
||||
/* A single enhanced triangle keeps its metadata through all row clips. */
|
||||
mock_enabled=1;gr_set_backend(GR_BACKEND_OPENGL);skipped_row=0;
|
||||
gr_set_draw_area(0,0,31,31);
|
||||
gr_set_precise_triangle(1,0,0,10*65536,0,0,10*65536);
|
||||
gr_set_perspective_triangle(1,1.0f,0.5f,0.25f);
|
||||
gr_draw_flat_triangle(0,0,10,0,0,10,0x7FFF);
|
||||
CHECK(mock_submissions==6 && mock_bad==0 && !mock_pc && !mock_pq);
|
||||
mock_submissions=0;
|
||||
gr_set_draw_area(0,0,1023,511);
|
||||
gr_set_precise_triangle(1,0,8*65536+16384,65536,8*65536+16384,0,9*65536+16384);
|
||||
gr_set_perspective_triangle(1,1.0f,1.0f,1.0f);
|
||||
gr_draw_flat_triangle(0,8,1,8,0,9,0x7FFF);
|
||||
CHECK(mock_submissions==3 && mock_bad==0 && !mock_pc && !mock_pq);
|
||||
for (int skip=-1; skip<=1; ++skip) {
|
||||
memset(overlay_rows,0,sizeof overlay_rows); skipped_row=skip;
|
||||
gr_set_draw_area(0,4,31,15);
|
||||
gr_draw_flat_rect(0,0,32,20,0x7FFF);
|
||||
for (int y=0; y<32; ++y) {
|
||||
unsigned expected = skip<0 ? y<20 : (y>=4 && y<=15 && (y&1)!=skip);
|
||||
CHECK(overlay_rows[y] == expected);
|
||||
}
|
||||
int x1,y1,x2,y2; gr_get_draw_area(&x1,&y1,&x2,&y2);
|
||||
CHECK(x1==0 && y1==4 && x2==31 && y2==15);
|
||||
}
|
||||
gr_set_backend(GR_BACKEND_SOFTWARE);
|
||||
if (failures) return 1;
|
||||
puts("PASS: field predicate, all raster primitives, clipping, transfers, mode switch, scales 1/4");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user