From 226bd8283a2b52957afeab63246b770e6d6b078e Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Sat, 24 Oct 2020 14:58:40 +0100 Subject: Overhaul frameskip options --- libretro.c | 281 +++++++++++++++++++++++++++++++++--------------- libretro.h | 63 +++++++++++ libretro_core_options.h | 69 +++++++----- main.h | 8 +- 4 files changed, 306 insertions(+), 115 deletions(-) diff --git a/libretro.c b/libretro.c index 51c40e7..bd930f8 100644 --- a/libretro.c +++ b/libretro.c @@ -53,19 +53,21 @@ static int translation_caches_inited = 0; // 59.72750057 hz #define GBA_FPS ((float) GBC_BASE_RATE) / (308 * 228 * 4) -#define MAX_FRAME_TIME 1.0f / ((float) GBA_FPS) -frameskip_type current_frameskip_type = no_frameskip; -u32 frameskip_value = 4; -u32 random_skip = 0; - -u32 skip_next_frame = 0; - -u32 frameskip_counter = 0; - -u32 num_skipped_frames = 0; - -static float frame_time; +/* Maximum number of consecutive frames that + * can be skipped */ +#define FRAMESKIP_MAX 30 + +u32 skip_next_frame = 0; +static frameskip_type current_frameskip_type = no_frameskip; +static u32 frameskip_threshold = 0; +static u32 frameskip_interval = 0; +static u32 frameskip_counter = 0; +static bool audio_buff_active = false; +static unsigned audio_buff_occupancy = 0; +static bool audio_buff_underrun = false; +static unsigned audio_latency = 0; +static bool update_audio_latency = false; static retro_log_printf_t log_cb; static retro_video_refresh_t video_cb; @@ -159,6 +161,80 @@ static uint32_t next_pow2(uint32_t v) } #endif +static void error_msg(const char* text) +{ + if (log_cb) + log_cb(RETRO_LOG_ERROR, "[gpSP]: %s\n", text); +} + +static void info_msg(const char* text) +{ + if (log_cb) + log_cb(RETRO_LOG_INFO, "[gpSP]: %s\n", text); +} + +/* Frameskip START */ + +static void audio_buff_status_cb( + bool active, unsigned occupancy, bool underrun_likely) +{ + audio_buff_active = active; + audio_buff_occupancy = occupancy; + audio_buff_underrun = underrun_likely; +} + +static void init_frameskip(void) +{ + if (current_frameskip_type == no_frameskip) + { + environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, NULL); + audio_latency = 0; + } + else + { + bool calculate_audio_latency = true; + + if (current_frameskip_type == fixed_interval_frameskip) + environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, NULL); + else + { + struct retro_audio_buffer_status_callback buff_status_cb; + buff_status_cb.callback = audio_buff_status_cb; + + if (!environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, &buff_status_cb)) + { + error_msg("Frameskip disabled - frontend does not support audio buffer status monitoring"); + + audio_buff_active = false; + audio_buff_occupancy = 0; + audio_buff_underrun = false; + audio_latency = 0; + calculate_audio_latency = false; + } + } + + if (calculate_audio_latency) + { + /* Frameskip is enabled - increase frontend + * audio latency to minimise potential + * buffer underruns */ + float frame_time_msec = 1000.0f / ((float) GBA_FPS); + + /* Set latency to 6x current frame time... */ + audio_latency = (unsigned)((6.0f * frame_time_msec) + 0.5f); + + /* ...then round up to nearest multiple of 32 */ + audio_latency = (audio_latency + 0x1F) & ~0x1F; + } + + } + + update_audio_latency = true; + frameskip_counter = 0; +} + +/* Frameskip END */ + /* Video post processing START */ /* Note: This code is intentionally W.E.T. @@ -322,9 +398,17 @@ static void init_post_processing(void) /* Video post processing END */ -static void video_run(void) { - +static void video_run(void) +{ u16 *gba_screen_pixels_buf = gba_screen_pixels; + + if (skip_next_frame) + { + video_cb(NULL, GBA_SCREEN_WIDTH, GBA_SCREEN_HEIGHT, + GBA_SCREEN_PITCH * 2); + return; + } + if (video_post_process) { video_post_process(); @@ -474,6 +558,15 @@ void retro_init(void) gba_screen_pixels = (uint16_t*)malloc(GBA_SCREEN_PITCH * GBA_SCREEN_HEIGHT * sizeof(uint16_t)); #endif + current_frameskip_type = no_frameskip; + frameskip_threshold = 0; + frameskip_interval = 0; + frameskip_counter = 0; + audio_buff_active = false; + audio_buff_occupancy = 0; + audio_buff_underrun = false; + audio_latency = 0; + update_audio_latency = false; } void retro_deinit(void) @@ -616,18 +709,6 @@ void retro_cheat_reset(void) } void retro_cheat_set(unsigned index, bool enabled, const char* code) {} -void error_msg(const char* text) -{ - if (log_cb) - log_cb(RETRO_LOG_ERROR, "[gpSP]: %s\n", text); -} - -void info_msg(const char* text) -{ - if (log_cb) - log_cb(RETRO_LOG_INFO, "[gpSP]: %s\n", text); -} - static void extract_directory(char* buf, const char* path, size_t size) { strncpy(buf, path, size - 1); @@ -644,6 +725,7 @@ static void extract_directory(char* buf, const char* path, size_t size) static void check_variables(int started_from_load) { struct retro_variable var; + bool frameskip_type_prev; bool post_process_cc_prev; bool post_process_mix_prev; @@ -665,32 +747,40 @@ static void check_variables(int started_from_load) dynarec_enable = 1; #endif - var.key = "gpsp_frameskip_value"; - var.value = NULL; - if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - frameskip_value = strtol(var.value, NULL, 10); + var.key = "gpsp_frameskip"; + var.value = 0; + frameskip_type_prev = current_frameskip_type; + current_frameskip_type = no_frameskip; - var.key = "gpsp_frameskip_type"; - var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { - if (!strcmp(var.value, "off")) - current_frameskip_type = no_frameskip; - else if (!strcmp(var.value, "automatic")) + if (!strcmp(var.value, "auto")) current_frameskip_type = auto_frameskip; - else if (!strcmp(var.value, "manual")) - current_frameskip_type = manual_frameskip; + else if (!strcmp(var.value, "auto_threshold")) + current_frameskip_type = auto_threshold_frameskip; + else if (!strcmp(var.value, "fixed_interval")) + current_frameskip_type = fixed_interval_frameskip; } - var.key = "gpsp_frameskip_variation"; - var.value = NULL; + var.key = "gpsp_frameskip_threshold"; + var.value = 0; + frameskip_threshold = 33; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { - if (!strcmp(var.value, "uniform")) - random_skip = 0; - else if (!strcmp(var.value, "random")) - random_skip = 1; - } + frameskip_threshold = strtol(var.value, NULL, 10); + + var.key = "gpsp_frameskip_interval"; + var.value = 0; + + frameskip_interval = 0; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + frameskip_interval = strtol(var.value, NULL, 10); + + /* (Re)Initialise frame skipping, if required */ + if (started_from_load || + (current_frameskip_type != frameskip_type_prev)) + init_frameskip(); var.key = "gpsp_color_correction"; var.value = NULL; @@ -753,11 +843,6 @@ static void set_input_descriptors() environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descriptors); } -static void frame_time_cb(retro_usec_t usec) -{ - frame_time = usec / 1000000.0; -} - static void set_memory_descriptors(void) { const uint64_t mem = RETRO_MEMORY_SYSTEM_RAM; @@ -821,9 +906,6 @@ bool retro_load_game(const struct retro_game_info* info) dynarec_enable = 0; #endif - struct retro_frame_time_callback frame_cb = { frame_time_cb, 1000000 / ((float) GBA_FPS) }; - environ_cb(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &frame_cb); - char filename_bios[MAX_PATH]; const char* dir = NULL; @@ -976,52 +1058,83 @@ void retro_run(void) input_poll_cb(); - s32 used_frameskip = frameskip_value; - + /* Check whether current frame should + * be skipped */ skip_next_frame = 0; - if(current_frameskip_type == auto_frameskip) + if (current_frameskip_type != no_frameskip) { - if(frame_time > MAX_FRAME_TIME) + switch (current_frameskip_type) { - if(num_skipped_frames < frameskip_value) - { - skip_next_frame = 1; - num_skipped_frames++; - } - else - { - num_skipped_frames = 0; - } + case auto_frameskip: + + skip_next_frame = + (audio_buff_active && audio_buff_underrun) ? + 1 : 0; + + if (!skip_next_frame || + (frameskip_counter >= FRAMESKIP_MAX)) + { + skip_next_frame = 0; + frameskip_counter = 0; + } + else + frameskip_counter++; + + break; + case auto_threshold_frameskip: + + skip_next_frame = + (audio_buff_active && + (audio_buff_occupancy < frameskip_threshold)) ? + 1 : 0; + + if (!skip_next_frame || + (frameskip_counter >= FRAMESKIP_MAX)) + { + skip_next_frame = 0; + frameskip_counter = 0; + } + else + frameskip_counter++; + + break; + case fixed_interval_frameskip: + + if (frameskip_counter < frameskip_interval) + { + skip_next_frame = 1; + frameskip_counter++; + } + else + { + skip_next_frame = 0; + frameskip_counter = 0; + } + + break; + default: + skip_next_frame = 0; + break; } } - else if(current_frameskip_type == manual_frameskip) + + /* If frameskip settings have changed, update + * frontend audio latency */ + if (update_audio_latency) { - frameskip_counter = (frameskip_counter + 1) % - (used_frameskip + 1); - if(random_skip) - { - if(frameskip_counter != (rand() % (used_frameskip + 1))) - skip_next_frame = 1; - } - else - { - if(frameskip_counter) - skip_next_frame = 1; - } + environ_cb(RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY, + &audio_latency); + update_audio_latency = false; } switch_to_cpu_thread(); render_audio(); - - /* Skip the video callback when skipping frames so the frontend can properly report FPS */ - if (!skip_next_frame) - video_run(); + video_run(); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(0); - } unsigned retro_api_version(void) diff --git a/libretro.h b/libretro.h index e03c5e4..59bd513 100644 --- a/libretro.h +++ b/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/libretro_core_options.h b/libretro_core_options.h index e9cfba3..7e68022 100644 --- a/libretro_core_options.h +++ b/libretro_core_options.h @@ -54,45 +54,62 @@ extern "C" { struct retro_core_option_definition option_defs_us[] = { { - "gpsp_frameskip_type", - "Frameskip Type", - "Skip frames to improve performance at the expense of visual smoothness. 'Manual' skips frames at an interval set by 'Frameskip Value' and 'Frameskip Variation'. 'Automatic' attempts to adjust frame skipping based on CPU load.", + "gpsp_frameskip", + "Frameskip", + "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.", { - { "off", "disabled" }, - { "manual", "Manual" }, - { "automatic", "Automatic" }, + { "disabled", NULL }, + { "auto", "Auto" }, + { "auto_threshold", "Auto (Threshold)" }, + { "fixed_interval", "Fixed Interval" }, { NULL, NULL }, }, - "off" + "disabled" }, { - "gpsp_frameskip_value", - "Frameskip Value", - "When 'Frameskip Type' is 'Manual', the value set here is the number of frames omitted after a frame is rendered - i.e. '1' = 30fps, '2' = 15fps, etc.", + "gpsp_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.", { - { "1", NULL }, - { "2", NULL }, - { "3", NULL }, - { "4", NULL }, - { "5", NULL }, - { "6", NULL }, - { "7", NULL }, - { "8", NULL }, - { "9", NULL }, + { "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 }, }, - "1" + "33" }, { - "gpsp_frameskip_variation", - "Frameskip Variation", - "When 'Frameskip Type' is 'Manual', specifies whether frame skipping should occur at regular ('Uniform') intervals, or with an element of 'Random' variation (may help reduce the visual impact of stuttering).", + "gpsp_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.", { - { "uniform", "Uniform" }, - { "random", "Random" }, + { "0", NULL }, + { "1", NULL }, + { "2", NULL }, + { "3", NULL }, + { "4", NULL }, + { "5", NULL }, + { "6", NULL }, + { "7", NULL }, + { "8", NULL }, + { "9", NULL }, + { "10", NULL }, { NULL, NULL }, }, - "uniform" + "1" }, { "gpsp_color_correction", diff --git a/main.h b/main.h index 9e57d51..e0aae95 100644 --- a/main.h +++ b/main.h @@ -58,16 +58,14 @@ typedef struct typedef enum { + no_frameskip = 0, auto_frameskip, - manual_frameskip, - no_frameskip + auto_threshold_frameskip, + fixed_interval_frameskip } frameskip_type; extern u32 cpu_ticks; extern u32 execute_cycles; -extern frameskip_type current_frameskip_type; -extern u32 frameskip_value; -extern u32 random_skip; extern u32 global_cycles_per_instruction; extern u32 skip_next_frame; -- cgit v1.2.3