runloop: audio yields to Scanline Sync

With Scanline Sync and audio_sync both live the loop is held by two
display-rate clocks in series: each frame waits for the audio buffer
to drain, then for the beam to reach the target. Measured at 25 ms per
frame - 40 fps - on a 120 Hz panel with 60 fps content, and the
overlay read "Audio+Scanline". At matched refresh the frame rate is
fine but the two clocks drift against each other with nothing to
arbitrate, the same fight VSync + audio_sync has.

When the scanline wait holds the loop it is the pace. Audio goes
non-blocking and Dynamic Audio Rate Control tracks the display through
the resampler ratio, which is what it exists for. The AUDIO bit is
cleared so the overlay reports what is actually holding the loop.
Restored through the same call every other blocking transition uses
when the scanline wait stops - VRR toggled on, calibration lost,
setting turned off.

Scoped to Scanline Sync only. VSync + audio_sync is the same conflict
with a far larger installed base and is not touched here.

runloop.h consumers checked; the one failure, gfx/drivers/rsx_gfx.c, is
PS3-only and fails identically on HEAD.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-02 06:10:28 +02:00
1 parent abcae92539
commit e88eea9911
2 files changed
+36

No files matched your search

+35
View File
@@ -8312,6 +8312,41 @@ end:
runloop_st->pace |= RUNLOOP_PACE_TIMER;
}
/* One display clock at a time: Scanline Sync and audio backpressure.
*
* With both blocking, each frame waits for the audio buffer to drain
* and then for the beam to reach the target, in series - 25 ms per
* frame measured on a 120 Hz panel with 60 fps content, and at
* matched refresh the two clocks still drift against each other with
* no arbiter. When the scanline wait is live it is the pace; audio
* goes non-blocking and Dynamic Audio Rate Control tracks the
* display through the resampler, which is exactly what it is for.
* Restored when the scanline wait stops holding the loop.
*
* Deliberately not applied to VSync + audio_sync, which is the same
* conflict with a far larger installed base; that one is a separate
* decision. */
if (runloop_st->pace & RUNLOOP_PACE_SCANLINE)
{
if ( (runloop_st->pace & RUNLOOP_PACE_AUDIO)
&& !runloop_st->audio_yielded_to_scanline)
{
audio_driver_set_nonblock_state(true);
runloop_st->audio_yielded_to_scanline = true;
}
runloop_st->pace &= ~RUNLOOP_PACE_AUDIO;
}
else if (runloop_st->audio_yielded_to_scanline)
{
/* Back to whatever audio_sync asks for, via the same call every
* other transition uses. */
audio_driver_set_nonblock_state(
settings->bools.audio_sync
? (input_st->flags & INP_FLAG_NONBLOCKING ? true : false)
: true);
runloop_st->audio_yielded_to_scanline = false;
}
/* if there's a fast forward limit, inject sleeps to keep from going too fast. */
{
retro_time_t frame_limit_min = runloop_st->frame_limit_minimum_time;
+1
View File
@@ -182,6 +182,7 @@ struct runloop
retro_time_t frame_limit_minimum_time;
retro_time_t frame_limit_last_time;
unsigned pace; /* enum runloop_pace_source bits */
bool audio_yielded_to_scanline; /* audio set non-blocking because Scanline Sync holds the loop */
retro_usec_t frame_time_last; /* int64_t alignment */
/* Per-frame scalar state. Kept adjacent to the timing block above so the