libretro-mirrors / libretro-arb

For proposed improvements to libretro API.
8 stars 2 forks source link

Frameskip support #16

Open mprobinson opened 9 years ago

mprobinson commented 9 years ago

There are two cases to handle here: (1): Cores running too slow for the display, and frontends telling the core to skip frames to give the core a chance to catch up. (2): Cores runnings at framerates higher than the display refresh rate, and the frontend telling the core to skip rendering frames to divide down the rendered framerate to match the display refresh rate.

Both cases need to interact cleanly with pause, frame advance, rewind, fast forward, fast rewind, and save states.

In case (2), the core must be permitted to render frames corresponding to past core state, otherwise performance will be severely harmed. The number of frames in the past should be exposed as a frontend option to allow users to trade between performance and latency. If the user pauses, frame advances, or loads saved state, the core must render a frame corresponding to current core state immediately. In the case of fast forward and fast rewind, the permitted latency may be increased. In the case of normal speed rewind no change is required.

The best way I can see to implement this is to split retro_run() into retro_run() and retro_render(). In case (1) the frontend merely skips retro_render() as required. In case (2), the core needs an additional environment variable to get the desired number of frames of rendering latency from the frontend, e.g. RETRO_ENVIRONMENT_GET_RENDER_LATENCY. The core will need to query this every retro_run() so it can know when to begin rendering a frame, as well as every retro_render() to know when that rendering has been invalidated by a pause or similar. A reasonable worst case scenario for this with current day hardware is a 720fps core running on an 180Hz CRT, giving an additional 1080* function calls per second. If this is unacceptable overhead then the alternative would be to pass the render latency into every call of retro_run() and retro_render().

Splitting rendering from core state update also allows for a clean implementation of partial frame rendering, which could allow for lower video latency by rendering to the front buffer Atari 2600 style. In this case the core would report support for partial frames and the frontend would call retro_render_partial() instead of retro_render().

To make things simpler for the cores, I suggest retro_run() and retro_render() should be guaranteed to be called from the same thread. Type (2) cores will need to implement their own internal threading to gain the performance benefits of higher latency rendering.

mprobinson commented 9 years ago

Alcaro pointed out on IRC that some cores render to a framebuffer on the stack, so splitting off retro_render() will be a problem for them. In the interests of keeping things as simple as possible for cores that don't need this functionality it would be better to handle all this through environment variables.

Alcaro commented 9 years ago

I've been thinking about this a little, and there is one thing I don't understand: Why do we need to expose render latency at all? Just let the core call video_cb as soon as it's done (from the child thread, to get rid of that round of polling); the front can measure how long it took, if it's relevant at all (we could still have a render latency setting, but the core won't see it). It sounds to me like it would give us all needed features, at less complexity than your method.

Plus something for plain old frameskip, of course. There would be little overlap between the two use cases, but we could probably put them in the same env anyways, they're similar enough.

Or maybe make the core call an env on retro_init to ask if the core is allowed to delay rendering non-skipped frames; no core has any real reason to use both delayed and non-delayed rendering. (If the front really wants the rendering finished before executing the next frame, it can let retro_run finish and then lock the main thread until video_cb gets called.)

mprobinson commented 9 years ago

That sounds like a good design. I'd be happy to see it in libretro 2.0.