Skip to content

Commit 076dffc

Browse files
authored
Merge pull request libretro#80 from jdgleaver/frameskip-latency
Frameskip improvements
2 parents 8acd98a + c67092a commit 076dffc

File tree

3 files changed

+135
-20
lines changed

3 files changed

+135
-20
lines changed

libretro.c

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,19 @@ static int32_t samples_per_frame = 0;
5555
static int32_t samplerate = (((SNES_CLOCK_SPEED * 6) / (32 * ONE_APU_CYCLE)));
5656

5757
static unsigned frameskip_type = 0;
58+
static unsigned frameskip_threshold = 0;
5859
static uint16_t frameskip_counter = 0;
60+
5961
static bool retro_audio_buff_active = false;
6062
static unsigned retro_audio_buff_occupancy = 0;
6163
static bool retro_audio_buff_underrun = false;
6264
/* Maximum number of consecutive frames that
6365
* can be skipped */
6466
#define FRAMESKIP_MAX 30
6567

68+
static unsigned retro_audio_latency = 0;
69+
static bool update_audio_latency = false;
70+
6671
#ifdef PERF_TEST
6772
#define RETRO_PERFORMANCE_INIT(name) \
6873
retro_perf_tick_t current_ticks; \
@@ -157,11 +162,34 @@ static void retro_set_audio_buff_status_cb(void)
157162
retro_audio_buff_active = false;
158163
retro_audio_buff_occupancy = 0;
159164
retro_audio_buff_underrun = false;
165+
retro_audio_latency = 0;
166+
}
167+
else
168+
{
169+
/* Frameskip is enabled - increase frontend
170+
* audio latency to minimise potential
171+
* buffer underruns */
172+
uint32_t frame_time_usec = Settings.FrameTime;
173+
174+
if (Settings.ForceNTSC)
175+
frame_time_usec = Settings.FrameTimeNTSC;
176+
if (Settings.ForcePAL)
177+
frame_time_usec = Settings.FrameTimePAL;
178+
179+
/* Set latency to 6x current frame time... */
180+
retro_audio_latency = (unsigned)(6 * frame_time_usec / 1000);
181+
182+
/* ...then round up to nearest multiple of 32 */
183+
retro_audio_latency = (retro_audio_latency + 0x1F) & ~0x1F;
160184
}
161185
}
162186
else
163-
environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK,
164-
NULL);
187+
{
188+
environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, NULL);
189+
retro_audio_latency = 0;
190+
}
191+
192+
update_audio_latency = true;
165193
}
166194

167195
void S9xDeinitDisplay(void)
@@ -383,7 +411,16 @@ void retro_deinit(void)
383411
perf_cb.perf_log();
384412
#endif
385413

414+
/* Reset globals (required for static builds) */
386415
libretro_supports_bitmasks = false;
416+
frameskip_type = 0;
417+
frameskip_threshold = 0;
418+
frameskip_counter = 0;
419+
retro_audio_buff_active = false;
420+
retro_audio_buff_occupancy = 0;
421+
retro_audio_buff_underrun = false;
422+
retro_audio_latency = 0;
423+
update_audio_latency = false;
387424
}
388425

389426
uint32_t S9xReadJoypad(int32_t port)
@@ -423,14 +460,19 @@ uint32_t S9xReadJoypad(int32_t port)
423460
return joypad;
424461
}
425462

426-
static void check_variables(void)
463+
static void check_variables(bool first_run)
427464
{
428465
struct retro_variable var;
466+
bool prev_force_ntsc;
467+
bool prev_force_pal;
429468
bool prev_frameskip_type;
430469

431470
var.key = "catsfc_VideoMode";
432471
var.value = NULL;
433472

473+
prev_force_ntsc = Settings.ForceNTSC;
474+
prev_force_pal = Settings.ForcePAL;
475+
434476
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
435477
{
436478
Settings.ForceNTSC = !strcmp(var.value, "NTSC");
@@ -447,14 +489,17 @@ static void check_variables(void)
447489
{
448490
if (strcmp(var.value, "auto") == 0)
449491
frameskip_type = 1;
450-
else if (strcmp(var.value, "aggressive") == 0)
492+
else if (strcmp(var.value, "manual") == 0)
451493
frameskip_type = 2;
452-
else if (strcmp(var.value, "max") == 0)
453-
frameskip_type = 3;
454494
}
455495

456-
if (frameskip_type != prev_frameskip_type)
457-
retro_set_audio_buff_status_cb();
496+
var.key = "catsfc_frameskip_threshold";
497+
var.value = NULL;
498+
499+
frameskip_threshold = 33;
500+
501+
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
502+
frameskip_threshold = strtol(var.value, NULL, 10);
458503

459504
var.key = "catsfc_overclock_cycles";
460505
var.value = NULL;
@@ -489,6 +534,13 @@ static void check_variables(void)
489534
else
490535
reduce_sprite_flicker = false;
491536
}
537+
538+
/* Reinitialise frameskipping, if required */
539+
if (!first_run &&
540+
((frameskip_type != prev_frameskip_type) ||
541+
(Settings.ForceNTSC != prev_force_ntsc) ||
542+
(Settings.ForcePAL != prev_force_pal)))
543+
retro_set_audio_buff_status_cb();
492544
}
493545

494546
static int32_t samples_to_play = 0;
@@ -502,7 +554,7 @@ void retro_run(void)
502554
#endif
503555

