present/audio: menu 4:3 with natural bars; SDL3 stream-rate fix; adaptive overclock removed

- Adaptive overclock REMOVED (user decision, and correctly so): trading
  overclock down preserved vblank cadence but starved the game's render
  loop (~30-50 real frames/s under a 110+ vblank counter) and modulated
  the guest-time ratio continuously — an audible CDDA warble. The
  DuckStation-standard model stands: static user overclock; a too-slow
  host slows everything uniformly and audio underruns, never pitch-shifts.
- SDL3 audio: psx_sdl_audio_open reported the DEVICE format instead of
  the stream's INPUT spec. SDL3 converts fed data FROM the requested
  spec; telling the DRC host_rate=48000 while SDL consumed it as
  44100 was a constant ~0.92x pitch-down plus perpetual ring overfill
  (overflow_drops clicks) on every SDL3 build. Report the input spec —
  the DRC stays the only resampler. (Audio-clocking audit confirmed the
  bridge already implements the DuckStation contract: guest-clocked
  production at 768 cycles/sample, device-rate pull, +-0.5% trim,
  hold+fade on underrun; production stages must not be touched —
  netplay/savestate invariants at main.cpp:3009/3071.)
- Present-aspect scene switch: with the widescreen mod engaged, menus/2D
  (scene gate inactive, 45-frame hysteresis in gpu.c) present at the
  game's own 4:3 so its NATURAL black bars stay, framed by the bezels;
  gameplay presents at the mod aspect. New export
  psx_ws_scene_wide_active().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tetrisgm
