flycast
flycast copied to clipboard
[libretro] Core breaches frontend timing (i.e. retro_system_av_info:timing.fps is not observed)
This is a general libretro core issue identified while investigating audio sample pacing. I don't know if it's possible to fix (an almost identical problem was recently found in the PPSSPP core, with no general solution...) but I'm reporting it for the sake of record keeping :)
The issue is this:
- In
retro_get_system_av_info()
a core specifiessample_rate
andfps
. This represents a contract with the frontend:- The frontend will call
retro_run()
exactlyfps
times per second (discounting fast forward/rewind/etc.) - The frontend will expect from the core exactly (
sample_rate
/fps
) audio samples per call ofretro_run()
- The frontend will call
- When flycast iterates content that runs natively at the set
fps
, this contract is broadly observed: one call ofretro_run()
seems to emulate one frame's worth of runtime, and audio samples (batched in chunks of 512) average out to ~(sample_rate
/fps
) - However, when flycast iterates content that doesn't run at the set
fps
(or when internal frame skipping is enabled), the contract is breached. For example:- If
fps
is set to 60 and the content runs 'natively' at 30 fps, then one call ofretro_run()
seems to emulate two frames' worth of runtime, and double the expected number of audio samples are sent to the frontend. - The core is therefore running effectively at double speed - but by over-saturating the audio buffer, the frontend is forced to run in 'slow motion'
- The two effects kinda-sorta cancel out, such that frames are presented to the user at approximately the expected rate...
- ...but this behaviour is out of spec/illegal, and completely bamboozles the frontend timing - such that proper frame pacing is impossible
- If
There are essentially two possible fixes:
- The core is modified so each call of
dc_run()
(or the threaded equivalent) inretro_run()
only emulates (1 /fps
) seconds' worth of clock cycles, regardless of the native content frame rate - The core detects the current effective frame rate of the running content, and updates the
fps
value reported to the frontend viaRETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO
where appropriate - sodc_run()
gets called by the frontend at the proper expected rate
(1) would allow for better frame pacing overall (running at less than the user's display refresh rate is never ideal) but I imagine that neither solution is easy (or even practical)...