Add optional automatic frame skipping based on frontend audio buffer level

This commit is contained in:
jdgleaver 2020-10-21 17:38:15 +01:00
parent a2762a0d2a
commit 942d58bf07
5 changed files with 337 additions and 57 deletions

View File

@ -383,6 +383,22 @@ else ifeq ($(platform), emscripten)
TARGET := $(TARGET_NAME)_libretro_$(platform).bc
DEFINES += -std=gnu99 -DHAVE_LOCALE
# GCW0
else ifeq ($(platform), gcw0)
TARGET := $(TARGET_NAME)_libretro.so
fpic := -fPIC
SHARED := -shared -Wl,-version-script=link.T
CC = /opt/gcw0-toolchain/usr/bin/mipsel-linux-gcc
AR = /opt/gcw0-toolchain/usr/bin/mipsel-linux-ar
PLATFORM_DEFINES += -D_GNU_SOURCE
CFLAGS += -fomit-frame-pointer -ffast-math -march=mips32 -mtune=mips32r2 -mhard-float
# The core is very slow on GCW0 platforms
# Use all possible optimisations...
CFLAGS += -finline-limit=42 -fno-unroll-loops -fno-ipa-cp
CFLAGS += -fno-common -fno-stack-protector -fno-guess-branch-probability
CFLAGS += -fno-caller-saves -fno-tree-loop-if-convert -fno-regmove
DEFINES += -std=c99
# Windows
else
TARGET := $(TARGET_NAME)_libretro.dll

View File

