Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reminder: pause/continue in sokol_app.h #301

Open
floooh opened this issue May 24, 2020 · 9 comments
Open

Reminder: pause/continue in sokol_app.h #301

floooh opened this issue May 24, 2020 · 9 comments
Assignees

Comments

@floooh
Copy link
Owner

floooh commented May 24, 2020

See here: https://twitter.com/andsve/status/1264513943051153408

Basically:

sapp_pause() skip calling the frame callback and buffer flip until sapp_continue() is called. This would be useful (amonst others) for event-driven application which only need to render on input events.

@floooh floooh self-assigned this May 24, 2020
@martincohen
Copy link
Contributor

martincohen commented Jun 4, 2020

I'm not sure what non-desktop platforms do, but I'd like to point out a few things. (And I'm also sure you already considered these)

Problem I see with this is that you'd lose periodic per-frame update call, which might be used to do app-specific optimizations or tasks:

  • Managing threads.
  • Running UI animations.
  • Slowing down updates with sleep if nothing is happening for a long time. (On Windows you can even sleep in alertable mode). Slowdown can happen when the app is not in front, or by looking at frequency at which it receives input events.
  • Detecting whether the drawing data is different from previous frame, and either doing no draw, partial per-tile draw, or full redraw.

So what I'd propose is a redraw flag (perhaps via a function similar to sapp_consume_event):

  • If the flag is true, then sapp does buffer swap and sets it to false.
  • If the flag is false then sapp does not swap buffers.
  • In either case frame callback is called.

This approach also makes you not having to worry about pairing pause/continue calls.

@floooh
Copy link
Owner Author

floooh commented Jun 4, 2020

Some more brain-dumping after more feedback from that twitter thread:

  • the important part isn't actually to pause/continue the entire frame callback, but to signal to sokol-app whether a frame-swap is needed

  • even in "paused state" it would be important to get a regular "tick", and not freeze the application completely (apart form "irregular" events)

For the two reasons above it's probably better to not ever stop calling the frame callback at regular intervals, but instead let the application decide whether it wants to do any rendering (or other expensive stuff) inside the frame callback, and instead tell sokol-app whether a frame-swap is needed.

Some (discarded) alternatives to not calling the frame callback:

If no frame-swap is taking place, the frame-callback-rate must be throttled with a sleep() on some platforms (this is already happening in some cases as a special workaround. For instance;

sokol/sokol_app.h

Lines 5078 to 5083 in 786c003

#if defined(SOKOL_D3D11)
IDXGISwapChain_Present(_sapp_dxgi_swap_chain, _sapp.swap_interval, 0);
if (IsIconic(_sapp_win32_hwnd)) {
Sleep(16 * _sapp.swap_interval);
}
#endif

Some ideas how to communicate the frame-swap to sokol-app:

  • Have a sapp_swap() functions which must be called each frame before returning from the frame callback (this would break all existing applications, alternatively only require this function to be called when a new init-flag "explicit_swap" is true). sapp_swap() would only set a an internal flag it wouldn't call any actual swap-functions, instead swapping would happen as usual after the frame callback returns, but only if the internal flag is set. The internal flag would be cleared each frame before the frame callback is called.

  • Have a frame-callback function with a bool return value which indicates whether a swap should take place (to not break compatibility this would require a new frame callback pointer in sapp_desc)

  • Have a "sticky" redraw-flag somewhat similar to Martin's comment above. The tricky part here is that changing the state of the flag from inside the frame callback should only affect the next frame, but is this always the wanted behaviour?

...all in all I'm currently favouring the sapp_swap() function together with a new "bool explicit_swap" member in sapp_desc.

@floooh
Copy link
Owner Author

floooh commented Jun 4, 2020

Some other minor things:

  • Should we get rid of "swap_interval"? It seems rather pointless now, and what we usually want is just turning vsync on/off (sokol_app: turning off vsync ? #292)

  • In cases where "manual throttling" via sleep() is needed, how long should the sleep be? Currently it's hardwired to 60 Hz, but this is different from the usual rate at which the frame callback is called if the display refresh rate isn't 60Hz. Alternatives (because on some platforms it's not possible to query the display refresh rate):

    • hardwired at 60Hz
    • keep track of previous frame durations and use this
    • let the user decide how long to sleep in such "no-swap frames" (but this might be counterproductive on e.g. the browser platform where the whole page might become laggy, so probably not a good idea)

@hb3p8
Copy link

hb3p8 commented Jun 4, 2020

  • let the user decide how long to sleep in such "no-swap frames" (but this might be counterproductive on e.g. the browser platform where the whole page might become laggy, so probably not a good idea)

Could we even do it in browsers at all? :)

@Manuzor
Copy link
Contributor

Manuzor commented Jun 14, 2020

One thing that may help a little with the OPs goal is to not render at all if the window isn't focused. If there was something like sapp_has_focus(), and the user was able to control buffer swaps (e.g. sapp_swap() as mentioned above), they'd keep the ability to update periodically but can choose to only update/render parts of their system when running in "background mode".

While I would like to be able to use sokol_app.h for such a use case myself, I wonder: Is this maybe out of scope?

@floooh
Copy link
Owner Author

floooh commented Jun 14, 2020

@Manuzor from what I've seen, most platforms don't allow such a fine level of control for when the draw callback is called, e.g. on the web platform, a hidden tab might be completely deactived or called with an extremely low frequency (you can check this for instance here https://floooh.github.io/tiny8bit/ when opening the emulators in a hidden tab, or switching away from a running emulator, the emulation will essentially freeze until the tab is brought to the foreground again).

Similar issues with putting applications on mobile platforms into the background.

One intended way to keep track of such things are the various sokol-app events (like SUSPENDED/RESUMED and ICONIFIED/RESTORED), but those might behave slightly differently on different platforms, and AFAIK on the web platform, it's not possible to detect if a tab is "slowed down" because it's a background tab (I haven't looked all that hard though)

@prime31
Copy link
Contributor

prime31 commented Jul 26, 2020

The way SDL does this works really well for event driven apps. They have a method SDL_WaitEventTimeout(n) that returns the next event as soon as it arrives or sleeps for n milliseconds. Something like that provides a nice balance between keeping things responsive and sleeping as long as possible while waiting for an event.

@laserbeam3
Copy link

This may be an old/irelevant issue, I hope it's ok to necro it. I also want to only render on event, not every frame to save on battery/GPU usage. To achieve this, on a local fork, I made frame_cb return a bool instead of void. If the return value is false, I skip the framebuffer flip call in sokol_app.h and I sleep some arbitrary amount of time.

This is rudimentary, it may not be the desired approach, and I've only added this behaviour in sokol_app.h for 1 single platform. However, this solution avoids a lot of event complexity and lets me manage when I want to skip frames, and when I don't. This also lets me manually decide I need to render frames when some animations are ongoing (irrespective of whether events took place or not).

Instead of using the return value of frame_cb, a skip_rendering_this_frame() call may work.

Think this strategy might work in a more general case?

@floooh
Copy link
Owner Author

floooh commented Mar 28, 2021

Thanks for the feedback! The issue isn't dead, just a bit neglected ;) I might also be a bit paralyzed towards bigger sokol_app.h changes because the next "big thing" for sokol_app.h will be multiwindow-support, and I'm not sure how much it makes sense to do (somewhat deep) changes like "lazy rendering" until multi-window is done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants