libretro / RetroArch

Cross-platform, sophisticated frontend for the libretro API. Licensed GPLv3.
http://www.libretro.com
GNU General Public License v3.0
10.39k stars 1.84k forks source link

Regarding timings #19

Closed bji closed 12 years ago

bji commented 12 years ago

Hi there, I was told that the best way to contact 'maister', who would be able to address questions about libretro, is via an Issue on this project. So here I go ...

I'm planning on implementing a libretro wrapper around libmame, as per some email discussions. I think that all of my questions have been answered in the emails and IRC chat (and also by reading the up-to-date libretro.h file instead of the old crufty one I had been referencing!), but there is one important issue that needs to have full clarity.

Can you please confirm my understanding that the libretro design is such that the caller to retro_run() is expected to run at whatever rate the frontend wants to (probably dictated by waiting for vertical refresh intervals on the display), and the libretro implementation is supposed to always call back with the frame that should be rendered at that moment?

So if the emulated game originally ran at 59.97 Hz, and retro_run() is being called at 60 Hz, it is the responsibility of the libretro implementation to occasionally deliver the same frame of video twice (and skip a frame of audio) in order to match the emulated game's frame rate? And likewise, if the original game ran at 61 Hz and retro_run() is being called at 60 Hz, it is the responsibility of libretro to occasionally drop frames of video internally in order to match the emulated game's frame rate?

If so, can you please explain what RETRO_ENVIRONMENT_GET_CAN_DUPE is for, as I don't understand what it would do in this design. Thank you!

Themaister commented 12 years ago

libretro is designed to be time-invariant, i.e. it is up to the frontend to call retro_run() at a suitable interval (usually by vsync/audio blocking). The libretro implementation should step exactly one frame internally, and callback the video frame along with audio generated during that frame. All considerations to timing etc are made by the frontend.

The libretro implementation cannot render frames behind the frontends back. Generated output of the implementation must be exact no matter how long time there is in-between frames (in the real time). Note that the frontend has complete control over the real time.

Since in mame you have to run it in its own main loop, I suggest a mechanism where you unlock the mame thread in retro_run(), and lock it when a new frame has been generated. This is essentially a cothread mechanism. I tried this with iMame, and it seemed to work just fine.

GET_CAN_DUPE is for cases when a libretro implementation generates two exact frames after each other, typically GameBoy does this. Normally, we don't care about rendering that duped frame as it's already on the screen, but FFmpeg recording must pick up this duped frame to maintain sync. In this case, NULL is passed to the video callback. You must explicitly check that you can use this NULL mechanism, as some frontends will probably not treat this NULL correctly and crash. It will probably be guaranteed to be present when libretro API is updated to v2.

bji commented 12 years ago

Just curious, how does the front end ensure that it calls retro_run() at the correct rate if that rate doesn't match the vertical refresh rate of the display?

For example, what does the frontend do to handle a 59.97 Hz emulator on a 60.0 Hz display? Does it internally track the video frame clock of the emulator and skip/duplicate frames on the display device as necessary?

Themaister commented 12 years ago

If monitor is 60 Hz, and game is 59.97 Hz, the game is simply run as if it was 60 Hz if it's synced against vblank. Sampling rate is compensated by the same amount. If you disable vsync, it is synced on audio anyways. Adjustments like these are done if monitor refresh rate and game refresh rate are sufficiently close.