From 942d58bf0763caa98b063a6afca116d458122115 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Wed, 21 Oct 2020 17:38:15 +0100 Subject: [PATCH] Add optional automatic frame skipping based on frontend audio buffer level --- Makefile.libretro | 16 ++ src/platform/libretro/libretro.c | 235 ++++++++++++++++-- src/platform/libretro/libretro.h | 63 +++++ src/platform/libretro/libretro_core_options.h | 40 ++- .../libretro/libretro_core_options_intl.h | 40 --- 5 files changed, 337 insertions(+), 57 deletions(-) diff --git a/Makefile.libretro b/Makefile.libretro index b069ccb72..fd5cf1a1a 100644 --- a/Makefile.libretro +++ b/Makefile.libretro @@ -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 diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 7d61895a6..4c20fc3f3 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -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) /* diff --git a/src/platform/libretro/libretro.h b/src/platform/libretro/libretro.h index e03c5e40c..59bd51378 100644 --- a/src/platform/libretro/libretro.h +++ b/src/platform/libretro/libretro.h @@ -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. * */ diff --git a/src/platform/libretro/libretro_core_options.h b/src/platform/libretro/libretro_core_options.h index ab0dda5b9..76ee1e5c1 100644 --- a/src/platform/libretro/libretro_core_options.h +++ b/src/platform/libretro/libretro_core_options.h @@ -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 }, diff --git a/src/platform/libretro/libretro_core_options_intl.h b/src/platform/libretro/libretro_core_options_intl.h index c2d23f242..24b6ebffc 100644 --- a/src/platform/libretro/libretro_core_options_intl.h +++ b/src/platform/libretro/libretro_core_options_intl.h @@ -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",