@ -102,6 +102,130 @@ static unsigned imcapWidth;
static unsigned imcapHeight;
static size_t camStride;
static bool envVarsUpdated;
static unsigned frameskipType;
static unsigned frameskipThreshold;
static uint16_t frameskipCounter;
static bool retroAudioBuffActive;
static unsigned retroAudioBuffOccupancy;
static bool retroAudioBuffUnderrun;
static unsigned retroAudioLatency;
static bool updateAudioLatency;
/* Maximum number of consecutive frames that
* can be skipped */
#define RETRO_FRAMESKIP_MAX 30
/* Frame skipping functions */
static void _retroAudioBuffStatusCallback(
bool active, unsigned occupancy, bool underrun_likely) {
retroAudioBuffActive = active;
retroAudioBuffOccupancy = occupancy;
retroAudioBuffUnderrun = underrun_likely;
}
static void _initFrameskip(void) {
if (frameskipType > 0) {
bool calculateAudioLatency = true;
if (frameskipType == 3) { /* Fixed Interval */
environCallback(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, NULL);
} else {
struct retro_audio_buffer_status_callback BuffStatusCb;
BuffStatusCb.callback = _retroAudioBuffStatusCallback;
if (!environCallback(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, &BuffStatusCb)) {
if (logCallback)
logCallback(RETRO_LOG_WARN, "Frameskip disabled - frontend does not support audio buffer status monitoring.\n");
retroAudioBuffActive = false;
retroAudioBuffOccupancy = 0;
retroAudioBuffUnderrun = false;
retroAudioLatency = 0;
calculateAudioLatency = false;
}
}
if (calculateAudioLatency) {
/* Frameskip is enabled - increase frontend
* audio latency to minimise potential
* buffer underruns */
float frameTimeMsec = 1000.0f * (float)core->frameCycles(core) /
(float)core->frequency(core);
/* Set latency to 6x current frame time... */
retroAudioLatency = (unsigned)((6.0f * frameTimeMsec) + 0.5f);
/* ...then round up to nearest multiple of 32 */
retroAudioLatency = (retroAudioLatency + 0x1F) & ~0x1F;
}
} else {
environCallback(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, NULL);
retroAudioLatency = 0;
}
updateAudioLatency = true;
}
static void _loadFrameskipSettings(struct mCoreOptions *opts) {
struct retro_variable var;
unsigned oldFrameskipType;
unsigned frameskipInterval;
var.key = "mgba_frameskip";
var.value = 0;
oldFrameskipType = frameskipType;
frameskipType = 0;
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
if (strcmp(var.value, "auto") == 0) {
frameskipType = 1;
} else if (strcmp(var.value, "auto_threshold") == 0) {
frameskipType = 2;
} else if (strcmp(var.value, "fixed_interval") == 0) {
frameskipType = 3;
}
}
var.key = "mgba_frameskip_threshold";
var.value = 0;
frameskipThreshold = 33;
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
frameskipThreshold = strtol(var.value, NULL, 10);
var.key = "mgba_frameskip_interval";
var.value = 0;
frameskipInterval = 0;
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
frameskipInterval = strtol(var.value, NULL, 10);
/* Update internal (mGBA config) frameskip value */
if (opts) {
opts->frameskip = (frameskipType == 3) ?
frameskipInterval : 0;
} else {
mCoreConfigSetUIntValue(&core->config, "frameskip",
(frameskipType == 3) ? frameskipInterval : 0);
mCoreLoadConfig(core);
}
/* (Re)initialise frameskipping, if required */
if (opts || (frameskipType != oldFrameskipType)) {
_initFrameskip();
}
}
/* Video post processing */
#if defined(COLOR_16_BIT) && defined(COLOR_5_6_5)
@ -970,11 +1094,7 @@ static void _reloadSettings(void) {
}
}
var.key = "mgba_frameskip";
var.value = 0;
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
opts.frameskip = strtol(var.value, NULL, 10);
}
_loadFrameskipSettings(&opts);
var.key = "mgba_idle_optimization";
var.value = 0;
@ -1151,6 +1271,15 @@ void retro_init(void) {
if (environCallback(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
libretro_supports_bitmasks = true;
frameskipType = 0;
frameskipThreshold = 0;
frameskipCounter = 0;
retroAudioBuffActive = false;
retroAudioBuffOccupancy = 0;
retroAudioBuffUnderrun = false;
retroAudioLatency = 0;
updateAudioLatency = false;
}
void retro_deinit(void) {
@ -1211,6 +1340,7 @@ int16_t cycleturbo(bool x/*turbo A*/, bool y/*turbo B*/, bool l2/*turbo L*/, boo
void retro_run(void) {
uint16_t keys;
bool skipFrame = false;
_initSensors();
inputPollCallback();
@ -1238,12 +1368,7 @@ void retro_run(void) {
}
}
var.key = "mgba_frameskip";
var.value = 0;
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
mCoreConfigSetUIntValue(&core->config, "frameskip", strtol(var.value, NULL, 10));
mCoreLoadConfig(core);
}
_loadFrameskipSettings(NULL);
#if defined(COLOR_16_BIT) && defined(COLOR_5_6_5)
_loadPostProcessingSettings();
@ -1300,17 +1425,95 @@ void retro_run(void) {
}
}
/* Check whether current frame should
* be skipped */
if ((frameskipType > 0) &&
(frameskipType != 3) && /* Ignore 'Fixed Interval' - handled internally */
retroAudioBuffActive) {
switch (frameskipType) {
case 1: /* Auto */
skipFrame = retroAudioBuffUnderrun;
break;
case 2: /* Auto (Threshold) */
skipFrame = (retroAudioBuffOccupancy < frameskipThreshold);
break;
default:
skipFrame = false;
break;
}
if (skipFrame) {
if(frameskipCounter < RETRO_FRAMESKIP_MAX) {
switch (core->platform(core)) {
#ifdef M_CORE_GBA
case PLATFORM_GBA:
((struct GBA*) core->board)->video.frameskipCounter = 1;
break;
#endif
#ifdef M_CORE_GB
case PLATFORM_GB:
((struct GB*) core->board)->video.frameskipCounter = 1;
break;
#endif
default:
break;
}
frameskipCounter++;
} else {
frameskipCounter = 0;
skipFrame = false;
}
} else {
frameskipCounter = 0;
}
}
/* If frameskip settings have changed, update
* frontend audio latency */
if (updateAudioLatency)
{
environCallback(RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY,
&retroAudioLatency);
updateAudioLatency = false;
}
core->runFrame(core);
unsigned width, height;
core->desiredVideoDimensions(core, &width, &height);
/* If using 'Fixed Interval' frameskipping, check
* whether a frame is currently available */
if (frameskipType == 3) {
switch (core->platform(core)) {
#ifdef M_CORE_GBA
case PLATFORM_GBA:
skipFrame = ((struct GBA*) core->board)->video.frameskipCounter > 0;
break;
#endif
#ifdef M_CORE_GB
case PLATFORM_GB:
skipFrame = ((struct GB*) core->board)->video.frameskipCounter > 0;
break;
#endif
default:
break;
}
}
if (!skipFrame) {
#if defined(COLOR_16_BIT) && defined(COLOR_5_6_5)
if (videoPostProcess) {
videoPostProcess(width, height);
videoCallback(ppOutputBuffer, width, height, VIDEO_WIDTH_MAX * sizeof(color_t));
} else
if (videoPostProcess) {
videoPostProcess(width, height);
videoCallback(ppOutputBuffer, width, height, VIDEO_WIDTH_MAX * sizeof(color_t));
} else
#endif
videoCallback(outputBuffer, width, height, VIDEO_WIDTH_MAX * sizeof(color_t));
videoCallback(outputBuffer, width, height, VIDEO_WIDTH_MAX * sizeof(color_t));
} else {
videoCallback(NULL, width, height, VIDEO_WIDTH_MAX * sizeof(color_t));
}
// This was from aliaspider patch (4539a0e), game boy audio is buggy with it (adapted for this refactored core)
/*

View File

@ -1335,6 +1335,45 @@ enum retro_mod
* should be considered active.
*/
#define RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK 62
/* const struct retro_audio_buffer_status_callback * --
* Lets the core know the occupancy level of the frontend
* audio buffer. Can be used by a core to attempt frame
* skipping in order to avoid buffer under-runs.
* A core may pass NULL to disable buffer status reporting
* in the frontend.
*/
#define RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY 63
/* const unsigned * --
* Sets minimum frontend audio latency in milliseconds.
* Resultant audio latency may be larger than set value,
* or smaller if a hardware limit is encountered. A frontend
* is expected to honour requests up to 512 ms.
*
* - If value is less than current frontend
* audio latency, callback has no effect
* - If value is zero, default frontend audio
* latency is set
*
* May be used by a core to increase audio latency and
* therefore decrease the probability of buffer under-runs
* (crackling) when performing 'intensive' operations.
* A core utilising RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK
* to implement audio-buffer-based frame skipping may achieve
* optimal results by setting the audio latency to a 'high'
* (typically 6x or 8x) integer multiple of the expected
* frame time.
*
* WARNING: This can only be called from within retro_run().
* Calling this can require a full reinitialization of audio
* drivers in the frontend, so it is important to call it very
* sparingly, and usually only with the users explicit consent.
* An eventual driver reinitialize will happen so that audio
* callbacks happening after this call within the same retro_run()
* call will target the newly initialized driver.
*/
/* VFS functionality */
/* File paths:
@ -2224,6 +2263,30 @@ struct retro_frame_time_callback
retro_usec_t reference;
};
/* Notifies a libretro core of the current occupancy
* level of the frontend audio buffer.
*
* - active: 'true' if audio buffer is currently
* in use. Will be 'false' if audio is
* disabled in the frontend
*
* - occupancy: Given as a value in the range [0,100],
* corresponding to the occupancy percentage
* of the audio buffer
*
* - underrun_likely: 'true' if the frontend expects an
* audio buffer underrun during the
* next frame (indicates that a core
* should attempt frame skipping)
*
* It will be called right before retro_run() every frame. */
typedef void (RETRO_CALLCONV *retro_audio_buffer_status_callback_t)(
bool active, unsigned occupancy, bool underrun_likely);
struct retro_audio_buffer_status_callback
{
retro_audio_buffer_status_callback_t callback;
};
/* Pass this to retro_video_refresh_t if rendering to hardware.
* Passing NULL to retro_video_refresh_t is still a frame dupe as normal.
* */

View File

@ -143,7 +143,45 @@ struct retro_core_option_definition option_defs_us[] = {
{
"mgba_frameskip",
"Frameskip",
"Skip frames to improve performance at the expense of visual smoothness. Value set here is the number of frames omitted after a frame is rendered - i.e. '0' = 60fps, '1' = 30fps, '2' = 15fps, etc.",
"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. 'Auto (Threshold)' utilises the 'Frameskip Threshold (%)' setting. 'Fixed Interval' utilises the 'Frameskip Interval' setting.",
{
{ "disabled", NULL },
{ "auto", "Auto" },
{ "auto_threshold", "Auto (Threshold)" },
{ "fixed_interval", "Fixed Interval" },
{ NULL, NULL },
},
"disabled"
},
{
"mgba_frameskip_threshold",
"Frameskip Threshold (%)",
"When 'Frameskip' is set to 'Auto (Threshold)', 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.",
{
{ "15", NULL },
{ "18", NULL },
{ "21", NULL },
{ "24", NULL },
{ "27", NULL },
{ "30", NULL },
{ "33", NULL },
{ "36", NULL },
{ "39", NULL },
{ "42", NULL },
{ "45", NULL },
{ "48", NULL },
{ "51", NULL },
{ "54", NULL },
{ "57", NULL },
{ "60", NULL },
{ NULL, NULL },
},
"33"
},
{
"mgba_frameskip_interval",
"Frameskip Interval",
"When 'Frameskip' is set to 'Fixed Interval', the value set here is the number of frames omitted after a frame is rendered - i.e. '0' = 60fps, '1' = 30fps, '2' = 15fps, etc.",
{
{ "0", NULL },
{ "1", NULL },

View File

@ -138,26 +138,6 @@ struct retro_core_option_definition option_defs_it[] = {
},
"Remove Known"
},
{
"mgba_frameskip",
"Salta Frame",
"Salta dei frame per migliorare le prestazioni a costo della fluidità dell'immagine. Il valore impostato qui è il numero dei frame rimosso dopo che un frame sia stato renderizzato - ovvero '0' = 60fps, '1' = 30fps, '2' = 15fps, ecc.",
{
{ "0", NULL },
{ "1", NULL },
{ "2", NULL },
{ "3", NULL },
{ "4", NULL },
{ "5", NULL },
{ "6", NULL },
{ "7", NULL },
{ "8", NULL },
{ "9", NULL },
{ "10", NULL },
{ NULL, NULL },
},
"0"
},
#if defined(COLOR_16_BIT) && defined(COLOR_5_6_5)
{
"mgba_color_correction",
@ -294,26 +274,6 @@ struct retro_core_option_definition option_defs_tr[] = {
},
"Remove Known"
},
{
"mgba_frameskip",
"Kare atlama",
"Görsel pürüzsüzlük pahasına performansı artırmak için çerçeveleri atlayın. Burada ayarlanan değer, bir kare oluşturulduktan sonra atlanan kare sayısıdır - yani '0' = 60fps, '1' = 30fps, '2' = 15fps, vb.",
{
{ "0", NULL },
{ "1", NULL },
{ "2", NULL },
{ "3", NULL },
{ "4", NULL },
{ "5", NULL },
{ "6", NULL },
{ "7", NULL },
{ "8", NULL },
{ "9", NULL },
{ "10", NULL },
{ NULL, NULL },
},
"0"
},
#if defined(COLOR_16_BIT) && defined(COLOR_5_6_5)
{
"mgba_color_correction",