mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Add protected offline local view pair presentation
This commit is contained in:
@@ -136,12 +136,15 @@ int gl_renderer_local_view_end(int keep);
|
||||
/* Stereo shares the VRAM transaction, not the temporal generation/schedule. */
|
||||
uint32_t gl_renderer_stereo_unavailable(void);
|
||||
int gl_renderer_stereo_begin(int x, int y, int w, int h, int reuse_backup);
|
||||
int gl_renderer_local_pair_begin(int x, int y, int w, int h, int reuse_backup);
|
||||
uint32_t gl_renderer_local_pair_unavailable(void);
|
||||
int gl_renderer_stereo_end(uint32_t eye, int keep);
|
||||
void gl_renderer_stereo_stage_reset(void);
|
||||
int gl_renderer_stereo_publish(uint64_t pair_id, uint64_t guest_cycle,
|
||||
const int32_t eye_view[2][3]);
|
||||
void gl_renderer_stereo_reset(void);
|
||||
int gl_renderer_stereo_set_presentation(uint32_t mode);
|
||||
int gl_renderer_local_pair_set_presentation(int enabled);
|
||||
void gl_renderer_stereo_dump_arm(const char *dir, int pairs);
|
||||
/* Bounded metadata for explicitly requested PNG evidence, queried over TCP. */
|
||||
typedef struct GLRenderStereoCapture {
|
||||
|
||||
@@ -647,6 +647,14 @@ typedef struct PSXModStereoFrame {
|
||||
typedef int (*PSXModStereoFn)(struct CPUState*, void*, uint32_t eye);
|
||||
int psx_mod_render_stereo(struct CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
PSXModStereoFn fn, void *user);
|
||||
/* Offline local multiplayer: draw views0/1 from one checkpoint, using the
|
||||
* reversible local-view sandbox. Both must succeed before publication.
|
||||
* Callbacks run with psx_mod_local_view_scope()==1. This never advances the
|
||||
* world or enables stereo/VR. Presentation uses two native4:3 views (8:3).
|
||||
* Unsupported backends refuse and leave ordinary presentation available. */
|
||||
int psx_mod_render_local_pair(struct CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
PSXModStereoFn fn, void *user);
|
||||
int psx_mod_set_local_pair_presentation(int enabled);
|
||||
uint32_t psx_mod_render_stereo_status(void);
|
||||
/* 0 disables stereo output; 1 shows a complete pair side by side. Capture
|
||||
* itself does not enable presentation. Default 0; applies to this session. */
|
||||
|
||||
@@ -8210,14 +8210,16 @@ static void pass_journal_rollback(void) {
|
||||
render_pass_journal_rollback(&s_pj_cpu, s_vram, VRAM_W);
|
||||
}
|
||||
|
||||
/* kind: 0 temporal pass, 1 stereo eye, 2 netplay local view. */
|
||||
/* kind: 0 temporal, 1 stereo, 2 netplay own view, 3 offline view pair. */
|
||||
static int transaction_begin(int x, int y, int w, int h, int open_gen,
|
||||
uint32_t period_vblanks, int reuse_backup, int kind) {
|
||||
const int stereo = kind == 1, local = kind == 2;
|
||||
const int stereo = kind == 1 || kind == 3;
|
||||
const int local = kind == 2 || (kind == 3 && s_cpu_auth_dual);
|
||||
int S = s_hr_scale, gi, wide, tw, th;
|
||||
PassGen *g;
|
||||
memset(&s_pass_begin_diag, 0, sizeof s_pass_begin_diag);
|
||||
s_pass_begin_diag.status = local ? gl_renderer_local_view_unavailable()
|
||||
s_pass_begin_diag.status = kind == 3 ? gl_renderer_local_pair_unavailable()
|
||||
: local ? gl_renderer_local_view_unavailable()
|
||||
: stereo ? gl_renderer_stereo_unavailable()
|
||||
: gl_renderer_pass_unavailable();
|
||||
s_pass_begin_diag.active = s_pass_active;
|
||||
@@ -8270,7 +8272,7 @@ static int transaction_begin(int x, int y, int w, int h, int open_gen,
|
||||
s_pass_begin_diag.requested_h = th;
|
||||
gi = s_pgen_write;
|
||||
g = &s_pgen[gi];
|
||||
if (local) {
|
||||
if (local && !stereo) {
|
||||
/* No generation: the image stays in the presenter surface. */
|
||||
} else if (stereo) {
|
||||
StereoPair *pair = &s_stereo_pair[1 - s_stereo_current];
|
||||
@@ -8461,6 +8463,28 @@ int gl_renderer_stereo_begin(int x, int y, int w, int h, int reuse) {
|
||||
GL_RT_SYNC("stereo_begin");
|
||||
return transaction_begin(x, y, w, h, 0, 0, reuse, 1);
|
||||
}
|
||||
int gl_renderer_local_pair_begin(int x, int y, int w, int h, int reuse) {
|
||||
GL_RT_SYNC("local_pair_begin");
|
||||
/* Offline GL may still own newer native textures/CLUTs. Own-view game
|
||||
* renderers read CPU VRAM while rebuilding authored tile/sprite banks;
|
||||
* synchronize the canonical image before freezing either view. Netplay's
|
||||
* dual raster already keeps this mirror current. */
|
||||
if (!s_pass_active && gl_renderer_local_pair_unavailable()==PSX_MOD_RENDER_PASS_READY)
|
||||
ensure_cpu();
|
||||
return transaction_begin(x, y, w, h, 0, 0, reuse, 3);
|
||||
}
|
||||
uint32_t gl_renderer_local_pair_unavailable(void) {
|
||||
GL_RT_SYNC("local_pair_unavailable");
|
||||
pass_refusal_init();
|
||||
if (!s_ctx || !s_raster_ok || !s_hr_fbo || gpu_display_is_depth24())
|
||||
return PSX_MOD_RENDER_PASS_NO_PRESENTER;
|
||||
/* The reversible transaction journals the wide surface too. The pair
|
||||
* captures each native rect after an own-view draw, so an allocated wide
|
||||
* surface is not itself a reason to refuse offline side-by-side. */
|
||||
if (s_hd_native_authority || s_hiw || s_pass_force_refuse > 0)
|
||||
return PSX_MOD_RENDER_PASS_BACKEND;
|
||||
return PSX_MOD_RENDER_PASS_READY;
|
||||
}
|
||||
void gl_renderer_pass_end(uint32_t alpha_q16, int keep) {
|
||||
GL_RT_SYNC("pass_end");
|
||||
int gi = s_pgen_write;
|
||||
@@ -8513,6 +8537,13 @@ int gl_renderer_stereo_set_presentation(uint32_t mode) {
|
||||
s_stereo_mode = (int)mode;
|
||||
return 1;
|
||||
}
|
||||
int gl_renderer_local_pair_set_presentation(int enabled) {
|
||||
GL_RT_SYNC("local_pair_set_presentation");
|
||||
int mode = enabled ? 2 : 0;
|
||||
if (s_stereo_mode != mode) stereo_invalidate();
|
||||
s_stereo_mode = mode;
|
||||
return 1;
|
||||
}
|
||||
static void stereo_resources_release(void) {
|
||||
for (int gi = 0; gi < 2; gi++) for (int eye = 0; eye < 2; eye++) {
|
||||
StereoPair *p = &s_stereo_pair[gi];
|
||||
@@ -8805,10 +8836,12 @@ static int stereo_present(int w, int h) {
|
||||
return 0;
|
||||
if (!s_stereo_mode || !s_stereo_valid || s_pass_active ||
|
||||
p->w != w || p->h != h || p->tw[0] != w * s_hr_scale ||
|
||||
gl_renderer_stereo_unavailable() != PSX_MOD_RENDER_PASS_READY) return 0;
|
||||
(s_stereo_mode == 2 ? gl_renderer_local_pair_unavailable() :
|
||||
gl_renderer_stereo_unavailable()) != PSX_MOD_RENDER_PASS_READY) return 0;
|
||||
SDL_GL_GetDrawableSize(s_win, &ww, &wh);
|
||||
gl_perf_present_enter();
|
||||
letterbox_rect_aspect(ww, wh, s_aspect_num * 2, s_aspect_den, &lx, &ly, &lw, &lh);
|
||||
letterbox_rect_aspect(ww, wh, s_stereo_mode == 2 ? 8 : s_aspect_num * 2,
|
||||
s_stereo_mode == 2 ? 3 : s_aspect_den, &lx, &ly, &lw, &lh);
|
||||
p_glBindFramebuffer(PSXGL_DRAW_FRAMEBUFFER, 0);
|
||||
glDisable(GL_SCISSOR_TEST); glViewport(0, 0, ww, wh);
|
||||
glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
+29
-14
@@ -363,7 +363,7 @@ int psx_mod_set_render_pass_flip(uint32_t mode) {
|
||||
static uint32_t transaction_status(int kind) {
|
||||
uint32_t gl;
|
||||
if (s_stats.disabled) return PSX_MOD_RENDER_PASS_DISABLED;
|
||||
if ((kind == 2 ? !psx_netplay_active() :
|
||||
if ((kind == 2 ? !psx_netplay_active() : kind == 3 ? psx_netplay_active() :
|
||||
(psx_netplay_active() && !s_netplay_allowed)) || psx_netplay_is_resimulating() ||
|
||||
psx_selfcheck_resim_active() || psx_rewind_is_open() ||
|
||||
g_ls_mode || g_ls_replay_active)
|
||||
@@ -373,7 +373,8 @@ static uint32_t transaction_status(int kind) {
|
||||
* redraw during catch-up exposes the canonical peer's image. Any load
|
||||
* shedding must skip a whole presentation, preserving the last displayed
|
||||
* local image, rather than substitute the shared frame here. */
|
||||
gl = kind == 2 ? gl_renderer_local_view_unavailable()
|
||||
gl = kind == 3 ? gl_renderer_local_pair_unavailable()
|
||||
: kind == 2 ? gl_renderer_local_view_unavailable()
|
||||
: kind == 1 ? gl_renderer_stereo_unavailable() : gl_renderer_pass_unavailable();
|
||||
if (gl != PSX_MOD_RENDER_PASS_READY) return gl;
|
||||
if (s_nesting || g_psx_guest_time_frozen || psx_get_in_exception() ||
|
||||
@@ -390,6 +391,9 @@ void render_stereo_get_stats(RenderStereoStats *out) { if (out) *out = s_stereo;
|
||||
int psx_mod_set_stereo_presentation(uint32_t mode) {
|
||||
return mode <= 1u ? gl_renderer_stereo_set_presentation(mode) : 0;
|
||||
}
|
||||
int psx_mod_set_local_pair_presentation(int enabled) {
|
||||
return gl_renderer_local_pair_set_presentation(enabled);
|
||||
}
|
||||
int psx_mod_render_view_offset(int32_t x, int32_t y, int32_t z) {
|
||||
int32_t xyz[3] = {x, y, z};
|
||||
if (!g_psx_render_pass_active) return 0;
|
||||
@@ -666,11 +670,11 @@ static void local_scope_set(int on) {
|
||||
int psx_mod_local_view_scope(void) { return s_local_scope; }
|
||||
|
||||
static int transaction_kind(int eye) {
|
||||
return eye >= 0 ? 1 : eye == RP_EYE_LOCAL ? 2 : 0;
|
||||
return eye >= 2 ? 3 : eye >= 0 ? 1 : eye == RP_EYE_LOCAL ? 2 : 0;
|
||||
}
|
||||
|
||||
static int transaction_end(int eye, uint32_t alpha, int keep) {
|
||||
if (eye >= 0) return gl_renderer_stereo_end((uint32_t)eye, keep);
|
||||
if (eye >= 0) return gl_renderer_stereo_end((uint32_t)eye & 1u, keep);
|
||||
if (eye == RP_EYE_LOCAL) return gl_renderer_local_view_end(keep);
|
||||
gl_renderer_pass_end(alpha, keep);
|
||||
return 1;
|
||||
@@ -728,7 +732,9 @@ static int render_transaction(struct CPUState *cpu, const PSXModRenderPass *pass
|
||||
s_restored_cycle == psx_cycle_count &&
|
||||
s_restored_stores == g_guest_store_count;
|
||||
/* Frame N's own image is captured by the first pass after a plan. */
|
||||
if (!(eye >= 0 ? gl_renderer_stereo_begin(pass->x, pass->y, pass->w,
|
||||
if (!(eye >= 2 ? gl_renderer_local_pair_begin(pass->x, pass->y, pass->w,
|
||||
pass->h, reuse)
|
||||
: eye >= 0 ? gl_renderer_stereo_begin(pass->x, pass->y, pass->w,
|
||||
pass->h, reuse)
|
||||
: eye == RP_EYE_LOCAL ? gl_renderer_local_view_begin(pass->x, pass->y,
|
||||
pass->w, pass->h)
|
||||
@@ -757,14 +763,14 @@ static int render_transaction(struct CPUState *cpu, const PSXModRenderPass *pass
|
||||
s_device_read_fault = 0;
|
||||
cycles_before = psx_cycle_count;
|
||||
if (eye >= 0) {
|
||||
s_stereo.eye_cycle[eye] = cycles_before;
|
||||
s_stereo.eye_hash[eye] = verify_on() ? hash_before : 0;
|
||||
s_stereo.eye_cycle[eye & 1] = cycles_before;
|
||||
s_stereo.eye_hash[eye & 1] = verify_on() ? hash_before : 0;
|
||||
}
|
||||
(void)psx_cycle_freeze_begin(&s_freeze, watchdog_cycles(),
|
||||
watchdog_overrun);
|
||||
/* A netplay local view is the one place a peer's presentation-only mods
|
||||
* act on guest code (own-view plugins, widescreen cull margin). */
|
||||
if (eye == RP_EYE_LOCAL) local_scope_set(1);
|
||||
if (eye == RP_EYE_LOCAL || eye >= 2) local_scope_set(1);
|
||||
if (setjmp(s_abort_jmp) == 0) {
|
||||
s_pass_cpu = cpu;
|
||||
s_abort_armed = 1;
|
||||
@@ -792,7 +798,7 @@ static int render_transaction(struct CPUState *cpu, const PSXModRenderPass *pass
|
||||
ok = 0;
|
||||
}
|
||||
s_stats.guest_cycles_last = psx_cycle_count - cycles_before;
|
||||
if (eye >= 0) gte_render_view_get(s_stereo.eye_view[eye]);
|
||||
if (eye >= 0) gte_render_view_get(s_stereo.eye_view[eye & 1]);
|
||||
tg = gl_renderer_perf_ticks();
|
||||
s_pass_cpu = NULL;
|
||||
|
||||
@@ -895,8 +901,8 @@ static int stereo_eye_draw(CPUState *cpu, void *user, uint32_t unused) {
|
||||
return c->fn(cpu, c->user, c->eye);
|
||||
}
|
||||
|
||||
int psx_mod_render_stereo(CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
PSXModStereoFn fn, void *user) {
|
||||
static int render_pair(CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
PSXModStereoFn fn, void *user, int local) {
|
||||
PSXModRenderPass rect;
|
||||
StereoCallback call;
|
||||
uint64_t t0, cycle, stores, mismatches, aborted;
|
||||
@@ -913,13 +919,13 @@ int psx_mod_render_stereo(CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
(uint32_t)frame->y + frame->h > 512u) {
|
||||
s_stereo.refused++; s_stereo.last_failure = "arguments"; return 0;
|
||||
}
|
||||
status = transaction_status(1);
|
||||
status = transaction_status(local ? 3 : 1);
|
||||
if (status != PSX_MOD_RENDER_PASS_READY) {
|
||||
s_stereo.refused++; s_stereo.last_failure = "status"; return 0;
|
||||
}
|
||||
budget_ms = frame->period_vblanks * (double)g_psx_vblank_cycles /
|
||||
33868.8 * 0.8;
|
||||
if (s_stereo.avg_pair_ms > budget_ms && ++s_pair_retry < 30u) {
|
||||
if (!local && s_stereo.avg_pair_ms > budget_ms && ++s_pair_retry < 30u) {
|
||||
s_stereo.shed++; return 0;
|
||||
}
|
||||
s_pair_retry = 0;
|
||||
@@ -942,7 +948,8 @@ int psx_mod_render_stereo(CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
if (psx_cycle_count != cycle || g_guest_store_count != stores) {
|
||||
ok = 0; s_stereo.last_failure = "checkpoint_changed"; break;
|
||||
}
|
||||
if (!render_transaction(cpu, &rect, stereo_eye_draw, &call, (int)call.eye)) {
|
||||
if (!render_transaction(cpu, &rect, stereo_eye_draw, &call,
|
||||
(int)call.eye + (local ? 2 : 0))) {
|
||||
ok = 0;
|
||||
if (!s_stereo.last_failure) s_stereo.last_failure =
|
||||
s_stats.aborted != aborted ? "eye_abort" : "eye_refused_or_discarded";
|
||||
@@ -978,3 +985,11 @@ int psx_mod_render_stereo(CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
s_pair_busy = 0;
|
||||
return ok;
|
||||
}
|
||||
int psx_mod_render_stereo(CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
PSXModStereoFn fn, void *user) {
|
||||
return render_pair(cpu,frame,fn,user,0);
|
||||
}
|
||||
int psx_mod_render_local_pair(CPUState *cpu, const PSXModStereoFrame *frame,
|
||||
PSXModStereoFn fn, void *user) {
|
||||
return render_pair(cpu,frame,fn,user,1);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ static int failures;
|
||||
#define CHECK(c, m) do { if (!(c)) { fprintf(stderr, "FAIL: %s\n", m); failures++; } } while (0)
|
||||
|
||||
/* ---- runtime globals the two units share with the rest of the runtime -- */
|
||||
int g_boot_state_cpu_timing_restored = 0;
|
||||
int g_ls_replay_active = 0;
|
||||
int g_ls_mode = 0;
|
||||
int g_precise_mode = 0;
|
||||
@@ -110,7 +111,8 @@ int psx_netplay_active(void) { return 0; }
|
||||
int psx_netplay_is_resimulating(void) { return 0; }
|
||||
void psx_netplay_local_view_clear(void) {}
|
||||
int psx_netplay_local_view_shed(void) { return 0; }
|
||||
void gpu_ws_set_local_view_scope(int on) { (void)on; }
|
||||
static int s_local_scope;
|
||||
void gpu_ws_set_local_view_scope(int on) { s_local_scope=on; }
|
||||
int psx_selfcheck_resim_active(void) { return 0; }
|
||||
int psx_selfcheck_enabled(void) { return 0; }
|
||||
int psx_rewind_is_open(void) { return 0; }
|
||||
@@ -207,6 +209,11 @@ uint32_t gl_renderer_stereo_unavailable(void) { return 0; }
|
||||
int gl_renderer_stereo_begin(int x, int y, int w, int h, int reuse) {
|
||||
return gl_renderer_pass_begin(x, y, w, h, 0, 0, reuse);
|
||||
}
|
||||
uint32_t gl_renderer_local_pair_unavailable(void) { return 0; }
|
||||
int gl_renderer_local_pair_begin(int x, int y, int w, int h, int reuse) {
|
||||
return gl_renderer_pass_begin(x,y,w,h,0,0,reuse);
|
||||
}
|
||||
int gl_renderer_local_pair_set_presentation(int enabled) { (void)enabled;return 1; }
|
||||
int gl_renderer_stereo_end(uint32_t eye, int keep) {
|
||||
gl_renderer_pass_end(0, keep);
|
||||
if (keep) s_stereo_mask |= 1u << eye;
|
||||
@@ -619,6 +626,26 @@ int main(void) {
|
||||
"normal drawing recovers after a rejected device read");
|
||||
}
|
||||
|
||||
/* Offline view pairs use the same unwind protection, including the
|
||||
* own-view scope which stereo rendering does not enter. */
|
||||
{
|
||||
PSXModStereoFrame frame={sizeof frame,1,0,0,320,240};
|
||||
uint64_t kept;
|
||||
snap(&live);
|
||||
CHECK(psx_mod_render_local_pair(&cpu,&frame,stereo_draw,NULL)==1,
|
||||
"complete local pair publishes");
|
||||
kept=s_stereo_published;
|
||||
CHECK(psx_mod_render_local_pair(&cpu,&frame,stereo_draw,&runaway)==0,
|
||||
"local second-view watchdog discards the pair");
|
||||
CHECK(s_stereo_published==kept && s_stereo_mask==0,
|
||||
"local abort cannot publish half a pair");
|
||||
CHECK(!s_local_scope && !psx_mod_local_view_scope(),
|
||||
"local abort closes the own-view scope");
|
||||
check_live(&live,"after local second-view watchdog");
|
||||
CHECK(psx_mod_render_local_pair(&cpu,&frame,stereo_draw,NULL)==1,
|
||||
"local pair recovers after watchdog");
|
||||
}
|
||||
|
||||
printf(failures ? "FAILED (%d)\n" : "ALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -273,6 +273,11 @@ uint32_t gl_renderer_stereo_unavailable(void) { return s_gl_status; }
|
||||
int gl_renderer_stereo_begin(int x, int y, int w, int h, int reuse) {
|
||||
return gl_renderer_pass_begin(x, y, w, h, 0, 0, reuse);
|
||||
}
|
||||
uint32_t gl_renderer_local_pair_unavailable(void) { return s_local_status; }
|
||||
int gl_renderer_local_pair_begin(int x, int y, int w, int h, int reuse) {
|
||||
return gl_renderer_pass_begin(x,y,w,h,0,0,reuse);
|
||||
}
|
||||
int gl_renderer_local_pair_set_presentation(int enabled) { (void)enabled;return 1; }
|
||||
int gl_renderer_stereo_end(uint32_t eye, int keep) {
|
||||
gl_renderer_pass_end(0, keep);
|
||||
if (keep && (int)eye == s_capture_fail_eye) return 0;
|
||||
@@ -770,6 +775,37 @@ static int local_draw(CPUState *cpu, void *user, uint32_t alpha) {
|
||||
psx_advance_cycles(5000u);
|
||||
return 1;
|
||||
}
|
||||
static int local_pair_draw(CPUState *cpu,void *user,uint32_t seat) {
|
||||
CHECK(psx_mod_local_view_scope() && s_ws_scope,
|
||||
"both local multiplayer views run in the own-view scope");
|
||||
return pair_draw(cpu,user,seat);
|
||||
}
|
||||
static void test_local_pair(void) {
|
||||
CPUState cpu={0};PSXModStereoFrame f={sizeof f,1,0,240,320,240};
|
||||
uint64_t cycle=psx_cycle_count,published;
|
||||
render_pass_reset_session();s_netplay_active=0;s_eye_calls=0;
|
||||
s_gl_status=PSX_MOD_RENDER_PASS_BACKEND;s_local_status=0;s_begin_ok=1;
|
||||
cpu.gpr[8]=123;s_ram[0x456]=0x77;
|
||||
CHECK(psx_mod_render_local_pair(&cpu,&f,local_pair_draw,NULL),
|
||||
"dual raster supports a local pair when ordinary stereo is unavailable");
|
||||
CHECK(s_eye_calls==2 && cpu.gpr[8]==123 && s_ram[0x456]==0x77 &&
|
||||
psx_cycle_count==cycle && !psx_mod_local_view_scope(),
|
||||
"two local views restore one common simulation checkpoint and close their scopes");
|
||||
published=s_stereo_published;s_eye_decline=1;
|
||||
CHECK(!psx_mod_render_local_pair(&cpu,&f,local_pair_draw,NULL) &&
|
||||
s_stereo_published==published && !s_stereo_mask,
|
||||
"failed second local view cannot publish a partial frame");
|
||||
CHECK(cpu.gpr[8]==123 && s_ram[0x456]==0x77 && psx_cycle_count==cycle,
|
||||
"failed local pair preserves live state");
|
||||
s_eye_decline=-1;s_netplay_active=1;psx_mod_set_render_pass_netplay(1);
|
||||
int calls=s_eye_calls;
|
||||
CHECK(!psx_mod_render_local_pair(&cpu,&f,local_pair_draw,NULL) && s_eye_calls==calls,
|
||||
"offline local pair is refused online even if temporal replay was permitted");
|
||||
s_netplay_active=0;s_local_status=PSX_MOD_RENDER_PASS_BACKEND;
|
||||
CHECK(!psx_mod_render_local_pair(&cpu,&f,local_pair_draw,NULL) && s_eye_calls==calls,
|
||||
"unsupported local presenter refuses before guest drawing");
|
||||
s_gl_status=s_local_status=0;
|
||||
}
|
||||
|
||||
/* Netplay local view: forward netplay frames only; guest state restored
|
||||
* exactly; a committed image cancels the crop request. */
|
||||
@@ -856,6 +892,7 @@ int main(void) {
|
||||
test_journal();
|
||||
test_stereo();
|
||||
test_local_view();
|
||||
test_local_pair();
|
||||
test_texture_stream_journal();
|
||||
CHECK(s_prec_open == 0, "precision checkpoints balanced");
|
||||
printf(failures ? "FAILED (%d)\n" : "ALL PASS\n", failures);
|
||||
|
||||
Reference in New Issue
Block a user