mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Integrate revised GPU hold snapshot and executable rendering regression
# Conflicts: # runtime/tests/test_depth24_present_hold_exec.c
This commit is contained in:
+2146
-2140
File diff suppressed because it is too large
Load Diff
+10
-3
@@ -75,15 +75,22 @@ uint32_t gpu_display_pixel_argb(const GpuDisplayInfo* di, uint32_t x, uint32_t y
|
||||
* overhead. `out` must hold at least `count` uint32_t ARGB entries. */
|
||||
void gpu_depth24_present_row(const GpuDisplayInfo* di, uint32_t y, uint32_t* out,
|
||||
uint32_t count);
|
||||
/* Stage a depth24 frame into a w x di->screen_height ARGB canvas and
|
||||
* black-fill trailing columns no upload covered (see gpu.c). Returns the
|
||||
* source rows staged. */
|
||||
uint32_t gpu_depth24_stage_scanout(const GpuDisplayInfo* di, uint32_t* buf,
|
||||
uint32_t w, uint32_t margin);
|
||||
/* Depth24: RGB columns covered by CPU→VRAM uploads since the last reset.
|
||||
* Returns 0 when no uploads yet, crtc_w when coverage is full/unknown-beyond,
|
||||
* else the covered RGB width. Present black-fills [limit, crtc_w) without
|
||||
* shrinking the CRTC-derived draw width (avoids a flickering black pillar). */
|
||||
uint32_t gpu_depth24_rgb_limit(uint32_t display_x, uint32_t crtc_w);
|
||||
void gpu_depth24_upload_span_reset(void);
|
||||
/* MotK intro cuts retarget GP1(07h). While hold > 0, present should skip
|
||||
* Swap (keep prior frame) so stale trailing VRAM never flashes. One tick
|
||||
* per vblank; returns non-zero while the hold is still active after tick. */
|
||||
/* MotK intro cuts retarget GP1(07h): the guest-state hold counts down at
|
||||
* guest VBlank (gpu_vblank_tick). Returns non-zero while the latest guest
|
||||
* VBlank was held (read-only; guest state in the GPU snap): presents should
|
||||
* skip Swap (keep the prior frame) so stale trailing
|
||||
* VRAM never flashes. */
|
||||
int gpu_depth24_present_hold_tick(void);
|
||||
/* After savestate restore: clear ephemeral hold so the restored depth24
|
||||
* frame can present immediately (span/prev_h come from the GPU snap). */
|
||||
|
||||
+50
-9
@@ -3435,11 +3435,13 @@ static uint16_t vram_write_pixels[1024 * 512];
|
||||
* gpu_depth24_rgb_limit — declared early so gpu_reset_state can clear it. */
|
||||
static uint32_t s_d24_upload_x1 = 0;
|
||||
static int s_d24_present_hold = 0; /* vblanks to skip Swap after GP1(07h) */
|
||||
/* Host side of the hold: whether the present after the latest guest VBlank
|
||||
* skips its Swap. The count itself is guest state (in the GPU snap) and moves
|
||||
* only at guest VBlank, so a live run, a rollback replay and a headless run
|
||||
* all hold the same span (#636: a present-ticked hold stuck at 3 in headless
|
||||
* live play and was zeroed by a rollback load, so replay != live). */
|
||||
/* Whether the latest guest VBlank was held: presents after it skip their
|
||||
* Swap. Both the count and this flag are guest state, set only at guest
|
||||
* VBlank and carried in the GPU snap (packed into the hold word), and a
|
||||
* present only reads them, so a windowed, headless or frame-skipping run and
|
||||
* a rollback replay hold the same span and restore the same output (#636: a
|
||||
* present-ticked hold stuck at 3 in headless live play and was zeroed by a
|
||||
* rollback load, so replay != live). */
|
||||
static int s_d24_present_skip = 0;
|
||||
static uint32_t s_d24_prev_disp_h = 0; /* last GP1(07h) band height */
|
||||
static void depth24_note_upload(uint32_t x, uint32_t w);
|
||||
@@ -4269,9 +4271,7 @@ void gpu_depth24_upload_span_reset(void) {
|
||||
}
|
||||
|
||||
int gpu_depth24_present_hold_tick(void) {
|
||||
int skip = s_d24_present_skip;
|
||||
s_d24_present_skip = 0; /* one skipped Swap per guest VBlank */
|
||||
return skip;
|
||||
return s_d24_present_skip;
|
||||
}
|
||||
|
||||
void gpu_depth24_on_savestate_loaded(void) {
|
||||
@@ -4420,6 +4420,42 @@ void gpu_depth24_present_row(const GpuDisplayInfo* di, uint32_t y, uint32_t* out
|
||||
out[x] = 0xFF000000u;
|
||||
}
|
||||
|
||||
/* Depth24 present staging, shared by every CPU present path: compose the
|
||||
* GP1(07h) intersection with active video into the PAL/NTSC canvas (rows
|
||||
* off-screen are clipping, not a shift), then black-fill the trailing RGB
|
||||
* columns no CPU->VRAM upload has covered since the last span reset, so stale
|
||||
* VRAM from a prior movie never shows past the new blit. With no span yet
|
||||
* (lim == 0) only the last `margin` columns are blanked: blanking [0..w) left
|
||||
* MotK's FMV permanently black after the GP1(07h) hold reset the span.
|
||||
* Returns the source rows staged. */
|
||||
uint32_t gpu_depth24_stage_scanout(const GpuDisplayInfo *di, uint32_t *buf,
|
||||
uint32_t w, uint32_t margin) {
|
||||
if (!di || !buf || w == 0u || di->screen_height == 0u)
|
||||
return 0u;
|
||||
const size_t count = (size_t)w * di->screen_height;
|
||||
for (size_t i = 0; i < count; i++)
|
||||
buf[i] = 0xFF000000u;
|
||||
if (di->screen_origin_y >= di->screen_height)
|
||||
return 0u;
|
||||
uint32_t rows = di->height;
|
||||
if (rows > di->screen_height - di->screen_origin_y)
|
||||
rows = di->screen_height - di->screen_origin_y;
|
||||
uint32_t *source = buf + (size_t)di->screen_origin_y * w;
|
||||
for (uint32_t y = 0; y < rows; y++)
|
||||
gpu_depth24_present_row(di, di->screen_source_skip_y + y,
|
||||
source + (size_t)y * w, w);
|
||||
if (rows == 0u || w < 8u || margin == 0u || margin >= w)
|
||||
return rows;
|
||||
uint32_t start = w - margin;
|
||||
uint32_t lim = gpu_depth24_rgb_limit(di->display_x, w);
|
||||
if (lim > 0u && lim < w && lim < start)
|
||||
start = lim;
|
||||
for (uint32_t y = 0; y < rows; y++)
|
||||
for (uint32_t x = start; x < w; x++)
|
||||
source[(size_t)y * w + x] = 0xFF000000u;
|
||||
return rows;
|
||||
}
|
||||
|
||||
int gpu_display_is_depth24(void) {
|
||||
return (int)(display_depth & 1u);
|
||||
}
|
||||
@@ -7850,7 +7886,8 @@ static int gpu_snap_emit(PstW *w) {
|
||||
WI(vram_read_active); WH(vram_read_x); WH(vram_read_y); WH(vram_read_w); WH(vram_read_h);
|
||||
WH(vram_read_col); WH(vram_read_row);
|
||||
/* Depth24 present helpers (MotK FMV) — must resume with upload span. */
|
||||
WU(s_d24_upload_x1); WI(s_d24_present_hold); WU(s_d24_prev_disp_h);
|
||||
WU(s_d24_upload_x1); WI(s_d24_present_hold | (s_d24_present_skip ? 0x100 : 0));
|
||||
WU(s_d24_prev_disp_h);
|
||||
#undef WU
|
||||
#undef WI
|
||||
#undef WH
|
||||
@@ -7884,6 +7921,10 @@ static int gpu_snap_parse(PstR *r) {
|
||||
RI(vram_read_active); RH(vram_read_x); RH(vram_read_y); RH(vram_read_w); RH(vram_read_h);
|
||||
RH(vram_read_col); RH(vram_read_row);
|
||||
RU(s_d24_upload_x1); RI(s_d24_present_hold); RU(s_d24_prev_disp_h);
|
||||
/* Bit 8: the latest VBlank was held. Older snaps lack it; a positive
|
||||
* count after a VBlank implies the VBlank was held. */
|
||||
s_d24_present_skip = (s_d24_present_hold & 0x100) || (s_d24_present_hold & 0xFF) > 0;
|
||||
s_d24_present_hold &= 0xFF;
|
||||
#undef RU
|
||||
#undef RI
|
||||
#undef RH
|
||||
|
||||
+8
-43
@@ -7483,55 +7483,20 @@ static void depth24_cutover_tick(int depth24) {
|
||||
*
|
||||
* Optional short cutover blank: full-frame black for 1–2 presents on movie
|
||||
* start / long idle resume only (see depth24_cutover_tick). */
|
||||
static void depth24_fix_trailing_margin(uint32_t *buf, uint32_t w, uint32_t h,
|
||||
uint32_t display_x) {
|
||||
if (!buf || w < 8u || h == 0u) return;
|
||||
|
||||
if (s_d24_cutover_blank > 0) {
|
||||
s_d24_cutover_blank--;
|
||||
const uint32_t n = w * h;
|
||||
for (uint32_t i = 0; i < n; i++)
|
||||
buf[i] = 0xFF000000u;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Default: last 8 columns. If the upload span is known and ends earlier
|
||||
* inside that margin, start blanking from the span edge instead. */
|
||||
const uint32_t margin = (uint32_t)g_video_depth24_trailing_margin;
|
||||
if (margin == 0u || margin >= w) return;
|
||||
uint32_t start = w - margin;
|
||||
uint32_t lim = gpu_depth24_rgb_limit(display_x, w);
|
||||
if (lim > 0u && lim < w && lim < start)
|
||||
start = lim;
|
||||
for (uint32_t y = 0; y < h; y++) {
|
||||
for (uint32_t x = start; x < w; x++)
|
||||
buf[y * w + x] = 0xFF000000u;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compose visible depth24 rows into the PAL/NTSC active canvas. Decode only
|
||||
* the GP1(07h) intersection with active video. This preserves every visible
|
||||
* source row and represents off-screen rows as clipping, not as an in-place
|
||||
* shift that discards additional FMV content. */
|
||||
static void depth24_stage_scanout(const GpuDisplayInfo *di, uint32_t *buf,
|
||||
uint32_t w) {
|
||||
if (!di || !buf || w == 0u || di->screen_height == 0u)
|
||||
if (!di || !buf || w == 0u)
|
||||
return;
|
||||
|
||||
/* gpu.c stages the visible rows and black-fills the uncovered trailing
|
||||
* columns (the same code depth24_present_hold_exec_test renders). */
|
||||
const uint32_t rows = gpu_depth24_stage_scanout(
|
||||
di, buf, w, (uint32_t)g_video_depth24_trailing_margin);
|
||||
if (rows == 0u || w < 8u || s_d24_cutover_blank <= 0)
|
||||
return;
|
||||
s_d24_cutover_blank--;
|
||||
const size_t count = (size_t)w * di->screen_height;
|
||||
for (size_t i = 0; i < count; i++)
|
||||
buf[i] = 0xFF000000u;
|
||||
|
||||
if (di->screen_origin_y >= di->screen_height)
|
||||
return;
|
||||
uint32_t rows = di->height;
|
||||
if (rows > di->screen_height - di->screen_origin_y)
|
||||
rows = di->screen_height - di->screen_origin_y;
|
||||
uint32_t *source = buf + (size_t)di->screen_origin_y * w;
|
||||
for (uint32_t y = 0; y < rows; y++)
|
||||
gpu_depth24_present_row(di, di->screen_source_skip_y + y,
|
||||
source + (size_t)y * w, w);
|
||||
depth24_fix_trailing_margin(source, w, rows, di->display_x);
|
||||
}
|
||||
|
||||
enum {
|
||||
|
||||
@@ -1,49 +1,321 @@
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
/* Executable regression for the depth24 GP1(07h) present hold (#636).
|
||||
*
|
||||
* Drives the real gpu.c through a scripted depth24 FMV: 15-bit setup, a
|
||||
* switch to 24-bit, a full-width movie A, a GP1(07h) band retarget to a
|
||||
* narrower movie B (the hold), display-mode transitions and a second
|
||||
* retarget. Each frame is: guest GP0/GP1 writes, gpu_vblank_tick(), then
|
||||
* (per present policy) the frontend present, modelled exactly as
|
||||
* sdl_vblank_present_body does it: skip the Swap when
|
||||
* gpu_depth24_present_hold_tick() says so, else stage the frame through
|
||||
* gpu_depth24_stage_scanout() (the code every CPU depth24 present runs).
|
||||
*
|
||||
* Checks:
|
||||
* - the hold count, its latched skips and the upload span are identical
|
||||
* per guest tick whether presents run every VBlank (windowed), never
|
||||
* (headless) or every other VBlank (deferred/skipped presents), and so
|
||||
* are the GPU snapshot bytes;
|
||||
* - the hold spans exactly three VBlanks (3 -> 2 -> 1 -> 0) and skips
|
||||
* exactly those presents; uploads during it do not grow the span;
|
||||
* - GP1(07h) in 15-bit mode, or an unchanged band, does not arm it;
|
||||
* - rendering: no staged frame after a retarget shows a single pixel of
|
||||
* the stale trailing movie-A columns, movie B is visible (not a black
|
||||
* screen), and staging that ignored the hold would expose the stale
|
||||
* columns (so the check has teeth);
|
||||
* - rollback: loading the snapshot of any tick (hold and held-VBlank flag
|
||||
* restored from the snap, whatever the pre-load flag was) and
|
||||
* replaying reproduces the live run tick for tick, including the present
|
||||
* of the restored tick and every staged frame;
|
||||
* - disk load: the hold and skip clear, the span comes back from the
|
||||
* snapshot and the restored frame presents at once.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#define GPU_EXEC_REAL_UI_GROUP
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../src/gpu.c"
|
||||
|
||||
#include "gpu_exec_stubs.inc"
|
||||
|
||||
static void arm(void) {
|
||||
gpu_reset_state(0);
|
||||
display_depth = 1;
|
||||
gp1_v_display_range((260u<<10)|20u);
|
||||
gp1_v_display_range((244u<<10)|20u);
|
||||
assert(s_d24_present_hold==3);
|
||||
enum { FRAMES = 30, PRESENT_W = 320, MAX_ROWS = 600 };
|
||||
enum { POLICY_EVERY, POLICY_NONE, POLICY_EVERY_OTHER, POLICY_COUNT };
|
||||
|
||||
#define MOVIE_A 0xAAu /* stale full-width movie bytes */
|
||||
#define MOVIE_B 0x33u /* narrower movie after the retarget */
|
||||
#define B_HALFWORDS 384u /* 256 RGB pixels */
|
||||
#define A_HALFWORDS 480u /* 320 RGB pixels */
|
||||
#define RETARGET_1 7
|
||||
#define MODE_15_FRAME 16
|
||||
#define MODE_24_FRAME 17
|
||||
#define RETARGET_2 21
|
||||
|
||||
typedef struct {
|
||||
int hold;
|
||||
int skip_latch;
|
||||
uint32_t span;
|
||||
uint64_t snap_hash;
|
||||
int presented; /* -1: no present ran, 0: Swap skipped, 1: staged */
|
||||
uint64_t frame_hash;
|
||||
} Tick;
|
||||
|
||||
static uint32_t canvas[PRESENT_W * MAX_ROWS];
|
||||
|
||||
static uint64_t fnv(const void *p, size_t n) {
|
||||
const uint8_t *b = (const uint8_t *)p;
|
||||
uint64_t h = 1469598103934665603ull;
|
||||
for (size_t i = 0; i < n; i++) { h ^= b[i]; h *= 1099511628211ull; }
|
||||
return h;
|
||||
}
|
||||
int main(void) {
|
||||
for (int host_present=0; host_present<2; ++host_present) {
|
||||
arm();
|
||||
for (int vb=0;vb<3;++vb) {
|
||||
depth24_note_upload(0,480);
|
||||
assert(s_d24_upload_x1==0);
|
||||
gpu_vblank_tick();
|
||||
assert(s_d24_present_hold==2-vb);
|
||||
if (host_present) {
|
||||
assert(gpu_depth24_present_hold_tick()==1);
|
||||
assert(gpu_depth24_present_hold_tick()==0);
|
||||
|
||||
static uint64_t snap_hash(void) {
|
||||
uint32_t n = gpu_snapshot_bytes();
|
||||
uint8_t *buf = (uint8_t *)malloc(n);
|
||||
assert(buf);
|
||||
gpu_snapshot_write(buf);
|
||||
uint64_t h = fnv(buf, n);
|
||||
free(buf);
|
||||
return h;
|
||||
}
|
||||
|
||||
static void gp1_vrange(uint32_t h) {
|
||||
gpu_write_gp1(0x07000000u | 0x10u | ((0x10u + h) << 10));
|
||||
}
|
||||
|
||||
static void upload(uint8_t byte, uint32_t halfwords, uint32_t rows) {
|
||||
const uint32_t hw = (uint32_t)byte | ((uint32_t)byte << 8);
|
||||
gpu_write_gp0(0xA0000000u);
|
||||
gpu_write_gp0(0u); /* x = 0, y = 0 */
|
||||
gpu_write_gp0((rows << 16) | halfwords);
|
||||
for (uint32_t i = 0; i < halfwords * rows / 2u; i++)
|
||||
gpu_write_gp0(hw | (hw << 16));
|
||||
assert(gp0_state == GP0_IDLE);
|
||||
}
|
||||
|
||||
/* Guest work for frame f (identical in every run and every replay). */
|
||||
static void guest_frame(int f) {
|
||||
switch (f) {
|
||||
case 0:
|
||||
gpu_write_gp1(0x08000001u); /* 15-bit, 320 */
|
||||
gpu_write_gp1(0x05000000u); /* display area 0,0 */
|
||||
gpu_write_gp1(0x06000000u | 0x260u | (0xC60u << 12));
|
||||
gp1_vrange(64);
|
||||
break;
|
||||
case 1: gp1_vrange(48); break; /* 15-bit: no hold */
|
||||
case 2: gp1_vrange(64); break;
|
||||
case 3: gpu_write_gp1(0x08000011u); break; /* -> 24-bit */
|
||||
case 4: gp1_vrange(64); break; /* same band: no hold */
|
||||
case RETARGET_1: gp1_vrange(48); break; /* MotK-style retarget */
|
||||
case MODE_15_FRAME: gpu_write_gp1(0x08000001u); break;
|
||||
case MODE_24_FRAME: gpu_write_gp1(0x08000011u); break;
|
||||
case RETARGET_2: gp1_vrange(40); break;
|
||||
default: break;
|
||||
}
|
||||
if (f >= 3 && f < RETARGET_1)
|
||||
upload(MOVIE_A, A_HALFWORDS, 64);
|
||||
else if (f >= RETARGET_1)
|
||||
upload(MOVIE_B, B_HALFWORDS, 48);
|
||||
}
|
||||
|
||||
static int present(uint64_t *frame_hash, int honor_hold) {
|
||||
int skip = gpu_depth24_present_hold_tick();
|
||||
if (skip && honor_hold)
|
||||
return 0;
|
||||
GpuDisplayInfo di;
|
||||
gpu_get_display_info(&di);
|
||||
if (!di.depth24) { *frame_hash = 0; return 1; }
|
||||
assert(di.screen_height <= MAX_ROWS);
|
||||
uint32_t rows = gpu_depth24_stage_scanout(&di, canvas, PRESENT_W, 8u);
|
||||
assert(rows > 0u);
|
||||
*frame_hash = fnv(canvas, sizeof(uint32_t) * PRESENT_W * di.screen_height);
|
||||
/* Rendering check on the staged frame. */
|
||||
const uint32_t stale = 0xFF000000u | (MOVIE_A << 16) | (MOVIE_A << 8) | MOVIE_A;
|
||||
const uint32_t fresh = 0xFF000000u | (MOVIE_B << 16) | (MOVIE_B << 8) | MOVIE_B;
|
||||
uint32_t n_stale = 0, n_fresh = 0;
|
||||
for (uint32_t i = 0; i < PRESENT_W * di.screen_height; i++) {
|
||||
n_stale += canvas[i] == stale;
|
||||
n_fresh += canvas[i] == fresh;
|
||||
}
|
||||
return 1 + (int)(n_stale > 0) * 2 + (int)(n_fresh > 0) * 4;
|
||||
}
|
||||
|
||||
static void reset_gpu(void) {
|
||||
gpu_reset_state(1);
|
||||
memset(test_ram, 0, sizeof(test_ram));
|
||||
}
|
||||
|
||||
static void record(Tick *t) {
|
||||
t->hold = s_d24_present_hold;
|
||||
t->skip_latch = s_d24_present_skip;
|
||||
t->span = s_d24_upload_x1;
|
||||
t->snap_hash = snap_hash();
|
||||
}
|
||||
|
||||
/* Present step; returns -1/0/1 and checks the staged image. */
|
||||
static int present_checked(int f, uint64_t *frame_hash) {
|
||||
int r = present(frame_hash, 1);
|
||||
if (r == 0) return 0;
|
||||
if (f >= RETARGET_1) {
|
||||
if (r & 2) {
|
||||
fprintf(stderr, "frame %d: stale movie-A pixels exposed\n", f);
|
||||
abort();
|
||||
}
|
||||
if (s_d24_upload_x1 != 0u && display_depth)
|
||||
assert(r & 4); /* movie B on screen */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void run(int policy, Tick out[FRAMES]) {
|
||||
reset_gpu();
|
||||
for (int f = 0; f < FRAMES; f++) {
|
||||
guest_frame(f);
|
||||
gpu_vblank_tick();
|
||||
record(&out[f]);
|
||||
out[f].presented = -1;
|
||||
out[f].frame_hash = 0;
|
||||
int do_present = policy == POLICY_EVERY ||
|
||||
(policy == POLICY_EVERY_OTHER && (f & 1));
|
||||
if (do_present)
|
||||
out[f].presented = present_checked(f, &out[f].frame_hash);
|
||||
}
|
||||
}
|
||||
|
||||
static void check_policies_agree(void) {
|
||||
static Tick t[POLICY_COUNT][FRAMES];
|
||||
for (int p = 0; p < POLICY_COUNT; p++)
|
||||
run(p, t[p]);
|
||||
for (int p = 1; p < POLICY_COUNT; p++)
|
||||
for (int f = 0; f < FRAMES; f++) {
|
||||
assert(t[p][f].hold == t[0][f].hold);
|
||||
assert(t[p][f].skip_latch == t[0][f].skip_latch);
|
||||
assert(t[p][f].span == t[0][f].span);
|
||||
assert(t[p][f].snap_hash == t[0][f].snap_hash);
|
||||
}
|
||||
|
||||
const Tick *w = t[POLICY_EVERY];
|
||||
/* 15-bit retargets and an unchanged band never arm the hold. */
|
||||
for (int f = 0; f < RETARGET_1; f++) assert(w[f].hold == 0 && !w[f].skip_latch);
|
||||
/* All three VBlanks of each hold: 2, 1, 0 after the ticks, three skips. */
|
||||
const int starts[2] = { RETARGET_1, RETARGET_2 };
|
||||
for (int k = 0; k < 2; k++) {
|
||||
int s = starts[k];
|
||||
assert(w[s].hold == 2 && w[s + 1].hold == 1 && w[s + 2].hold == 0);
|
||||
assert(w[s + 3].hold == 0);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
assert(w[s + i].presented == 0); /* Swap skipped */
|
||||
assert(w[s + i].span == 0u); /* held uploads ignored */
|
||||
}
|
||||
assert(w[s + 3].presented == 1); /* first staged frame */
|
||||
assert(w[s + 3].span == B_HALFWORDS);
|
||||
}
|
||||
int skipped = 0;
|
||||
for (int f = 0; f < FRAMES; f++) skipped += w[f].presented == 0;
|
||||
assert(skipped == 6);
|
||||
/* Display-mode transitions: leaving 24-bit drops the span; coming back
|
||||
* gets it from that frame's upload. */
|
||||
assert(w[MODE_15_FRAME].span == 0u);
|
||||
assert(w[MODE_24_FRAME].span == B_HALFWORDS);
|
||||
/* Before the retarget movie A is legitimately full width. */
|
||||
assert(w[RETARGET_1 - 1].presented == 1);
|
||||
/* Deferred presents (odd ticks only): a held tick's present skips, the
|
||||
* first present after the hold stages a clean frame. */
|
||||
const Tick *d = t[POLICY_EVERY_OTHER];
|
||||
assert(d[RETARGET_1].presented == 0 && d[RETARGET_1 + 1].presented == -1);
|
||||
assert(d[RETARGET_1 + 2].presented == 0 && d[RETARGET_1 + 4].presented == 1);
|
||||
}
|
||||
|
||||
/* Teeth: a present that ignored the hold right after the retarget would
|
||||
* show stale movie-A columns (the span was reset, lim == 0). */
|
||||
static void check_hold_is_needed(void) {
|
||||
reset_gpu();
|
||||
for (int f = 0; f <= RETARGET_1; f++) { guest_frame(f); gpu_vblank_tick(); }
|
||||
uint64_t h;
|
||||
int r = present(&h, 0);
|
||||
assert(r & 2);
|
||||
}
|
||||
|
||||
static void check_rollback_and_disk(void) {
|
||||
static Tick live[FRAMES];
|
||||
static uint8_t snaps[FRAMES][4096];
|
||||
static uint16_t vrams[FRAMES][1024 * 512];
|
||||
uint32_t n = gpu_snapshot_bytes();
|
||||
assert(n <= sizeof snaps[0]);
|
||||
|
||||
reset_gpu();
|
||||
for (int f = 0; f < FRAMES; f++) {
|
||||
guest_frame(f);
|
||||
gpu_vblank_tick();
|
||||
record(&live[f]);
|
||||
gpu_snapshot_write(snaps[f]);
|
||||
memcpy(vrams[f], vram, sizeof vram);
|
||||
live[f].presented = present_checked(f, &live[f].frame_hash);
|
||||
}
|
||||
|
||||
for (int k = RETARGET_1 - 2; k < FRAMES - 1; k++) {
|
||||
for (int latch = 0; latch < 2; latch++) {
|
||||
s_d24_present_skip = latch; /* whatever the tip had */
|
||||
memcpy(vram, vrams[k], sizeof vram);
|
||||
assert(gpu_snapshot_read(snaps[k], n));
|
||||
/* psx_frontend_on_rb_snap_loaded keeps the hold. */
|
||||
assert(s_d24_present_hold == live[k].hold);
|
||||
assert(s_d24_upload_x1 == live[k].span);
|
||||
/* Restored output/skip: the present of the restored tick does
|
||||
* what the live present of that tick did. */
|
||||
uint64_t h = 0;
|
||||
int r = present_checked(k, &h);
|
||||
assert(r == live[k].presented);
|
||||
if (r == 1) assert(h == live[k].frame_hash);
|
||||
for (int f = k + 1; f < FRAMES; f++) {
|
||||
Tick t;
|
||||
guest_frame(f);
|
||||
gpu_vblank_tick();
|
||||
record(&t);
|
||||
assert(t.hold == live[f].hold);
|
||||
assert(t.skip_latch == live[f].skip_latch);
|
||||
assert(t.span == live[f].span);
|
||||
assert(t.snap_hash == live[f].snap_hash);
|
||||
t.frame_hash = 0;
|
||||
t.presented = present_checked(f, &t.frame_hash);
|
||||
assert(t.presented == live[f].presented);
|
||||
assert(t.frame_hash == live[f].frame_hash);
|
||||
}
|
||||
}
|
||||
depth24_note_upload(0,480);
|
||||
assert(s_d24_upload_x1==480);
|
||||
gpu_vblank_tick();
|
||||
assert(gpu_depth24_present_hold_tick()==0);
|
||||
}
|
||||
arm(); gpu_vblank_tick();
|
||||
unsigned char saved[4096];
|
||||
uint32_t n=gpu_snapshot_bytes();
|
||||
assert(n<=sizeof saved);
|
||||
gpu_snapshot_write(saved);
|
||||
gpu_vblank_tick(); gpu_vblank_tick();
|
||||
assert(s_d24_present_hold==0);
|
||||
assert(gpu_snapshot_read(saved,n));
|
||||
assert(s_d24_present_hold==2);
|
||||
gpu_vblank_tick(); assert(s_d24_present_hold==1);
|
||||
|
||||
/* Disk savestate load mid-hold: hold and skip clear, span from the snap,
|
||||
* the restored frame presents at once. */
|
||||
for (int k = RETARGET_2; k < RETARGET_2 + 3; k++) {
|
||||
s_d24_present_skip = 1;
|
||||
memcpy(vram, vrams[k], sizeof vram);
|
||||
assert(gpu_snapshot_read(snaps[k], n));
|
||||
gpu_depth24_on_savestate_loaded();
|
||||
assert(s_d24_present_hold == 0);
|
||||
assert(s_d24_upload_x1 == live[k].span);
|
||||
uint64_t h;
|
||||
assert(present(&h, 1) != 0);
|
||||
/* The next VBlank latches no skip: the cleared hold stays cleared. */
|
||||
gpu_vblank_tick();
|
||||
assert(gpu_depth24_present_hold_tick() == 0);
|
||||
}
|
||||
/* A disk load after the hold keeps the restored span. */
|
||||
memcpy(vram, vrams[RETARGET_1 + 3], sizeof vram);
|
||||
assert(gpu_snapshot_read(snaps[RETARGET_1 + 3], n));
|
||||
gpu_depth24_on_savestate_loaded();
|
||||
assert(s_d24_present_hold==0 && gpu_depth24_present_hold_tick()==0);
|
||||
puts("depth24 hold: retarget, headless/present cadence, upload span and snapshot restore PASS");
|
||||
assert(s_d24_upload_x1 == B_HALFWORDS);
|
||||
uint64_t h = 0;
|
||||
int r = present_checked(RETARGET_1 + 3, &h);
|
||||
assert(r == live[RETARGET_1 + 3].presented && h == live[RETARGET_1 + 3].frame_hash);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
check_policies_agree();
|
||||
check_hold_is_needed();
|
||||
check_rollback_and_disk();
|
||||
puts("depth24_present_hold_exec_test: ok");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user