mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Host pause API (in-game overlay P1)
psx_host_pause_push/pop: a nesting, host-owned pause. While it is held the runtime freezes the guest at the vblank present (as the save-state menu does), keeps presenting the last frame and pauses audio. Push refuses during netplay. Nothing pushes unless an overlay provider (or the debug server's new host_pause command) asks, so behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -793,6 +793,9 @@ if(BUILD_TESTING)
|
||||
tests/test_pad_external_input.c src/pad_external_input.c)
|
||||
target_include_directories(pad_external_input_test PRIVATE include src)
|
||||
add_test(NAME pad_external_input_test COMMAND pad_external_input_test)
|
||||
add_executable(host_overlay_test tests/test_host_overlay.c src/host_overlay.c)
|
||||
target_include_directories(host_overlay_test PRIVATE include src)
|
||||
add_test(NAME host_overlay_test COMMAND host_overlay_test)
|
||||
|
||||
add_executable(gte_register_access_test
|
||||
tests/test_gte_register_access.cpp
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/* host_overlay.h -- hooks for an optional in-game menu ("overlay provider").
|
||||
*
|
||||
* Every hook is inert until something registers with it: a title with no
|
||||
* overlay provider runs exactly as before. Sections are added by the
|
||||
* docs/IN_GAME_OVERLAY.md PR stack (P1 pause, P2 draw, P3 input, ...).
|
||||
*/
|
||||
#ifndef PSX_HOST_OVERLAY_H
|
||||
#define PSX_HOST_OVERLAY_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- P1: host pause --------------------------------------------------------
|
||||
* A nesting pause owned by the host (not the guest): while depth > 0 the
|
||||
* runtime holds the guest at the vblank present, keeps presenting the last
|
||||
* frame and pauses audio. Push refuses (returns 0) while the refusal probe
|
||||
* says so -- the runtime installs psx_netplay_active() there, because pausing
|
||||
* one peer would stall the session. Main thread only. */
|
||||
typedef int (*PsxHostPauseRefuseFn)(void);
|
||||
int psx_host_pause_push(const char *reason);
|
||||
void psx_host_pause_pop(void);
|
||||
int psx_host_pause_depth(void);
|
||||
const char *psx_host_pause_reason(void);
|
||||
void psx_host_pause_set_refuse_probe(PsxHostPauseRefuseFn fn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -21,6 +21,9 @@ typedef struct PsxSdlAudioSpec {
|
||||
SDL_AudioDeviceID psx_sdl_audio_open(
|
||||
const PsxSdlAudioSpec *want, PsxSdlAudioSpec *have);
|
||||
int psx_sdl_audio_resume(SDL_AudioDeviceID device);
|
||||
/* Host pause (host_overlay.h P1): stop/start the device without dropping
|
||||
* queued samples. */
|
||||
void psx_sdl_audio_set_paused(SDL_AudioDeviceID device, int paused);
|
||||
void psx_sdl_audio_close(SDL_AudioDeviceID device);
|
||||
void psx_sdl_audio_clear(SDL_AudioDeviceID device);
|
||||
Uint32 psx_sdl_audio_queued_size(SDL_AudioDeviceID device);
|
||||
|
||||
@@ -390,6 +390,7 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_openxr.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/mod_controller_source.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/pad_external_input.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/host_overlay.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/mod_pad_transform.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/vr_pose_math.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gpu_vk_renderer.c
|
||||
|
||||
@@ -9246,6 +9246,18 @@ static void handle_debug_key(int id, const char *json)
|
||||
send_fmt("{\"id\":%d,\"ok\":%s,\"state\":\"%s\"}", id, ok ? "true" : "false", msg);
|
||||
}
|
||||
|
||||
/* host_pause on=<1|0>: push/pop a host pause (host_overlay.h P1). */
|
||||
#include "host_overlay.h"
|
||||
static void handle_host_pause(int id, const char *json)
|
||||
{
|
||||
int on = json_get_int(json, "on", -1);
|
||||
int ok = 1;
|
||||
if (on == 1) ok = psx_host_pause_push("debug");
|
||||
else if (on == 0) psx_host_pause_pop();
|
||||
send_fmt("{\"id\":%d,\"ok\":%s,\"depth\":%d}", id, ok ? "true" : "false",
|
||||
psx_host_pause_depth());
|
||||
}
|
||||
|
||||
static void handle_ws_nw(int id, const char *json)
|
||||
{
|
||||
int on = json_get_int(json, "on", -1);
|
||||
@@ -15907,6 +15919,7 @@ static const CmdEntry s_commands[] = {
|
||||
{ "window_size", handle_window_size },
|
||||
{ "ws_nw", handle_ws_nw },
|
||||
{ "debug_key", handle_debug_key },
|
||||
{ "host_pause", handle_host_pause },
|
||||
{ "scanline", handle_scanline },
|
||||
{ "ws_backdrop_ring", handle_ws_backdrop_ring },
|
||||
{ "ws_ui_groups", handle_ws_ui_groups },
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/* host_overlay.c -- see host_overlay.h. */
|
||||
#include "host_overlay.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* ---- P1: host pause ------------------------------------------------------ */
|
||||
#define PSX_HOST_PAUSE_MAX 8
|
||||
static int s_pause_depth = 0;
|
||||
static const char *s_pause_reason[PSX_HOST_PAUSE_MAX];
|
||||
static PsxHostPauseRefuseFn s_pause_refuse = NULL;
|
||||
|
||||
int psx_host_pause_push(const char *reason) {
|
||||
if (s_pause_refuse && s_pause_refuse()) return 0;
|
||||
if (s_pause_depth >= PSX_HOST_PAUSE_MAX) return 0;
|
||||
s_pause_reason[s_pause_depth++] = reason ? reason : "host";
|
||||
return 1;
|
||||
}
|
||||
|
||||
void psx_host_pause_pop(void) {
|
||||
if (s_pause_depth > 0) s_pause_reason[--s_pause_depth] = NULL;
|
||||
}
|
||||
|
||||
int psx_host_pause_depth(void) { return s_pause_depth; }
|
||||
|
||||
const char *psx_host_pause_reason(void) {
|
||||
return s_pause_depth > 0 ? s_pause_reason[s_pause_depth - 1] : NULL;
|
||||
}
|
||||
|
||||
void psx_host_pause_set_refuse_probe(PsxHostPauseRefuseFn fn) { s_pause_refuse = fn; }
|
||||
+53
-1
@@ -125,7 +125,8 @@ extern "C" void psx_event_step_conservative_env_init(void);
|
||||
#include "sbi_setup.h"
|
||||
#include "disc_path.h"
|
||||
#include "iso_reader.h" /* text-image guard: extract the boot EXE from the disc */
|
||||
#include "psx_keybinds.h" /* configurable keyboard->DualShock keybinds (keybinds.ini) */
|
||||
#include "psx_keybinds.h"
|
||||
#include "host_overlay.h" /* optional in-game menu hooks (P1 pause, ...) */ /* configurable keyboard->DualShock keybinds (keybinds.ini) */
|
||||
#include "psx_window_icon.h"
|
||||
|
||||
#if defined(RECOMP_LAUNCHER)
|
||||
@@ -8021,6 +8022,53 @@ static void rewind_host_pause_loop(void) {
|
||||
savestate_input_guard_arm();
|
||||
}
|
||||
|
||||
/* Host pause (host_overlay.h, P1). Same freeze as the save-state menu: the
|
||||
* guest is held at the vblank present, the last frame keeps presenting and
|
||||
* audio is paused, until every psx_host_pause_push() has been popped. Nothing
|
||||
* pushes unless an overlay provider is registered, so titles without one never
|
||||
* enter this loop. */
|
||||
static void host_pause_service(void); /* P6 fills this in; no-op until then */
|
||||
static void host_pause_service(void) {}
|
||||
static bool host_pause_event(const SDL_Event& ev); /* P3: input sink */
|
||||
static bool host_pause_event(const SDL_Event&) { return false; }
|
||||
|
||||
static void host_pause_loop(void) {
|
||||
psx_local_mouse_begin(sdl_window, false);
|
||||
psx_local_mouse_reset();
|
||||
freeze_heartbeat_set_paused(1);
|
||||
psx_sdl_audio_set_paused(sdl_audio_device, 1);
|
||||
while (psx_host_pause_depth() > 0) {
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
debug_server_poll();
|
||||
#endif
|
||||
SDL_Event ev;
|
||||
while (!g_headless && SDL_PollEvent(&ev)) {
|
||||
(void)psx_local_mouse_event(ev);
|
||||
if (host_pause_event(ev)) continue;
|
||||
if (ev.type == SDL_QUIT) {
|
||||
psx_crash_trace_set_exit_origin("sdl_window_close");
|
||||
shutdown_runtime();
|
||||
std::exit(0);
|
||||
} else if (ev.type == SDL_CONTROLLERDEVICEADDED) {
|
||||
refresh_player_devices();
|
||||
} else if (ev.type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
close_controller();
|
||||
refresh_player_devices();
|
||||
}
|
||||
}
|
||||
host_pause_service();
|
||||
if (!g_headless) rewind_pause_present();
|
||||
starvation_watchdog_heartbeat();
|
||||
SDL_Delay(g_headless ? 1 : 8);
|
||||
}
|
||||
psx_sdl_audio_set_paused(sdl_audio_device, 0);
|
||||
freeze_heartbeat_set_paused(0);
|
||||
/* Swallow the still-held resume press so it doesn't reach the game. */
|
||||
savestate_input_guard_arm();
|
||||
}
|
||||
|
||||
static int host_pause_refuse_netplay(void) { return psx_netplay_active() ? 1 : 0; }
|
||||
|
||||
/* Freeze guest in vblank present while the save-state slot menu is open. */
|
||||
static void savestate_menu_host_pause_loop(void) {
|
||||
psx_local_mouse_begin(sdl_window, false);
|
||||
@@ -8486,6 +8534,8 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
|
||||
savestate_menu_host_pause_loop();
|
||||
if (psx_rewind_is_open())
|
||||
rewind_host_pause_loop();
|
||||
if (psx_host_pause_depth() > 0)
|
||||
host_pause_loop();
|
||||
} else if (host_pad_layer(nullptr, nullptr, nullptr, nullptr)) {
|
||||
/* Headless with the debug host layer: the host shortcuts that need no
|
||||
* window (Rewind capture/open, fast-forward latch) poll as they would
|
||||
@@ -8872,6 +8922,7 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
|
||||
// A hotkey acquired during pacing may open a host menu.
|
||||
if (savestate_menu_open) savestate_menu_host_pause_loop();
|
||||
if (psx_rewind_is_open()) rewind_host_pause_loop();
|
||||
if (psx_host_pause_depth() > 0) host_pause_loop();
|
||||
psx_local_mouse_begin(sdl_window, local_mouse_live(override));
|
||||
} else {
|
||||
SDL_PumpEvents(); // retain native timing when no policy exists
|
||||
@@ -15888,6 +15939,7 @@ int main(int argc, char** argv) {
|
||||
#if defined(RECOMP_LAUNCHER)
|
||||
launcher_boot_timing_mark("host:main_enter");
|
||||
#endif
|
||||
psx_host_pause_set_refuse_probe(host_pause_refuse_netplay);
|
||||
|
||||
/* --audit-mod-plugins <manifest-root>...: build-time check, no emulation.
|
||||
* Every trusted implementation this executable registered must be named
|
||||
|
||||
@@ -67,6 +67,13 @@ extern "C" int psx_sdl_audio_resume(SDL_AudioDeviceID)
|
||||
: -1;
|
||||
}
|
||||
|
||||
extern "C" void psx_sdl_audio_set_paused(SDL_AudioDeviceID, int paused)
|
||||
{
|
||||
if (!s_audio_stream) return;
|
||||
if (paused) (void)SDL_PauseAudioStreamDevice(s_audio_stream);
|
||||
else (void)SDL_ResumeAudioStreamDevice(s_audio_stream);
|
||||
}
|
||||
|
||||
extern "C" void psx_sdl_audio_close(SDL_AudioDeviceID)
|
||||
{
|
||||
if (s_audio_stream) SDL_DestroyAudioStream(s_audio_stream);
|
||||
@@ -140,6 +147,11 @@ extern "C" int psx_sdl_audio_resume(SDL_AudioDeviceID device)
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void psx_sdl_audio_set_paused(SDL_AudioDeviceID device, int paused)
|
||||
{
|
||||
SDL_PauseAudioDevice(device, paused ? 1 : 0);
|
||||
}
|
||||
|
||||
extern "C" void psx_sdl_audio_close(SDL_AudioDeviceID device)
|
||||
{
|
||||
SDL_CloseAudioDevice(device);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* test_host_overlay.c -- unit tests for host_overlay.c hooks. */
|
||||
#include "host_overlay.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, #c); g_fail++; } } while (0)
|
||||
|
||||
static int refuse_on = 0;
|
||||
static int refuse(void) { return refuse_on; }
|
||||
|
||||
static void test_pause(void) {
|
||||
CHECK(psx_host_pause_depth() == 0 && psx_host_pause_reason() == NULL);
|
||||
CHECK(psx_host_pause_push("overlay"));
|
||||
CHECK(psx_host_pause_push("other"));
|
||||
CHECK(psx_host_pause_depth() == 2 && strcmp(psx_host_pause_reason(), "other") == 0);
|
||||
psx_host_pause_pop();
|
||||
CHECK(strcmp(psx_host_pause_reason(), "overlay") == 0);
|
||||
psx_host_pause_pop();
|
||||
psx_host_pause_pop(); /* extra pop is harmless */
|
||||
CHECK(psx_host_pause_depth() == 0);
|
||||
psx_host_pause_set_refuse_probe(refuse);
|
||||
refuse_on = 1;
|
||||
CHECK(!psx_host_pause_push("netplay") && psx_host_pause_depth() == 0);
|
||||
refuse_on = 0;
|
||||
CHECK(psx_host_pause_push(NULL) && strcmp(psx_host_pause_reason(), "host") == 0);
|
||||
psx_host_pause_pop();
|
||||
for (int i = 0; i < 8; i++) CHECK(psx_host_pause_push("n"));
|
||||
CHECK(!psx_host_pause_push("overflow"));
|
||||
for (int i = 0; i < 8; i++) psx_host_pause_pop();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_pause();
|
||||
printf("host_overlay_test: %s\n", g_fail ? "FAIL" : "ok");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user