2026-08-19 19:32:24 -07:00
parent 8ec702dae7
commit 42109c580a
3 changed files with 31 additions and 68 deletions
+4
View File
@@ -368,6 +368,10 @@ int gpu_ws_present_native_43(void) {
/* Squash applies only when configured AND the frame is being stretched. */
static int ws_active(void) { return ws_configured() && !gpu_ws_present_native_43(); }
/* Exported scene gate for the present-aspect switch (main.cpp): menus/2D and
* FMV present at the game's own 4:3 (its natural bars intact, bezels around);
* gameplay presents at the mod aspect. */
int psx_ws_scene_wide_active(void) { return ws_active() && ws_game_mode(); }
/* ----- Native-wide (mode 2) ------------------------------------------------
* Render the wider FOV into an actually wider frame instead of squashing it.
+17 -63
View File
@@ -6635,69 +6635,7 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
load_transition_note(cdrom_data_read_active(), logical_load_active,
turbo_loads_active, load_run_value);
/* Adaptive overclock: pin GUEST TIME to 1.0x wall. The configured
* cpu_overclock is a CEILING; when the emu thread cannot retire that many
* scaled cycles per wall vblank the guest timeline dilates — CD audio,
* SIO pad exchange, and every guest timer slow together while per-vblank
* game logic runs on whatever vblanks do arrive (measured live: CDDA at
* 0.4x with gameplay 1.6x fast). Trading overclock DOWN in heavy scenes
* keeps guest time (and so vblank cadence, CDDA, inputs) at wall rate;
* headroom brings it back up toward the ceiling. Proportional controller
* on an EMA of the wall vblank period, adjusted at most every 8 vblanks,
* frozen during intentional unpaced windows (turbo loads / HLE boot).
* PSX_ADAPTIVE_OC=0 disables; =N sets the floor percent (default 200). */
{
static int aoc_enabled = -1;
static uint32_t aoc_floor = 200, aoc_ceiling = 0;
static double aoc_ema = 0.0;
static Uint64 aoc_last_pc = 0;
static int aoc_cool = 0;
if (aoc_enabled < 0) {
const char *e = std::getenv("PSX_ADAPTIVE_OC");
aoc_enabled = 1;
if (e && *e) {
int v = atoi(e);
if (v == 0) aoc_enabled = 0;
else if (v >= 100) aoc_floor = (uint32_t)v;
}
aoc_ceiling = psx_get_cpu_overclock();
if (aoc_ceiling <= aoc_floor) aoc_enabled = 0;
if (aoc_enabled)
std::fprintf(stdout,
"psxrecomp: adaptive overclock armed (floor %u%%, ceiling %u%%)\n",
aoc_floor, aoc_ceiling);
}
if (aoc_enabled && g_frame_period_ms > 0.0 && !turbo_loads_active &&
!psx_netplay_active()) {
Uint64 pc_now = SDL_GetPerformanceCounter();
if (aoc_last_pc) {
double dt = (double)(pc_now - aoc_last_pc) * 1000.0 /
(double)SDL_GetPerformanceFrequency();
if (dt > 0.0 && dt < 250.0) {
aoc_ema = (aoc_ema > 0.0) ? aoc_ema * 0.85 + dt * 0.15 : dt;
if (++aoc_cool >= 8 && aoc_ema > 0.0) {
aoc_cool = 0;
double r = aoc_ema / g_frame_period_ms;
uint32_t cur = psx_get_cpu_overclock();
if (r > 1.04) {
uint32_t n = (uint32_t)((double)cur / r);
if (n < aoc_floor) n = aoc_floor;
if (n < cur) psx_set_cpu_overclock(n);
} else if (r < 0.99 && cur < aoc_ceiling) {
uint32_t n = cur + (aoc_ceiling - cur + 9u) / 10u;
if (n > aoc_ceiling) n = aoc_ceiling;
psx_set_cpu_overclock(n);
}
}
} else {
aoc_ema = 0.0; /* pause/hitch: relearn */
}
}
aoc_last_pc = pc_now;
} else {
aoc_last_pc = 0; /* frozen window: do not learn across it */
}
}
/* FMV auto-skip ([video] auto_skip_fmv). A streaming FMV is XA audio + MDEC
* video together. Detect "MDEC produced a frame since the last vblank AND XA
@@ -6968,6 +6906,22 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) {
refresh_widescreen_projection();
}
/* Present-aspect scene switch: menus/2D (widescreen scene gate inactive)
* present at the game's own 4:3 so its NATURAL black bars stay, framed by
* the bezels; gameplay presents at the mod aspect. The gpu.c gate carries
* the 45-frame hysteresis and the bezel fade covers the transition. */
if (g_ws_engaged) {
extern int psx_ws_scene_wide_active(void);
static int s_last_present_wide = -1;
int wide_now = psx_ws_scene_wide_active() ? 1 : 0;
if (wide_now != s_last_present_wide) {
s_last_present_wide = wide_now;
gl_renderer_set_display_aspect(
wide_now ? g_video_aspect_num : 4,
wide_now ? g_video_aspect_den : 3);
}
}
/* Rollback resim (§33/§47): short catch-up keeps hold-last; long catch-up
* periodically presents live Replay VRAM so the display shows progress
* while sim stays uncapped. TipHold invent-cap stall uses hold-last from
+10 -5
View File
@@ -46,13 +46,18 @@ extern "C" SDL_AudioDeviceID psx_sdl_audio_open(
}
const SDL_AudioDeviceID device = SDL_GetAudioStreamDevice(s_audio_stream);
SDL_AudioSpec obtained = requested;
(void)SDL_GetAudioDeviceFormat(device, &obtained, nullptr);
if (have) {
/* Report the STREAM'S INPUT spec, not the device format: SDL3
* converts whatever we feed FROM `requested` TO the device rate
* internally. Reporting the device rate here made the DRC render
* at (typically) 48 kHz intent while SDL re-resampled it as
* 44.1 kHz — a constant ~0.92x pitch-down on every SDL3 build plus
* a perpetual ring overfill drained by overflow_drops clicks. The
* DRC is the only resampler; SDL3 must see honest 44.1 kHz input. */
std::memset(have, 0, sizeof(*have));
have->freq = obtained.freq;
have->format = obtained.format;
have->channels = obtained.channels;
have->freq = requested.freq;
have->format = requested.format;
have->channels = requested.channels;
have->samples = want->samples;
}