504556
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
505-
check_variables();
557+
check_variables(false);
506558

507559
#ifdef NO_VIDEO_OUTPUT
508560
video_cb(NULL, IPPU.RenderedScreenWidth, IPPU.RenderedScreenHeight, GFX.Pitch);
@@ -544,11 +596,8 @@ void retro_run(void)
544596
case 1: /* auto */
545597
skip_frame = retro_audio_buff_underrun;
546598
break;
547-
case 2: /* aggressive */
548-
skip_frame = (retro_audio_buff_occupancy < 33);
549-
break;
550-
case 3: /* max */
551-
skip_frame = (retro_audio_buff_occupancy < 50);
599+
case 2: /* manual */
600+
skip_frame = (retro_audio_buff_occupancy < frameskip_threshold);
552601
break;
553602
default:
554603
skip_frame = false;
@@ -567,6 +616,18 @@ void retro_run(void)
567616
}
568617
}
569618

619+
/* If frameskip/timing settings have changed,
620+
* update frontend audio latency
621+
* > Can do this before or after the frameskip
622+
* check, but doing it after means we at least
623+
* retain the current frame's audio output */
624+
if (update_audio_latency)
625+
{
626+
environ_cb(RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY,
627+
&retro_audio_latency);
628+
update_audio_latency = false;
629+
}
630+
570631
poll_cb();
571632

572633
RETRO_PERFORMANCE_INIT(S9xMainLoop_func);
@@ -960,7 +1021,7 @@ bool retro_load_game(const struct retro_game_info* game)
9601021

9611022
CPU.Flags = 0;
9621023
init_descriptors();
963-
check_variables();
1024+
check_variables(true);
9641025

9651026
#ifdef LOAD_FROM_MEMORY_TEST
9661027
if (!LoadROM(game))

libretro.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,36 @@ enum retro_mod
13441344
* in the frontend.
13451345
*/
13461346

1347+
#define RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY 63
1348+
/* const unsigned * --
1349+
* Sets minimum frontend audio latency in milliseconds.
1350+
* Resultant audio latency may be larger than set value,
1351+
* or smaller if a hardware limit is encountered. A frontend
1352+
* is expected to honour requests up to 512 ms.
1353+
*
1354+
* - If value is less than current frontend
1355+
* audio latency, callback has no effect
1356+
* - If value is zero, default frontend audio
1357+
* latency is set
1358+
*
1359+
* May be used by a core to increase audio latency and
1360+
* therefore decrease the probability of buffer under-runs
1361+
* (crackling) when performing 'intensive' operations.
1362+
* A core utilising RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK
1363+
* to implement audio-buffer-based frame skipping may achieve
1364+
* optimal results by setting the audio latency to a 'high'
1365+
* (typically 6x or 8x) integer multiple of the expected
1366+
* frame time.
1367+
*
1368+
* WARNING: This can only be called from within retro_run().
1369+
* Calling this can require a full reinitialization of audio
1370+
* drivers in the frontend, so it is important to call it very
1371+
* sparingly, and usually only with the users explicit consent.
1372+
* An eventual driver reinitialize will happen so that audio
1373+
* callbacks happening after this call within the same retro_run()
1374+
* call will target the newly initialized driver.
1375+
*/
1376+
13471377
/* VFS functionality */
13481378

13491379
/* File paths:

libretro_core_options.h

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,40 @@ struct retro_core_option_definition option_defs_us[] = {
6969
{
7070
"catsfc_frameskip",
7171
"Frameskip",
72-
"Automatically skip frames to avoid audio buffer under-run (crackling). 'Aggressive' and 'Max' increase the buffer threshold at which frames are skipped. Improves performance at the expense of visual smoothness. NOTE: For best results, frontend 'Audio Latency' should be set to at least 128 ms.",
72+
"Skip frames to avoid audio buffer under-run (crackling). Improves performance at the expense of visual smoothness. 'Auto' skips frames when advised by the frontend. 'Manual' utilises the 'Frameskip Threshold (%)' setting.",
7373
{
74-
{ "disabled", NULL },
75-
{ "auto", "Auto" },
76-
{ "aggressive", "Aggressive" },
77-
{ "max", "Max" },
74+
{ "disabled", NULL },
75+
{ "auto", "Auto" },
76+
{ "manual", "Manual" },
7877
{ NULL, NULL },
7978
},
8079
"disabled"
8180
},
81+
{
82+
"catsfc_frameskip_threshold",
83+
"Frameskip Threshold (%)",
84+
"When 'Frameskip' is set to 'Manual', specifies the audio buffer occupancy threshold (percentage) below which frames will be skipped. Higher values reduce the risk of crackling by causing frames to be dropped more frequently.",
85+
{
86+
{ "15", NULL },
87+
{ "18", NULL },
88+
{ "21", NULL },
89+
{ "24", NULL },
90+
{ "27", NULL },
91+
{ "30", NULL },
92+
{ "33", NULL },
93+
{ "36", NULL },
94+
{ "39", NULL },
95+
{ "42", NULL },
96+
{ "45", NULL },
97+
{ "48", NULL },
98+
{ "51", NULL },
99+
{ "54", NULL },
100+
{ "57", NULL },
101+
{ "60", NULL },
102+
{ NULL, NULL },
103+
},
104+
"33"
105+
},
82106
{
83107
"catsfc_overclock_cycles",
84108
"Reduce Slowdown (Hack, Unsafe, Restart)",

0 commit comments

Comments
 (0)