runloop: the audio pace bit reads whether a write happened, not who might have written

RUNLOOP_PACE_AUDIO required runloop_is_libretro_running(), on the
theory that with the core paused nothing writes to the driver. Wrong:
audio_driver_menu_sample() feeds a frame of silence every menu
iteration the core is not running, with the mixer, menu sounds and
thumbnail video audio mixed in, through the same blocking
audio_driver_flush() funnel. Audio holds the menu loop too, and the
overlay said it did not.

Stop predicting and record the fact. AUDIO_FLAG_WROTE is set at the
three driver write sites, cleared at the top of runloop_iterate()
alongside the pace reset, and the bit reads it. Whatever path reached
the write - core, menu silence, mixer - the answer is the same.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-02 06:29:46 +02:00
1 parent ee439c5ee8
commit c8f05b12f2
3 files changed
+20 -7

No files matched your search

+10 -1
View File
@@ -175,7 +175,16 @@ enum audio_driver_state_flags
* The threaded pipeline's producer only waits for ring space while
* this is set: with the consumer parked, waiting could never end.
*/
AUDIO_FLAG_STARTED = (1 << 9)
AUDIO_FLAG_STARTED = (1 << 9),
/**
* A driver write happened since the runloop last cleared this. Set at
* the write sites, whichever path reached them - core audio, or the
* frame of silence audio_driver_menu_sample() feeds while the core is
* paused with the mixer and thumbnail audio mixed in. Read by the
* runloop's pace record so "audio is holding the loop" is a fact
* about this iteration rather than a guess about who wrote.
*/
AUDIO_FLAG_WROTE = (1 << 10)
};
typedef struct audio_statistics
+3
View File
@@ -1248,6 +1248,7 @@ static void audio_driver_flush(audio_driver_state_t *audio_st,
out_frames, mixer_gain, override);
}
#endif
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_WROTE);
audio->write(audio_st->context_audio_data,
audio_st->output_samples_buf,
out_frames * 2 * sizeof(float));
@@ -1321,6 +1322,7 @@ static void audio_driver_flush(audio_driver_state_t *audio_st,
mixer_gain, override);
}
#endif
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_WROTE);
audio->write(audio_st->context_audio_data,
audio_st->output_samples_int16,
out_frames * 2 * sizeof(int16_t));
@@ -1738,6 +1740,7 @@ static void audio_driver_flush(audio_driver_state_t *audio_st,
output_frames *= sizeof(int16_t); /* Unit: bytes */
}
AUDIO_FLAGS_SET(audio_st, AUDIO_FLAG_WROTE);
audio->write(audio_st->context_audio_data,
output_data, output_frames * 2);
}
+7 -6
View File
@@ -7933,6 +7933,7 @@ int runloop_iterate(void)
* than path, so it is set here and holds for those early returns:
* they do rely on it. */
runloop_st->pace = RUNLOOP_PACE_NONE;
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_WROTE);
if ( settings->bools.video_vsync
&& !(input_st->flags & INP_FLAG_NONBLOCKING)
&& !(runloop_st->flags & RUNLOOP_FLAG_FORCE_NONBLOCK))
@@ -8287,14 +8288,14 @@ end:
* puts the driver into non-blocking mode for a few frames while
* audio_sync stays true, and during those frames audio is not
* holding anything. */
/* ...and a write actually happened this iteration. Not a predicate
* on who might have written: the menu feeds a frame of silence
* through the same blocking funnel while the core is paused, with
* the mixer and thumbnail audio mixed in, so "libretro running" was
* the wrong test. The flag is set at the write sites. */
if ( (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_ACTIVE)
&& !(AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_NONBLOCK)
/* ...and the core is actually writing samples this frame. In
* the menu with the core paused the driver stays in blocking
* mode but nothing calls audio_driver_write(). Same predicate
* the RUNLOOP_STATE_MENU throttle uses for this question. */
&& runloop_is_libretro_running(runloop_st,
settings->bools.menu_pause_libretro))
&& (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_WROTE))
runloop_st->pace |= RUNLOOP_PACE_AUDIO;
/* Mirrors the gate at the video_driver_scanline_after_frame() call
* site, which skips the wait under fast-forward. A failed