Open floooh opened 4 years ago
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:
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.So what I'd propose is a redraw
flag (perhaps via a function similar to sapp_consume_event
):
true
, then sapp
does buffer swap and sets it to false
.false
then sapp
does not swap buffers.frame
callback is called.This approach also makes you not having to worry about pairing
pause/continue calls.
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:
have a "new frame" event which is called each frame before any other events (this is probably still a good idea for other reasons: https://github.com/floooh/sokol/issues/293 and https://github.com/floooh/sokol/issues/283)
have a separate "paused" callback (not a good idea)
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;
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.
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 (https://github.com/floooh/sokol/issues/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):
- 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? :)
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?
@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)
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.
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?
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.
See here: https://twitter.com/andsve/status/1264513943051153408
Basically:
sapp_pause()
skip calling the frame callback and buffer flip untilsapp_continue()
is called. This would be useful (amonst others) for event-driven application which only need to render on input events.