video: add Skip Duplicate Frames

A repeat the core sends as a NULL frame is left unpresented, so the
display receives the content's own frame rate and frame generation or
interpolation tools see the real cadence. It stays out of the way where
the presents are the effect (Black Frame Insertion, shader sub-frames,
Threaded Video Frame Repeat, a headset) and with the menu open,
fast-forward or a cached frame shown again.

While repeats are being skipped, the runloop holds the loop to the
content's period with its timer wherever only VSync or the threaded
hold would have, since neither can block on a frame that is never
presented, and the driver's release stands aside. Lanes in
samples/runloop/pacing and samples/gfx/threaded_video pin both halves.

Idea from beetle-psx-libretro#1013 by Razor221.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-12 21:11:42 +01:00
1 parent 13677e89f8
commit 1656558977
12 files changed
+283 -2

No files matched your search

+4
View File
@@ -571,6 +571,10 @@
* times. Off: the fixed-timer pacing threaded video always had. */
#define DEFAULT_VIDEO_THREADED_DISPLAY_PACING false
/* Leave a frame the core marks as a repeat unpresented, so the display
* sees the content's own frame rate. Off: every repeat is presented. */
#define DEFAULT_VIDEO_SKIP_DUPE_FRAMES false
/* The Direct3D 9 drivers on a Direct3D9Ex device: a window presented
* through the flip model, and the frame latency settings honoured.
* Off: the discard swap effect and the runtime's own queue, as before. */
+1
View File
@@ -754,6 +754,7 @@ typedef struct settings
bool video_threaded_display_pacing;
bool video_d3d9ex_present;
bool video_present_timing_from_display;
bool video_skip_dupe_frames;
bool video_font_enable;
bool video_disable_composition;
bool video_post_filter_record;
+28
View File
@@ -9749,6 +9749,34 @@ void video_driver_frame(const void *data, unsigned width,
if (data)
video_st->main_flags &= ~VIDEO_FLAG_FF_FRAME_OWED;
/* Skip Duplicate Frames: a repeat from the core is left unpresented,
* so the display gets the content's own rate. Only a repeat the core
* sent, not the cached frame sent again for pause or the menu, and
* not where the presents themselves are the effect: Black Frame
* Insertion, shader sub-frames, the threaded presenter's repeats or
* a headset's interval. The hold tells the runloop to pace these
* iterations itself (runloop_pace_dupe_skip_engages). */
if ( !data
&& render_frame
&& settings->bools.video_skip_dupe_frames
&& !video_st->frame_repeat
&& !menu_is_alive
&& !video_info.input_driver_nonblock_state
&& !video_info.black_frame_insertion
&& video_info.shader_subframes <= 1
&& !video_st->headset_interval
#ifdef HAVE_THREADS
&& !( video_st->thread_wrapper_active
&& video_info.threaded_present_repeat)
#endif
)
{
render_frame = false;
video_st->dupe_skip_hold = VIDEO_DUPE_SKIP_HOLD;
}
else if (video_st->dupe_skip_hold && !video_st->frame_repeat)
video_st->dupe_skip_hold--;
/* What the shader chains read as FrameTimeDelta, from the reading
* taken at the top of this function. Measured between the frames
* the driver is handed, so a frame dropped by fast-forward
+9
View File
@@ -2054,6 +2054,10 @@ typedef struct
unsigned driver_pacing_interval_us;
uint8_t driver_pacing_mode;
uint8_t driver_pacing_reason;
/* Skip Duplicate Frames: the core frames left before the runloop
* stops pacing for skipped repeats, rearmed by each one skipped.
* Main thread. */
uint8_t dupe_skip_hold;
bool threaded;
@@ -2606,6 +2610,11 @@ float video_driver_get_aspect_ratio(void);
/* The aspect ratio, as the bits of the float in an atomic word. A
* union, not a cast through a pointer, so it stays inside what the
* standard allows and MSVC's aliasing rules. */
/* Core frames a skipped repeat keeps the runloop's dupe-skip pacing on
* for: long enough to span the gaps between repeats down to 1 in 8. */
#define VIDEO_DUPE_SKIP_HOLD 8
#ifdef HAVE_THREADS
#define VIDEO_CACHE_KEEPER ((size_t)sthread_get_current_thread_id())
#else
+1
View File
@@ -2181,6 +2181,7 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
{ MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT, MENU_ENUM_SUBLABEL_VIDEO_THREADED_PRESENT_REPEAT },
{ MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING, MENU_ENUM_SUBLABEL_VIDEO_THREADED_DISPLAY_PACING },
{ MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY, MENU_ENUM_SUBLABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY },
{ MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES, MENU_ENUM_SUBLABEL_VIDEO_SKIP_DUPE_FRAMES },
{ MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY, MENU_ENUM_SUBLABEL_VIDEO_MAX_FRAME_LATENCY },
{ MENU_ENUM_LABEL_VIDEO_HARD_SYNC, MENU_ENUM_SUBLABEL_VIDEO_HARD_SYNC },
{ MENU_ENUM_LABEL_VIDEO_HARD_SYNC_FRAMES, MENU_ENUM_SUBLABEL_VIDEO_HARD_SYNC_FRAMES },
+1
View File
@@ -12907,6 +12907,7 @@ unsigned menu_displaylist_build_list(
{MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT, PARSE_ONLY_BOOL, false},
{MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING, PARSE_ONLY_BOOL, false},
{MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY, PARSE_ONLY_BOOL, false},
{MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY, PARSE_ONLY_INT, false},
{MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES, PARSE_ONLY_UINT, false},
#if defined(HAVE_D3DKMT) || defined(HAVE_KMS)
+2
View File
@@ -942,6 +942,7 @@ extern const struct msg_hash_us_lbl_blob_s msg_hash_us_lbl_blob;
#define MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT)
#define MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING)
#define MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY)
#define MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES)
#define MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY)
#define MENU_ENUM_LABEL_VIDEO_HARD_SYNC_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_HARD_SYNC)
#define MENU_ENUM_LABEL_VIDEO_HARD_SYNC_FRAMES_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_HARD_SYNC_FRAMES)
@@ -2727,6 +2728,7 @@ extern const struct msg_hash_us_lbl_blob_s msg_hash_us_lbl_blob;
#define MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT_STR "video_threaded_present_repeat"
#define MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING_STR "video_threaded_display_pacing"
#define MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY_STR "video_present_timing_from_display"
#define MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES_STR "video_skip_dupe_frames"
#define MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY_STR "video_max_frame_latency"
#define MENU_ENUM_LABEL_VIDEO_HARD_SYNC_STR "video_hard_sync"
#define MENU_ENUM_LABEL_VIDEO_HARD_SYNC_FRAMES_STR "video_hard_sync_frames"
+5 -1
View File
@@ -6289,6 +6289,9 @@ static runloop_pace_facts_t runloop_pace_gather(settings_t *settings,
if (video_context_driver_presentable()) f |= PACE_FACT_PRESENTABLE;
if (runloop_st->frame_limit_minimum_time) f |= PACE_FACT_FRAME_LIMIT;
if (core_ran) f |= PACE_FACT_CORE_RAN;
if ( core_ran
&& video_st->dupe_skip_hold
&& settings->bools.video_skip_dupe_frames) f |= PACE_FACT_DUPE_SKIP;
return runloop_driver_pacing_gather(settings, runloop_st, video_st, f);
}
@@ -10380,7 +10383,8 @@ end:
? video_st->video_refresh_rate_original
: settings->floats.video_refresh_rate);
}
else if (runloop_pace_driver_fallback(pace_facts, runloop_st->pace))
else if ( runloop_pace_driver_fallback(pace_facts, runloop_st->pace)
|| runloop_pace_dupe_skip_engages(pace_facts, runloop_st->pace))
{
runloop_st->pace |= RUNLOOP_PACE_TIMER;
pace_limit_min = runloop_content_frame_time_us(
+24 -1
View File
@@ -786,9 +786,12 @@ enum runloop_pace_fact
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_DRIVER_PACING_MISSING = (1 << 20) /* Driver Pacing is on, but
PACE_FACT_DRIVER_PACING_MISSING = (1 << 20),/* Driver Pacing is on, but
the driver has no backend
that took it */
PACE_FACT_DUPE_SKIP = (1 << 21) /* Skip Duplicate Frames is
leaving the core's repeats
unpresented */
};
typedef unsigned runloop_pace_facts_t;
@@ -822,6 +825,7 @@ static INLINE bool runloop_pace_driver_owns(runloop_pace_facts_t f,
{
return (f & PACE_FACT_DRIVER_PACING)
&& (f & PACE_FACT_CORE_RAN)
&& !(f & PACE_FACT_DUPE_SKIP)
&& (f & PACE_FACT_FOCUSED)
&& !(f & PACE_FACT_NONBLOCKING)
&& !(f & PACE_FACT_FASTMOTION)
@@ -859,6 +863,23 @@ static INLINE bool runloop_pace_driver_fallback(runloop_pace_facts_t f,
&& runloop_pace_driver_owns(f | PACE_FACT_DRIVER_PACING, pace);
}
/* A repeat left unpresented by Skip Duplicate Frames reaches no present,
* so neither VSync nor the threaded hold can keep the loop on it, and the
* presents between repeats come at the content's rate, which a swapchain
* queues without blocking. Where audio, a scanline or the timer do not
* already hold the loop, the timer holds it to the content's period for
* as long as repeats are being skipped. The driver's release waits on a
* present, so it stands aside for that time (runloop_pace_driver_owns). */
static INLINE bool runloop_pace_dupe_skip_engages(runloop_pace_facts_t f,
unsigned pace)
{
return (f & PACE_FACT_DUPE_SKIP)
&& !(f & PACE_FACT_NONBLOCKING)
&& !(f & PACE_FACT_FASTMOTION)
&& !(pace & (RUNLOOP_PACE_AUDIO | RUNLOOP_PACE_SCANLINE
| RUNLOOP_PACE_TIMER | RUNLOOP_PACE_NOWINDOW));
}
static INLINE unsigned runloop_pace_sources(runloop_pace_facts_t f)
{
unsigned pace = RUNLOOP_PACE_NONE;
@@ -932,6 +953,8 @@ static INLINE unsigned runloop_pace_decide(runloop_pace_facts_t f)
pace |= RUNLOOP_PACE_TIMER;
else if (runloop_pace_driver_fallback(f, pace))
pace |= RUNLOOP_PACE_TIMER;
else if (runloop_pace_dupe_skip_engages(f, pace))
pace |= RUNLOOP_PACE_TIMER;
return pace;
}
@@ -5813,6 +5813,150 @@ static void lane_dupe_under_wrapper(void)
fprintf(stderr, "[pass] dupe lane (NULL reaches the driver as NULL)\n");
}
/* Skip Duplicate Frames.
*
* With the setting on, a repeat the core sends (NULL) is not handed to
* the driver at all, so the display gets the content's own rate, and
* the hold that tells the runloop to pace those iterations is armed.
* Where the presents are the point - Black Frame Insertion, the menu -
* or with the setting off, the repeat still reaches the driver as NULL,
* and frames that are presented wind the hold down to nothing. Threaded
* and not: the decision is made before the wrapper is reached. */
#define SKIPLANE_LOG 16
static video_driver_t skiplane_driver;
static const video_driver_t *skiplane_inner;
static int skiplane_seen[SKIPLANE_LOG];
static retro_atomic_size_t skiplane_count;
static bool skiplane_frame(void *data, const void *frame,
unsigned dims, uint64_t frame_count,
unsigned pitch, const char *msg, video_frame_info_t *video_info)
{
size_t n = retro_atomic_load_acquire_size(&skiplane_count);
if (n < SKIPLANE_LOG)
skiplane_seen[n] = frame ? (int)*(const uint8_t*)frame : -1;
retro_atomic_store_release_size(&skiplane_count, n + 1);
return skiplane_inner->frame(data, NULL, dims, frame_count,
pitch, msg, video_info);
}
static void skiplane_push(const void *pix, bool threaded)
{
video_driver_frame(pix, 32, 32, 32 * sizeof(uint32_t));
if (threaded)
video_thread_wait_idle();
}
static void lane_dupe_skip(bool threaded)
{
unsigned had = failures;
const char *mode = threaded ? "threaded" : "unthreaded";
settings_t *settings = config_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
bool saved_skip = settings->bools.video_skip_dupe_frames;
bool saved_repeat = settings->bools.video_threaded_present_repeat;
unsigned saved_bfi = settings->uints.video_black_frame_insertion;
/* Static, as in the dupe lane: the cached frame borrows it */
static uint8_t pix_a[32 * 32 * sizeof(uint32_t)];
static uint8_t pix_b[32 * 32 * sizeof(uint32_t)];
size_t seen;
unsigned i;
memset(pix_a, 0x10, sizeof(pix_a));
memset(pix_b, 0x20, sizeof(pix_b));
set_threaded_via_setting(threaded);
run_frames(3);
expect_wrapper(threaded, "dupe skip lane");
if (menu_is_up())
command_event(CMD_EVENT_MENU_TOGGLE, NULL);
CHECK(!menu_is_up(), "dupe skip lane: menu still up");
run_frames(2);
if (threaded)
video_thread_wait_idle();
settings->bools.video_threaded_present_repeat = false;
settings->uints.video_black_frame_insertion = 0;
if (threaded)
{
thread_video_t *thr = (thread_video_t*)video_st->data;
skiplane_inner = thr->driver;
skiplane_driver = *thr->driver;
skiplane_driver.frame = skiplane_frame;
set_driver(thr, &skiplane_driver);
}
else
{
skiplane_inner = video_st->current_video;
skiplane_driver = *video_st->current_video;
skiplane_driver.frame = skiplane_frame;
video_st->current_video = &skiplane_driver;
}
/* On: real, repeat, real, repeat reaches the driver as two frames */
settings->bools.video_skip_dupe_frames = true;
video_st->dupe_skip_hold = 0;
retro_atomic_store_release_size(&skiplane_count, 0);
skiplane_push(pix_a, threaded);
skiplane_push(NULL, threaded);
skiplane_push(pix_b, threaded);
skiplane_push(NULL, threaded);
seen = retro_atomic_load_acquire_size(&skiplane_count);
CHECK(seen == 2,
"dupe skip lane (%s): 2 frames and 2 repeats reached the driver "
"as %u presents, want the 2 frames alone", mode, (unsigned)seen);
if (seen == 2)
CHECK(skiplane_seen[0] == 0x10 && skiplane_seen[1] == 0x20,
"dupe skip lane (%s): the driver saw 0x%02x, 0x%02x", mode,
skiplane_seen[0], skiplane_seen[1]);
CHECK(video_st->dupe_skip_hold == VIDEO_DUPE_SKIP_HOLD,
"dupe skip lane (%s): a skipped repeat left the hold at %u, "
"the runloop would not pace it", mode,
(unsigned)video_st->dupe_skip_hold);
/* Black Frame Insertion: the presents are the effect */
settings->uints.video_black_frame_insertion = 1;
retro_atomic_store_release_size(&skiplane_count, 0);
skiplane_push(NULL, threaded);
settings->uints.video_black_frame_insertion = 0;
seen = retro_atomic_load_acquire_size(&skiplane_count);
CHECK(seen == 1 && skiplane_seen[0] == -1,
"dupe skip lane (%s): under BFI a repeat reached the driver %u "
"times, want once as NULL", mode, (unsigned)seen);
/* Off: the repeat is presented, and presented frames wind the hold
* down to nothing */
settings->bools.video_skip_dupe_frames = false;
retro_atomic_store_release_size(&skiplane_count, 0);
for (i = 0; i < VIDEO_DUPE_SKIP_HOLD; i++)
skiplane_push(NULL, threaded);
seen = retro_atomic_load_acquire_size(&skiplane_count);
CHECK(seen == VIDEO_DUPE_SKIP_HOLD,
"dupe skip lane (%s): setting off, %u repeats reached the driver "
"as %u presents", mode, (unsigned)VIDEO_DUPE_SKIP_HOLD,
(unsigned)seen);
CHECK(video_st->dupe_skip_hold == 0,
"dupe skip lane (%s): the hold stayed at %u after %u presented "
"frames", mode, (unsigned)video_st->dupe_skip_hold,
(unsigned)VIDEO_DUPE_SKIP_HOLD);
if (threaded)
set_driver((thread_video_t*)video_st->data, skiplane_inner);
else
video_st->current_video = (video_driver_t*)skiplane_inner;
settings->bools.video_skip_dupe_frames = saved_skip;
settings->bools.video_threaded_present_repeat = saved_repeat;
settings->uints.video_black_frame_insertion = saved_bfi;
video_st->dupe_skip_hold = 0;
video_driver_cached_frame_invalidate();
if (!menu_is_up())
command_event(CMD_EVENT_MENU_TOGGLE, NULL);
run_frames(2);
if (failures == had)
fprintf(stderr, "[pass] dupe skip lane (%s: repeats unpresented, "
"BFI and setting off present them)\n", mode);
}
/* ------------------------------------------------------------------ */
/* Lane: a view count shrink behind queued frames */
/* A frame queued before the count shrinks draws with the views its */
@@ -12622,6 +12766,8 @@ int main(int argc, char *argv[])
lane_frame_path_heap();
lane_resize_under_wrapper();
lane_dupe_under_wrapper();
lane_dupe_skip(false);
lane_dupe_skip(true);
lane_view_count_shrink();
lane_movie_ring();
lane_suppress_screensaver();
+57
View File
@@ -232,6 +232,62 @@ static void test_driver_fallback(void)
"content's period exactly where a backend would have\n");
}
/* Skip Duplicate Frames: a repeat left unpresented reaches no present,
* so VSync and the threaded hold cannot keep the loop on it - with
* either alone, 30 fps content on a 60 Hz display ran two core frames
* per refresh, double speed. The timer holds the content's period
* wherever nothing that blocks without a present (audio, a scanline)
* does, and the driver's release, which waits on a present, stands
* aside. */
static void test_dupe_skip(void)
{
runloop_pace_facts_t d = PACE_FACT_FOCUSED | PACE_FACT_CORE_RAN
| PACE_FACT_PRESENTABLE | PACE_FACT_DUPE_SKIP;
DRIVER_ROW(d | PACE_FACT_VSYNC,
RUNLOOP_PACE_VSYNC | RUNLOOP_PACE_TIMER,
"dupe skip, vsync: the timer, vsync cannot hold a skipped repeat");
DRIVER_ROW((d | PACE_FACT_VSYNC) & ~PACE_FACT_DUPE_SKIP,
RUNLOOP_PACE_VSYNC,
"no repeats skipped: VSync alone, as before");
DRIVER_ROW(d | PACE_FACT_WRAPPER | PACE_FACT_DISPLAY_PACING,
RUNLOOP_PACE_DISPLAY | RUNLOOP_PACE_TIMER,
"dupe skip, display pacing: the timer, the hold needs a push");
DRIVER_ROW(d, RUNLOOP_PACE_TIMER,
"dupe skip, nothing else: the timer, not unthrottled");
DRIVER_ROW(d | PACE_FACT_VSYNC | PACE_FACT_AUDIO_HOLDING,
RUNLOOP_PACE_VSYNC | RUNLOOP_PACE_AUDIO,
"dupe skip, audio sync: audio alone, never two clocks");
DRIVER_ROW(d | PACE_FACT_SCANLINE_SYNC | PACE_FACT_SCANLINE_LOCKED,
RUNLOOP_PACE_SCANLINE,
"dupe skip, scanline sync: the scanline alone");
DRIVER_ROW(d | PACE_FACT_VSYNC | PACE_FACT_VRR | PACE_FACT_FRAME_LIMIT,
RUNLOOP_PACE_VSYNC | RUNLOOP_PACE_TIMER,
"dupe skip, VRR: its own timer, once");
DRIVER_ROW(d | PACE_FACT_VSYNC | PACE_FACT_DRIVER_PACING,
RUNLOOP_PACE_VSYNC | RUNLOOP_PACE_TIMER,
"dupe skip, driver pacing: no release, which waits on a present");
DRIVER_ROW(d | PACE_FACT_DRIVER_PACING_MISSING,
RUNLOOP_PACE_TIMER,
"dupe skip, Driver Pacing without a backend: one timer");
DRIVER_ROW(d | PACE_FACT_VSYNC | PACE_FACT_FASTMOTION
| PACE_FACT_NONBLOCKING,
RUNLOOP_PACE_NONE,
"dupe skip, fast-forward: unthrottled");
DRIVER_ROW((d | PACE_FACT_VSYNC) & ~PACE_FACT_PRESENTABLE,
RUNLOOP_PACE_VSYNC | RUNLOOP_PACE_TIMER,
"dupe skip, no window, vsync: the timer, nothing presents");
DRIVER_ROW(d & ~PACE_FACT_PRESENTABLE,
RUNLOOP_PACE_NOWINDOW,
"dupe skip, no window: the no-window wait alone");
check(runloop_pace_dupe_skip_engages(d, RUNLOOP_PACE_NOWINDOW) == false,
"dupe skip: the no-window wait is not paced twice");
printf(" dupe skip: the timer holds the content's period wherever "
"only the display would have, never beside audio, a scanline "
"or another timer, and the driver's release stands aside\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. */
@@ -1035,6 +1091,7 @@ int main(void)
test_driver_pacing();
test_driver_interval();
test_driver_fallback();
test_dupe_skip();
if (failures)
{
+5
View File
@@ -77,6 +77,11 @@ S_BOOL(video_present_timing_from_display, VIDEO_PRESENT_TIMING_FROM_DISPLAY,
DEFAULT_VIDEO_PRESENT_TIMING_FROM_DISPLAY, SD_FLAG_NONE, 0, CMD_EVENT_NONE,
"Pace Repeats From The Display",
"Time repeated frames from when the display says a present actually reached it, rather than from the frontend's own clock. Applies where the video driver can report it; the clock is used everywhere else, and wherever the report goes stale. Turn off to always use the clock.")
S_BOOL(video_skip_dupe_frames, VIDEO_SKIP_DUPE_FRAMES,
"video_skip_dupe_frames",
DEFAULT_VIDEO_SKIP_DUPE_FRAMES, SD_FLAG_NONE, 0, CMD_EVENT_NONE,
"Skip Duplicate Frames",
"Do not present a frame the core marks as a repeat of the previous one, so the display receives the content's own frame rate: 30 fps content arrives at 30 Hz. For frame generation and interpolation tools that work on presented frames. While repeats are skipped, audio sync, Scanline Sync or the frontend's timer paces the content instead of VSync. Needs Frame Duping in the core. Not applied with the menu open, fast-forward, Black Frame Insertion, shader sub-frames or Threaded Video Frame Repeat.")
S_INT(video_max_frame_latency, VIDEO_MAX_FRAME_LATENCY,
"video_max_frame_latency",
DEFAULT_MAX_FRAME_LATENCY, SD_FLAG_CMD_APPLY_AUTO, SDESC_RANGE_MINMAX, CMD_EVENT_REINIT, -1, MAXIMUM_MAX_FRAME_LATENCY, 1, -1, setting_action_ok_uint, NULL,