mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
(libretro) Add option to detect and notifiy frontend of internal frame rate changes (60 <-> 30 <-> 20 fps, etc.) + restore 'Duplicate Frames in 30 Hz Games' core option
This commit is contained in:
parent
83f3539f32
commit
75bd828a5d
1 changed files with 224 additions and 17 deletions
|
@ -34,6 +34,8 @@
|
|||
#include "Core/Host.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/Display.h"
|
||||
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/GPUInterface.h"
|
||||
|
@ -64,6 +66,20 @@
|
|||
// frames, or 3 seconds of runtime...
|
||||
#define AUDIO_FRAMES_MOVING_AVG_ALPHA (1.0f / 180.0f)
|
||||
|
||||
// Calculated swap interval is 'stable' if the same
|
||||
// value is recorded for a number of retro_run()
|
||||
// calls equal to 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
|
||||
// Swap interval detection is only enabled if the
|
||||
// core is running at 'normal' speed - i.e. if
|
||||
// run speed is within VSYNC_SWAP_INTERVAL_RUN_SPEED_THRESHOLD
|
||||
// percent of 100
|
||||
#define VSYNC_SWAP_INTERVAL_RUN_SPEED_THRESHOLD 5.0f
|
||||
|
||||
static bool libretro_supports_bitmasks = false;
|
||||
static std::string changeProAdhocServer;
|
||||
|
||||
|
@ -76,6 +92,139 @@ namespace Libretro
|
|||
static retro_input_state_t input_state_cb;
|
||||
} // namespace Libretro
|
||||
|
||||
namespace Libretro
|
||||
{
|
||||
static bool detectVsyncSwapInterval = false;
|
||||
static bool detectVsyncSwapIntervalOptShown = true;
|
||||
|
||||
static s64 expectedTimeUsPerRun = 0;
|
||||
static uint32_t vsyncSwapInterval = 1;
|
||||
static uint32_t vsyncSwapIntervalLast = 1;
|
||||
static uint32_t vsyncSwapIntervalCounter = 0;
|
||||
static int numVBlanksLast = 0;
|
||||
static double fpsTimeLast = 0.0;
|
||||
static float runSpeed = 0.0f;
|
||||
static s64 runTicksLast = 0;
|
||||
|
||||
static void VsyncSwapIntervalReset()
|
||||
{
|
||||
expectedTimeUsPerRun = (s64)(1000000.0f / (60.0f / 1.001f));
|
||||
vsyncSwapInterval = 1;
|
||||
vsyncSwapIntervalLast = 1;
|
||||
vsyncSwapIntervalCounter = 0;
|
||||
|
||||
numVBlanksLast = 0;
|
||||
fpsTimeLast = 0.0;
|
||||
runSpeed = 0.0f;
|
||||
runTicksLast = 0;
|
||||
|
||||
detectVsyncSwapIntervalOptShown = true;
|
||||
}
|
||||
|
||||
static void VsyncSwapIntervalDetect()
|
||||
{
|
||||
if (!detectVsyncSwapInterval)
|
||||
return;
|
||||
|
||||
// All bets are off if core is running at
|
||||
// the 'wrong' speed (i.e. cycle count for
|
||||
// this run will be meaningless if internal
|
||||
// frame rate is dropping below expected
|
||||
// value, or fast forward is enabled)
|
||||
double fpsTime = time_now_d();
|
||||
int numVBlanks = __DisplayGetNumVblanks();
|
||||
int frames = numVBlanks - numVBlanksLast;
|
||||
|
||||
if (frames >= VSYNC_SWAP_INTERVAL_FRAMES << 1)
|
||||
{
|
||||
double fps = (double)frames / (fpsTime - fpsTimeLast);
|
||||
runSpeed = fps / ((60.0f / 1.001f) / 100.0f);
|
||||
|
||||
fpsTimeLast = fpsTime;
|
||||
numVBlanksLast = numVBlanks;
|
||||
}
|
||||
|
||||
float speedDelta = 100.0f - runSpeed;
|
||||
speedDelta = (speedDelta < 0.0f) ? -speedDelta : speedDelta;
|
||||
|
||||
// Speed is measured relative to a 60 Hz refresh
|
||||
// rate. If we are transitioning from a low internal
|
||||
// frame rate to a higher internal frame rate, then
|
||||
// 'full speed' may actually equate to
|
||||
// (100 / current_swap_interval)...
|
||||
if ((vsyncSwapInterval > 1) &&
|
||||
(speedDelta >= VSYNC_SWAP_INTERVAL_RUN_SPEED_THRESHOLD))
|
||||
{
|
||||
speedDelta = 100.0f - (runSpeed * (float)vsyncSwapInterval);
|
||||
speedDelta = (speedDelta < 0.0f) ? -speedDelta : speedDelta;
|
||||
}
|
||||
|
||||
if (speedDelta >= VSYNC_SWAP_INTERVAL_RUN_SPEED_THRESHOLD)
|
||||
{
|
||||
// Swap interval detection is invalid - bail out
|
||||
vsyncSwapIntervalCounter = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get elapsed time (us) for this run
|
||||
s64 runTicks = CoreTiming::GetTicks();
|
||||
s64 runTimeUs = cyclesToUs(runTicks - runTicksLast);
|
||||
|
||||
// Check if current internal frame rate is a
|
||||
// factor of the default ~60 Hz
|
||||
float swapRatio = (float)runTimeUs / (float)expectedTimeUsPerRun;
|
||||
uint32_t swapInteger;
|
||||
float swapRemainder;
|
||||
|
||||
// If internal frame rate is equal to (within threshold)
|
||||
// or higher than the default ~60 Hz, fall back to a
|
||||
// swap interval of 1
|
||||
if (swapRatio < (1.0f + VSYNC_SWAP_INTERVAL_THRESHOLD))
|
||||
{
|
||||
swapInteger = 1;
|
||||
swapRemainder = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
swapInteger = (uint32_t)(swapRatio + 0.5f);
|
||||
swapRemainder = swapRatio - (float)swapInteger;
|
||||
swapRemainder = (swapRemainder < 0.0f) ?
|
||||
-swapRemainder : swapRemainder;
|
||||
}
|
||||
|
||||
// > 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 ((swapRemainder <= VSYNC_SWAP_INTERVAL_THRESHOLD) &&
|
||||
(swapInteger != vsyncSwapInterval))
|
||||
{
|
||||
vsyncSwapIntervalCounter =
|
||||
(swapInteger == vsyncSwapIntervalLast) ?
|
||||
(vsyncSwapIntervalCounter + 1) : 0;
|
||||
|
||||
// Check whether swap interval is 'stable'
|
||||
if (vsyncSwapIntervalCounter >= VSYNC_SWAP_INTERVAL_FRAMES)
|
||||
{
|
||||
vsyncSwapInterval = swapInteger;
|
||||
vsyncSwapIntervalCounter = 0;
|
||||
|
||||
// Notify frontend
|
||||
retro_system_av_info avInfo;
|
||||
retro_get_system_av_info(&avInfo);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avInfo);
|
||||
}
|
||||
|
||||
vsyncSwapIntervalLast = swapInteger;
|
||||
}
|
||||
else
|
||||
vsyncSwapIntervalCounter = 0;
|
||||
|
||||
runTicksLast = runTicks;
|
||||
}
|
||||
} // namespace Libretro
|
||||
|
||||
namespace Libretro
|
||||
{
|
||||
static std::mutex audioSampleLock_;
|
||||
|
@ -168,14 +317,15 @@ namespace Libretro
|
|||
|
||||
static void AudioUploadSamples()
|
||||
{
|
||||
// - The core specifies a fixed frame rate of (60.0f / 1.001f)
|
||||
// and a fixed sample rate of 44100
|
||||
// - This means the frontend expects exactly 735.735
|
||||
// sample frames per call of retro_run()
|
||||
// - Provided that g_Config.bRenderDuplicateFrames is
|
||||
// force enabled and frameskip is disabled, the mean
|
||||
// of the buffer occupancy will approximate to this
|
||||
// value in most cases
|
||||
|
||||
// - If 'Detect Frame Rate Changes' is disabled, then
|
||||
// the core specifies a fixed frame rate of (60.0f / 1.001f)
|
||||
// - At the audio sample rate of 44100, this means the
|
||||
// frontend expects exactly 735.735 sample frames per call of
|
||||
// retro_run()
|
||||
// - If g_Config.bRenderDuplicateFrames is enabled and
|
||||
// frameskip is disabled, the mean of the buffer occupancy
|
||||
// willapproximate to this value in most cases
|
||||
uint32_t framesAvailable = AudioBufferOccupancy();
|
||||
|
||||
if (framesAvailable > 0)
|
||||
|
@ -405,6 +555,8 @@ static RetroOption<bool> ppsspp_gpu_hardware_transform("ppsspp_gpu_hardware_tran
|
|||
static RetroOption<bool> ppsspp_vertex_cache("ppsspp_vertex_cache", "Vertex Cache (Speedhack)", false);
|
||||
static RetroOption<bool> ppsspp_cheats("ppsspp_cheats", "Internal Cheats Support", false);
|
||||
static RetroOption<IOTimingMethods> ppsspp_io_timing_method("ppsspp_io_timing_method", "IO Timing Method", { { "Fast", IOTimingMethods::IOTIMING_FAST }, { "Host", IOTimingMethods::IOTIMING_HOST }, { "Simulate UMD delays", IOTimingMethods::IOTIMING_REALISTIC } });
|
||||
static RetroOption<bool> ppsspp_frame_duplication("ppsspp_frame_duplication", "Duplicate Frames in 30 Hz Games", false);
|
||||
static RetroOption<bool> ppsspp_detect_vsync_swap_interval("ppsspp_detect_vsync_swap_interval", "Detect Frame Rate Changes (Notify Frontend)", false);
|
||||
static RetroOption<bool> ppsspp_software_skinning("ppsspp_software_skinning", "Software Skinning", true);
|
||||
static RetroOption<bool> ppsspp_ignore_bad_memory_access("ppsspp_ignore_bad_memory_access", "Ignore bad memory accesses", true);
|
||||
static RetroOption<bool> ppsspp_lazy_texture_caching("ppsspp_lazy_texture_caching", "Lazy texture caching (Speedup)", false);
|
||||
|
@ -490,7 +642,27 @@ static bool set_variable_visibility(void)
|
|||
if (ppsspp_enable_upnp.Update(&g_Config.bEnableUPnP))
|
||||
updated = true;
|
||||
|
||||
ppsspp_upnp_use_original_port.Show(g_Config.bEnableUPnP);
|
||||
ppsspp_upnp_use_original_port.Show(g_Config.bEnableUPnP);
|
||||
|
||||
bool detectVsyncSwapIntervalOptShownLast = detectVsyncSwapIntervalOptShown;
|
||||
bool autoFrameSkip = false;
|
||||
int frameSkip = 0;
|
||||
bool renderDuplicateFrames = false;
|
||||
|
||||
ppsspp_auto_frameskip.Update(&autoFrameSkip);
|
||||
ppsspp_frameskip.Update(&frameSkip);
|
||||
ppsspp_frame_duplication.Update(&renderDuplicateFrames);
|
||||
|
||||
detectVsyncSwapIntervalOptShown =
|
||||
!autoFrameSkip &&
|
||||
(frameSkip == 0) &&
|
||||
!renderDuplicateFrames;
|
||||
|
||||
if (detectVsyncSwapIntervalOptShown != detectVsyncSwapIntervalOptShownLast)
|
||||
{
|
||||
ppsspp_detect_vsync_swap_interval.Show(detectVsyncSwapIntervalOptShown);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
@ -501,7 +673,7 @@ void retro_set_environment(retro_environment_t cb)
|
|||
|
||||
struct retro_core_options_update_display_callback update_display_cb;
|
||||
update_display_cb.callback = set_variable_visibility;
|
||||
cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK, &update_display_cb);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK, &update_display_cb);
|
||||
|
||||
std::vector<retro_variable> vars;
|
||||
vars.push_back(ppsspp_internal_resolution.GetOptions());
|
||||
|
@ -516,6 +688,8 @@ void retro_set_environment(retro_environment_t cb)
|
|||
vars.push_back(ppsspp_auto_frameskip.GetOptions());
|
||||
vars.push_back(ppsspp_frameskip.GetOptions());
|
||||
vars.push_back(ppsspp_frameskiptype.GetOptions());
|
||||
vars.push_back(ppsspp_frame_duplication.GetOptions());
|
||||
vars.push_back(ppsspp_detect_vsync_swap_interval.GetOptions());
|
||||
vars.push_back(ppsspp_vertex_cache.GetOptions());
|
||||
vars.push_back(ppsspp_fast_memory.GetOptions());
|
||||
vars.push_back(ppsspp_block_transfer_gpu.GetOptions());
|
||||
|
@ -550,7 +724,7 @@ void retro_set_environment(retro_environment_t cb)
|
|||
vars.push_back(ppsspp_forced_first_connect.GetOptions());
|
||||
vars.push_back({});
|
||||
|
||||
cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars.data());
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars.data());
|
||||
}
|
||||
|
||||
static int get_language_auto(void)
|
||||
|
@ -648,6 +822,8 @@ static void check_variables(CoreParameter &coreParam)
|
|||
ppsspp_cpu_core.Update((CPUCore *)&g_Config.iCpuCore);
|
||||
ppsspp_io_timing_method.Update((IOTimingMethods *)&g_Config.iIOTimingMethod);
|
||||
ppsspp_lower_resolution_for_effects.Update(&g_Config.iBloomHack);
|
||||
ppsspp_frame_duplication.Update(&g_Config.bRenderDuplicateFrames);
|
||||
ppsspp_detect_vsync_swap_interval.Update(&detectVsyncSwapInterval);
|
||||
ppsspp_software_skinning.Update(&g_Config.bSoftwareSkinning);
|
||||
ppsspp_ignore_bad_memory_access.Update(&g_Config.bIgnoreBadMemAccess);
|
||||
ppsspp_lazy_texture_caching.Update(&g_Config.bTextureBackoffCache);
|
||||
|
@ -675,6 +851,21 @@ static void check_variables(CoreParameter &coreParam)
|
|||
g_Config.sLanguageIni = map_psp_language_to_i18n_locale(g_Config.iLanguage);
|
||||
i18nrepo.LoadIni(g_Config.sLanguageIni);
|
||||
|
||||
// Cannot detect refresh rate changes if:
|
||||
// > Frame skipping is enabled
|
||||
// > Frame duplication is enabled
|
||||
detectVsyncSwapInterval &=
|
||||
!g_Config.bAutoFrameSkip &&
|
||||
(g_Config.iFrameSkip == 0) &&
|
||||
!g_Config.bRenderDuplicateFrames;
|
||||
|
||||
bool updateAvInfo = false;
|
||||
if (!detectVsyncSwapInterval && (vsyncSwapInterval != 1))
|
||||
{
|
||||
vsyncSwapInterval = 1;
|
||||
updateAvInfo = true;
|
||||
}
|
||||
|
||||
if (ppsspp_internal_resolution.Update(&g_Config.iInternalResolution) && !PSP_IsInited())
|
||||
{
|
||||
coreParam.pixelWidth = coreParam.renderWidth = g_Config.iInternalResolution * 480;
|
||||
|
@ -682,13 +873,21 @@ static void check_variables(CoreParameter &coreParam)
|
|||
|
||||
if (gpu)
|
||||
{
|
||||
retro_system_av_info av_info;
|
||||
retro_get_system_av_info(&av_info);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av_info);
|
||||
retro_system_av_info avInfo;
|
||||
retro_get_system_av_info(&avInfo);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avInfo);
|
||||
updateAvInfo = false;
|
||||
gpu->Resized();
|
||||
}
|
||||
}
|
||||
|
||||
if (updateAvInfo)
|
||||
{
|
||||
retro_system_av_info avInfo;
|
||||
retro_get_system_av_info(&avInfo);
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avInfo);
|
||||
}
|
||||
|
||||
bool isFastForwarding = environ_cb(RETRO_ENVIRONMENT_GET_FASTFORWARDING, &isFastForwarding);
|
||||
coreParam.fastForward = isFastForwarding;
|
||||
|
||||
|
@ -736,6 +935,7 @@ void retro_set_input_state(retro_input_state_t cb) { input_state_cb = cb; }
|
|||
|
||||
void retro_init(void)
|
||||
{
|
||||
VsyncSwapIntervalReset();
|
||||
AudioBufferInit();
|
||||
|
||||
g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count);
|
||||
|
@ -779,7 +979,6 @@ void retro_init(void)
|
|||
|
||||
g_Config.Load("", "");
|
||||
g_Config.iInternalResolution = 0;
|
||||
g_Config.bRenderDuplicateFrames = true;
|
||||
|
||||
const char* nickname = NULL;
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_USERNAME, &nickname) && nickname)
|
||||
|
@ -822,6 +1021,7 @@ void retro_deinit(void)
|
|||
|
||||
libretro_supports_bitmasks = false;
|
||||
|
||||
VsyncSwapIntervalReset();
|
||||
AudioBufferDeinit();
|
||||
}
|
||||
|
||||
|
@ -843,7 +1043,7 @@ void retro_get_system_info(struct retro_system_info *info)
|
|||
void retro_get_system_av_info(struct retro_system_av_info *info)
|
||||
{
|
||||
*info = {};
|
||||
info->timing.fps = 60.0f / 1.001f;
|
||||
info->timing.fps = (60.0 / 1.001) / (double)vsyncSwapInterval;
|
||||
info->timing.sample_rate = SAMPLERATE;
|
||||
|
||||
info->geometry.base_width = g_Config.iInternalResolution * 480;
|
||||
|
@ -1103,6 +1303,7 @@ void retro_run(void)
|
|||
if( emuThreadState == EmuThreadState::PAUSED ||
|
||||
emuThreadState == EmuThreadState::PAUSE_REQUESTED)
|
||||
{
|
||||
VsyncSwapIntervalDetect();
|
||||
AudioUploadSamples();
|
||||
ctx->SwapBuffers();
|
||||
return;
|
||||
|
@ -1113,6 +1314,7 @@ void retro_run(void)
|
|||
|
||||
if (!ctx->ThreadFrame())
|
||||
{
|
||||
VsyncSwapIntervalDetect();
|
||||
AudioUploadSamples();
|
||||
return;
|
||||
}
|
||||
|
@ -1120,6 +1322,7 @@ void retro_run(void)
|
|||
else
|
||||
EmuFrame();
|
||||
|
||||
VsyncSwapIntervalDetect();
|
||||
AudioUploadSamples();
|
||||
ctx->SwapBuffers();
|
||||
}
|
||||
|
@ -1247,7 +1450,11 @@ float System_GetPropertyFloat(SystemProperty prop)
|
|||
switch (prop)
|
||||
{
|
||||
case SYSPROP_DISPLAY_REFRESH_RATE:
|
||||
return 60.f;
|
||||
// Have to lie here and report 60 Hz instead
|
||||
// of (60.0 / 1.001), otherwise the internal
|
||||
// stereo resampler will output at the wrong
|
||||
// frequency...
|
||||
return 60.0f;
|
||||
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
|
||||
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
|
||||
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
|
||||
|
|
Loading…
Add table
Reference in a new issue