(libretro) Add option to detect and notifiy frontend of internal frame rate changes (60 <-> 30 <-> 20 fps, etc.)
Improves frame pacing in games with locked 30 fps and 20 fps frame rates
This commit is contained in:
parent
903c768f7f
commit
0e23b0bedd
|
@ -24,8 +24,33 @@
|
|||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
/* Detect output refresh rate changes by monitoring
|
||||
* the last 'VSYNC_SWAP_INTERVAL_FRAMES' frames:
|
||||
* - Measure average (mean) audio samples per upload
|
||||
* operation
|
||||
* - Determine vsync swap interval based on
|
||||
* expected samples at 60 (or 50) Hz
|
||||
* - Check that vsync swap interval remains
|
||||
* 'stable' for at least 'VSYNC_SWAP_INTERVAL_FRAMES' */
|
||||
#define VSYNC_SWAP_INTERVAL_FRAMES 6
|
||||
/* Calculated swap interval is 'valid' if it is
|
||||
* within 'VSYNC_SWAP_INTERVAL_THRESHOLD' of an integer
|
||||
* value */
|
||||
#define VSYNC_SWAP_INTERVAL_THRESHOLD 0.05f
|
||||
|
||||
extern void setAVInfo(retro_system_av_info& avinfo);
|
||||
|
||||
extern retro_environment_t environ_cb;
|
||||
extern retro_audio_sample_batch_t audio_batch_cb;
|
||||
|
||||
extern float libretro_expected_audio_samples_per_run;
|
||||
extern unsigned libretro_vsync_swap_interval;
|
||||
extern bool libretro_detect_vsync_swap_interval;
|
||||
|
||||
static float audio_samples_per_frame_avg;
|
||||
static unsigned vsync_swap_interval_last;
|
||||
static unsigned vsync_swap_interval_conter;
|
||||
|
||||
static std::mutex audio_buffer_mutex;
|
||||
static std::vector<int16_t> audio_buffer;
|
||||
static size_t audio_buffer_idx;
|
||||
|
@ -57,6 +82,10 @@ void retro_audio_init(void)
|
|||
audio_out_buffer = (int16_t*)malloc(audio_buffer_size * sizeof(int16_t));
|
||||
|
||||
drop_samples = false;
|
||||
|
||||
audio_samples_per_frame_avg = 0.0f;
|
||||
vsync_swap_interval_last = 1;
|
||||
vsync_swap_interval_conter = 0;
|
||||
}
|
||||
|
||||
void retro_audio_deinit(void)
|
||||
|
@ -72,6 +101,10 @@ void retro_audio_deinit(void)
|
|||
audio_out_buffer = nullptr;
|
||||
|
||||
drop_samples = true;
|
||||
|
||||
audio_samples_per_frame_avg = 0.0f;
|
||||
vsync_swap_interval_last = 1;
|
||||
vsync_swap_interval_conter = 0;
|
||||
}
|
||||
|
||||
void retro_audio_flush_buffer(void)
|
||||
|
@ -100,6 +133,65 @@ void retro_audio_upload(void)
|
|||
|
||||
audio_buffer_mutex.unlock();
|
||||
|
||||
/* Attempt to detect changes in output refresh rate */
|
||||
if (libretro_detect_vsync_swap_interval &&
|
||||
(num_frames > 0))
|
||||
{
|
||||
/* Simple running average (leaky-integrator) */
|
||||
audio_samples_per_frame_avg = ((1.0f / (float)VSYNC_SWAP_INTERVAL_FRAMES) * (float)num_frames) +
|
||||
((1.0f - (1.0f / (float)VSYNC_SWAP_INTERVAL_FRAMES)) * audio_samples_per_frame_avg);
|
||||
|
||||
float swap_ratio = audio_samples_per_frame_avg /
|
||||
libretro_expected_audio_samples_per_run;
|
||||
unsigned swap_integer;
|
||||
float swap_remainder;
|
||||
|
||||
/* If internal frame rate is equal to (within threshold)
|
||||
* or higher than the default 60 (or 50) Hz, fall back
|
||||
* to a swap interval of 1 */
|
||||
if (swap_ratio < (1.0f + VSYNC_SWAP_INTERVAL_THRESHOLD))
|
||||
{
|
||||
swap_integer = 1;
|
||||
swap_remainder = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
swap_integer = (unsigned)(swap_ratio + 0.5f);
|
||||
swap_remainder = swap_ratio - (float)swap_integer;
|
||||
swap_remainder = (swap_remainder < 0.0f) ?
|
||||
-swap_remainder : swap_remainder;
|
||||
}
|
||||
|
||||
/* > Swap interval is considered 'valid' if it is
|
||||
* within VSYNC_SWAP_INTERVAL_THRESHOLD of an integer
|
||||
* value
|
||||
* > If valid, check if new swap interval differs from
|
||||
* previously logged value */
|
||||
if ((swap_remainder <= VSYNC_SWAP_INTERVAL_THRESHOLD) &&
|
||||
(swap_integer != libretro_vsync_swap_interval))
|
||||
{
|
||||
vsync_swap_interval_conter =
|
||||
(swap_integer == vsync_swap_interval_last) ?
|
||||
(vsync_swap_interval_conter + 1) : 0;
|
||||
|
||||
/* Check whether swap interval is 'stable' */
|
||||
if (vsync_swap_interval_conter >= VSYNC_SWAP_INTERVAL_FRAMES)
|
||||
{
|
||||
libretro_vsync_swap_interval = swap_integer;
|
||||
vsync_swap_interval_conter = 0;
|
||||
|
||||
/* Notify frontend */
|
||||
retro_system_av_info avinfo;
|
||||
setAVInfo(avinfo);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avinfo);
|
||||
}
|
||||
|
||||
vsync_swap_interval_last = swap_integer;
|
||||
}
|
||||
else
|
||||
vsync_swap_interval_conter = 0;
|
||||
}
|
||||
|
||||
int16_t *audio_out_buffer_ptr = audio_out_buffer;
|
||||
while (num_frames > 0)
|
||||
{
|
||||
|
|
|
@ -120,6 +120,7 @@ static bool platformIsDreamcast = true;
|
|||
static bool platformIsArcade = false;
|
||||
static bool threadedRenderingEnabled = true;
|
||||
static bool oitEnabled = false;
|
||||
static bool autoSkipFrameEnabled = false;
|
||||
#ifndef TARGET_NO_OPENMP
|
||||
static bool textureUpscaleEnabled = false;
|
||||
#endif
|
||||
|
@ -164,6 +165,10 @@ static int framebufferHeight;
|
|||
static int maxFramebufferWidth;
|
||||
static int maxFramebufferHeight;
|
||||
|
||||
float libretro_expected_audio_samples_per_run;
|
||||
unsigned libretro_vsync_swap_interval = 1;
|
||||
bool libretro_detect_vsync_swap_interval = false;
|
||||
|
||||
static retro_perf_callback perf_cb;
|
||||
static retro_get_cpu_features_t perf_get_cpu_features_cb;
|
||||
|
||||
|
@ -172,8 +177,8 @@ static retro_log_printf_t log_cb;
|
|||
static retro_video_refresh_t video_cb;
|
||||
static retro_input_poll_t poll_cb;
|
||||
static retro_input_state_t input_cb;
|
||||
retro_audio_sample_batch_t audio_batch_cb;
|
||||
static retro_environment_t environ_cb;
|
||||
retro_audio_sample_batch_t audio_batch_cb;
|
||||
retro_environment_t environ_cb;
|
||||
|
||||
static retro_rumble_interface rumble;
|
||||
|
||||
|
@ -357,11 +362,14 @@ void retro_deinit()
|
|||
platformIsArcade = false;
|
||||
threadedRenderingEnabled = true;
|
||||
oitEnabled = false;
|
||||
autoSkipFrameEnabled = false;
|
||||
#ifndef TARGET_NO_OPENMP
|
||||
textureUpscaleEnabled = false;
|
||||
#endif
|
||||
vmuScreenSettingsShown = true;
|
||||
lightgunSettingsShown = true;
|
||||
libretro_vsync_swap_interval = 1;
|
||||
libretro_detect_vsync_swap_interval = false;
|
||||
LogManager::Shutdown();
|
||||
|
||||
retro_audio_deinit();
|
||||
|
@ -500,6 +508,24 @@ static bool set_variable_visibility(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
// Only if automatic frame skipping is disabled
|
||||
bool autoSkipFrameWasEnabled = autoSkipFrameEnabled;
|
||||
|
||||
autoSkipFrameEnabled = false;
|
||||
var.key = CORE_OPTION_NAME "_auto_skip_frame";
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && strcmp(var.value, "disabled"))
|
||||
autoSkipFrameEnabled = true;
|
||||
|
||||
if (first_run ||
|
||||
(autoSkipFrameEnabled != autoSkipFrameWasEnabled) ||
|
||||
(threadedRenderingEnabled != threadedRenderingWasEnabled))
|
||||
{
|
||||
option_display.visible = (!autoSkipFrameEnabled || !threadedRenderingEnabled);
|
||||
option_display.key = CORE_OPTION_NAME "_detect_vsync_swap_interval";
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
// If categories are supported, no further action is required
|
||||
if (categoriesSupported)
|
||||
return updated;
|
||||
|
@ -596,11 +622,16 @@ static void setGameGeometry(retro_game_geometry& geometry)
|
|||
geometry.base_height = 480;
|
||||
}
|
||||
|
||||
static void setAVInfo(retro_system_av_info& avinfo)
|
||||
void setAVInfo(retro_system_av_info& avinfo)
|
||||
{
|
||||
double sample_rate = 44100.0;
|
||||
double fps = SPG_CONTROL.NTSC ? 59.94 : SPG_CONTROL.PAL ? 50.0 : 60.0;
|
||||
|
||||
setGameGeometry(avinfo.geometry);
|
||||
avinfo.timing.sample_rate = 44100.0;
|
||||
avinfo.timing.fps = SPG_CONTROL.NTSC ? 59.94 : SPG_CONTROL.PAL ? 50.0 : 60.0;
|
||||
avinfo.timing.sample_rate = sample_rate;
|
||||
avinfo.timing.fps = fps / (double)libretro_vsync_swap_interval;
|
||||
|
||||
libretro_expected_audio_samples_per_run = sample_rate / fps;
|
||||
}
|
||||
|
||||
static void setRotation()
|
||||
|
@ -628,6 +659,7 @@ static void update_variables(bool first_startup)
|
|||
int prevMaxFramebufferHeight = maxFramebufferHeight;
|
||||
int prevMaxFramebufferWidth = maxFramebufferWidth;
|
||||
bool prevRotateScreen = rotate_screen;
|
||||
bool prevDetectVsyncSwapInterval = libretro_detect_vsync_swap_interval;
|
||||
config::Settings::instance().setRetroEnvironment(environ_cb);
|
||||
config::Settings::instance().setOptionDefinitions(option_defs_us);
|
||||
config::Settings::instance().load(false);
|
||||
|
@ -749,6 +781,22 @@ static void update_variables(bool first_startup)
|
|||
config::PixelBufferSize = 0x20000000u;
|
||||
#endif
|
||||
|
||||
if ((config::AutoSkipFrame != 0) && config::ThreadedRendering)
|
||||
libretro_detect_vsync_swap_interval = false;
|
||||
else
|
||||
{
|
||||
var.key = CORE_OPTION_NAME "_detect_vsync_swap_interval";
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
||||
{
|
||||
if (!strcmp(var.value, "enabled"))
|
||||
libretro_detect_vsync_swap_interval = true;
|
||||
else if (!strcmp(var.value, "disabled"))
|
||||
libretro_detect_vsync_swap_interval = false;
|
||||
}
|
||||
else
|
||||
libretro_detect_vsync_swap_interval = false;
|
||||
}
|
||||
|
||||
if (first_startup)
|
||||
{
|
||||
if (config::ThreadedRendering)
|
||||
|
@ -972,6 +1020,16 @@ static void update_variables(bool first_startup)
|
|||
if (rotate_game)
|
||||
config::Widescreen.override(false);
|
||||
setFramebufferSize();
|
||||
|
||||
bool avInfoChanged = false;
|
||||
if ((libretro_detect_vsync_swap_interval != prevDetectVsyncSwapInterval) &&
|
||||
!libretro_detect_vsync_swap_interval &&
|
||||
(libretro_vsync_swap_interval != 1))
|
||||
{
|
||||
libretro_vsync_swap_interval = 1;
|
||||
avInfoChanged = true;
|
||||
}
|
||||
|
||||
if ((prevMaxFramebufferWidth < maxFramebufferWidth || prevMaxFramebufferHeight < maxFramebufferHeight)
|
||||
// TODO crash with dx11
|
||||
&& config::RendererType != RenderType::DirectX11 && config::RendererType != RenderType::DirectX11_OIT)
|
||||
|
@ -980,6 +1038,7 @@ static void update_variables(bool first_startup)
|
|||
setAVInfo(avinfo);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avinfo);
|
||||
rend_resize_renderer();
|
||||
avInfoChanged = false;
|
||||
}
|
||||
else if (prevFramebufferWidth != framebufferWidth || prevFramebufferHeight != framebufferHeight || geometryChanged)
|
||||
{
|
||||
|
@ -988,6 +1047,13 @@ static void update_variables(bool first_startup)
|
|||
environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &geometry);
|
||||
rend_resize_renderer();
|
||||
}
|
||||
|
||||
if (avInfoChanged)
|
||||
{
|
||||
retro_system_av_info avinfo;
|
||||
setAVInfo(avinfo);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avinfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -473,6 +473,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
|
|||
},
|
||||
"disabled",
|
||||
},
|
||||
{
|
||||
CORE_OPTION_NAME "_detect_vsync_swap_interval",
|
||||
"Detect Frame Rate Changes",
|
||||
NULL,
|
||||
"Notify frontend when internal frame rate changes (e.g. from 60 fps to 30 fps). Improves frame pacing in games that run at a locked 30 fps or 20 fps, but should be disabled for games with unlocked (unstable) frame rates (e.g. Ecco the Dolphin, Unreal Tournament). Note: Unavailable when 'Auto Skip Frame' is enabled.",
|
||||
NULL,
|
||||
"video",
|
||||
{
|
||||
{ "disabled", NULL },
|
||||
{ "enabled", NULL },
|
||||
{ NULL, NULL },
|
||||
},
|
||||
"disabled",
|
||||
},
|
||||
{
|
||||
CORE_OPTION_NAME "_pvr2_filtering",
|
||||
"PowerVR2 Post-processing Filter",
|
||||
|
|
Loading…
Reference in New Issue