runloop: hold the content rate where Driver Pacing has no backend

With Driver Pacing on and nothing else holding the loop, the backend's
release keeps the content's frame period. On a driver without a backend
- gl, glcore, or a GPU without the extension - the setting did nothing,
and with vsync and audio sync off the loop ran unthrottled. The frame
limiter now holds the content's period in exactly the iterations a
backend would have owned: a content frame at normal speed, focused, the
menu down, and nothing else pacing. Driver Pacing off is unchanged.
This commit is contained in:
libretroadmin committed 2026-11-03 14:55:28 +01:00
1 parent 8ca610dc6e
commit 23f465bf2d
3 files changed
+71 -1

No files matched your search

+17
View File
@@ -6157,9 +6157,11 @@ static runloop_pace_facts_t runloop_driver_pacing_gather(settings_t *settings,
{
unsigned want = settings->uints.video_driver_pacing;
unsigned reason = VIDEO_DRIVER_PACING_REASON_NONE;
bool asked;
const video_poke_interface_t *poke = video_st->poke;
if (want >= VIDEO_DRIVER_PACING_LAST)
want = VIDEO_DRIVER_PACING_OFF;
asked = want != VIDEO_DRIVER_PACING_OFF;
if (!want)
reason = VIDEO_DRIVER_PACING_REASON_OFF;
else if (!poke || !poke->driver_pacing_set || !video_st->data)
@@ -6190,6 +6192,13 @@ static runloop_pace_facts_t runloop_driver_pacing_gather(settings_t *settings,
f |= PACE_FACT_DRIVER_PACING;
else
{
/* nothing could take it: runloop_pace_driver_fallback() */
if (asked && ( reason == VIDEO_DRIVER_PACING_REASON_DRIVER
|| reason == VIDEO_DRIVER_PACING_REASON_DEVICE
|| reason == VIDEO_DRIVER_PACING_REASON_SWAPCHAIN
|| reason == VIDEO_DRIVER_PACING_REASON_FAILED
|| reason == VIDEO_DRIVER_PACING_REASON_CORE))
f |= PACE_FACT_DRIVER_PACING_MISSING;
runloop_st->driver_pacing_frame_id = 0;
video_st->driver_pacing_id = 0;
video_st->driver_pacing_interval_us = 0;
@@ -10319,6 +10328,14 @@ end:
? video_st->video_refresh_rate_original
: settings->floats.video_refresh_rate);
}
else if (runloop_pace_driver_fallback(pace_facts, runloop_st->pace))
{
runloop_st->pace |= RUNLOOP_PACE_TIMER;
pace_limit_min = runloop_content_frame_time_us(
video_st->core_hz);
pace_limit_ns = runloop_content_frame_time_ns(
video_st->core_hz);
}
/* The low-latency backend releases the next frame: the hold is the
* driver's signal, taken here where the frame delay would have
+18 -1
View File
@@ -766,7 +766,10 @@ enum runloop_pace_fact
PACE_FACT_FRAME_LIMIT = (1 << 17), /* frame_limit_minimum_time != 0 */
PACE_FACT_DRIVER_PACING = (1 << 18), /* the driver's low-latency backend
is up and took the mode */
PACE_FACT_CORE_RAN = (1 << 19) /* this iteration ran the core */
PACE_FACT_CORE_RAN = (1 << 19), /* this iteration ran the core */
PACE_FACT_DRIVER_PACING_MISSING = (1 << 20) /* Driver Pacing is on, but
the driver has no backend
that took it */
};
typedef unsigned runloop_pace_facts_t;
@@ -825,6 +828,18 @@ static INLINE unsigned runloop_pace_driver_interval(unsigned pace,
return (unsigned)content_period;
}
/* Driver Pacing on a driver with no backend for it (gl, glcore, a GPU
* without the extension): where the backend would have owned the
* release with nothing else holding the loop, the timer holds it to
* the content's period, the interval the backend would have kept. */
static INLINE bool runloop_pace_driver_fallback(runloop_pace_facts_t f,
unsigned pace)
{
return (f & PACE_FACT_DRIVER_PACING_MISSING)
&& pace == RUNLOOP_PACE_NONE
&& runloop_pace_driver_owns(f | PACE_FACT_DRIVER_PACING, pace);
}
static INLINE unsigned runloop_pace_sources(runloop_pace_facts_t f)
{
unsigned pace = RUNLOOP_PACE_NONE;
@@ -896,6 +911,8 @@ static INLINE unsigned runloop_pace_decide(runloop_pace_facts_t f)
(f & PACE_FACT_SCANLINE_SYNC) != 0,
(f & PACE_FACT_RATE_CONTROL) != 0))
pace |= RUNLOOP_PACE_TIMER;
else if (runloop_pace_driver_fallback(f, pace))
pace |= RUNLOOP_PACE_TIMER;
return pace;
}
+36
View File
@@ -197,6 +197,41 @@ static void test_driver_pacing(void)
"scanline sync, never as a clock\n");
}
/* Driver Pacing on, but nothing that could take it - gl, glcore, a GPU
* without the extension: the timer holds the loop where the backend
* would have, else vsync off and audio sync off run it unthrottled. */
static void test_driver_fallback(void)
{
runloop_pace_facts_t m = PACE_FACT_FOCUSED | PACE_FACT_CORE_RAN
| PACE_FACT_DRIVER_PACING_MISSING | PACE_FACT_PRESENTABLE;
DRIVER_ROW(m, RUNLOOP_PACE_TIMER,
"no backend, nothing else: the timer at the content's period");
DRIVER_ROW(m | PACE_FACT_WRAPPER, RUNLOOP_PACE_TIMER,
"no backend, threaded, no display pacing: the timer");
DRIVER_ROW(m & ~PACE_FACT_DRIVER_PACING_MISSING, RUNLOOP_PACE_NONE,
"Driver Pacing off: unthrottled, as before");
DRIVER_ROW(m | PACE_FACT_VSYNC, RUNLOOP_PACE_VSYNC,
"no backend, vsync: VSync alone");
DRIVER_ROW(m | PACE_FACT_AUDIO_HOLDING, RUNLOOP_PACE_AUDIO,
"no backend, audio sync: Audio alone");
DRIVER_ROW(m | PACE_FACT_WRAPPER | PACE_FACT_DISPLAY_PACING,
RUNLOOP_PACE_DISPLAY,
"no backend, display pacing: the hold alone");
DRIVER_ROW(m | PACE_FACT_RATE_CONTROL, RUNLOOP_PACE_TIMER,
"no backend, rate control: the gap limiter");
DRIVER_ROW(m | PACE_FACT_MENU_ALIVE, RUNLOOP_PACE_NONE,
"no backend, menu up: the menu's own pacing");
DRIVER_ROW(m | PACE_FACT_FASTMOTION | PACE_FACT_NONBLOCKING,
RUNLOOP_PACE_NONE,
"no backend, fast-forward: unthrottled");
DRIVER_ROW(m & ~PACE_FACT_FOCUSED, RUNLOOP_PACE_NONE,
"no backend, unfocused: as with a backend");
printf(" driver pacing without a backend: the timer holds the "
"content's period exactly where a backend would have\n");
}
/* The interval each owned release hands the driver, from the whole
* decision: a release with nothing else holding the loop keeps the
* content's rate rather than none. */
@@ -911,6 +946,7 @@ int main(void)
test_menu_table();
test_driver_pacing();
test_driver_interval();
test_driver_fallback();
if (failures)
{