Open dvec opened 2 years ago
I'm also interested in this, mainly because my applications don't require high frame rates and should be CPU efficient. Should it be up to the user to limit the FPS or will egui implement such a feature?
egui_glow
backend is using vsync (as well as egui_glium
), so as far as I know, app FPS should be limited to monitor refresh rate. But by some reason egui
demo app shows about 1500 FPS, and I was able to verify this number using counter in update
function. It seems like egui
runs update
function 1500 times per second, while GPU renders only 60 frames (on 60 HZ screen), which looks like overkill.
UPD: I was able to fix vsync issue by updating macOS to 12.1 and it seems to be not related to egui
(even minecraft was affected), so this issue is not a problem for me anymore. But I still think egui
should have a way to control FPS and turn on/off vsync.
I oppose the request to this feature, at least for egui
(eframe
would be a different story). My understanding is, that egui
is supposed to generate a list of vertices spanning triangles, maybe some color information for said primitives and that's it. Time is completely out of the equation for egui
itself. How should egui
enforce the FPS limit? By blocking wait? Because egui
doesn't require threads and isn't async, I don't see another obvious way (but just truncating every new call to egui for the time slot of the current frame, which would again create spin-lock like busy waiting).
Also it would be kind of weird if a library which is not at all time-aware starts having a blocking wait function (which is what "enforcing an fps limit" essentially boils down to).
On a side note, using just
const MAX_FPS: f64 = 60;
loop{
// egui code
std::thread::sleep(Duration::from_sec(1.0/MAX_FPS));
}
Will result in jitter for the actual frame time. Rather it makes sense to wait only time left for the current frame, something like this (untested, just off my head)
const MAX_FPS: f64 = 60;
let frame_time = Duration::from_secs(1.0/MAX_FPS);
let mut next_frame = Instant::now();
loop{
// egui code
next_frame += frame_time;
std::thread::sleep(next_frame - Instant::now());
}
@wucke13 Beware using Instant for this, it tries to prevent subtraction from ever overflowing, and to accomplish this mutex locks in old versions. Newer code is lockless but will still create cache line contention if you have multiple threads calling Instant::now()
. It's not ideal for realtime and measuring performance if your app/game is multithreaded.
For most people, vsync
is the best choice. If that is broken, it should be fixed. Anything slower than vsync will make animations non-smooth. Of course, if one wants to save CPU, this can still be worthwhile.
As @wucke13 points out, you can always do this sleeping yourself in order to enforce a maximum FPS. Beware though that std::thread::sleep
has terrible resolution on windows.
As for turning off vsync: why would you want to do that? I guess it can give you lower latency, but is it really noticeable?
Now: if vsync is broken for more people, then perhaps it makes sense for eframe
to throttle the FPS as a fallback, e.g. to 200 FPS by default (since there are high-hz monitors), but it is not obvious how to do that since std::thread::sleep
has such terrible resolution on Windows.
Without knowing much about the technical details would the approaches of the spin_sleep
crate be able to provide any guidance?
The tricks spin_sleep
uses to get more accurate sleeping in Windows (timeBeginPeriod
) could be useful, but the spinning part is counter-productive (we want to save CPU after all).
@emilk Just for clarification: the maximum framerate is determined by the backend, i.e. 60fps when using glium
or glow
. If I want to lower this value, then I have to put in some thread::sleep() myself. Is this correct?
BTW: is there an internal frame count that can be queried?
The tricks
spin_sleep
uses to get more accurate sleeping in Windows (timeBeginPeriod
) could be useful, but the spinning part is counter-productive (we want to save CPU after all).
Ye spinning is more CPU intensive. Was more thinking of it in the context of the fallback throttle you mentioned if vsync fails. In that case I'm guessing it would (maybe) save CPU resources instead of letting it run wild. But again i don't know the technical details. Just peeking from the shadow here mostly 👀
@emilk Just for clarification: the maximum framerate is determined by the backend, i.e. 60fps when using
glium
orglow
. If I want to lower this value, then I have to put in some thread::sleep() myself. Is this correct?
It should be determined by your display refresh rate, which often is 60Hz. If you want something lower you need to add std::thread::sleep
, yes.
I would like to revive this issue, because I'd really like to set a higher FPS limit than my monitor refresh rate. I am convinced this would improve the general perception of egui desktop apps by a lot! The lag and rubberbanding when turning on vsync on a 60Hz monitor just feels so sluggish (equally bad for glow and wgpu, slightly better in the browser). Maybe I am especially sensitive to input lag, but it can't be just me. I usually turn off vsync because of that, but that's a bad solution since it burns unnecessarily many CPU cycles and it lead to weird crashes on windows in the past (not on linux though!). Setting a limit of something like 120 or 180FPS would be a game changer!
I am also encountering this, similarly to @vkahl, I would like to disable vsync where possible since I am using egui to render things similar to the fractal clock example, using egui primitives to render interesting graphics in the background of my UI, making them run at a high fps is paramount to making it look good when a monitor supports it. You should have the option to control the fps cap (or if there is one) as well as controlling if vsync is enabled.
@emilk Just for clarification: the maximum framerate is determined by the backend, i.e. 60fps when using
glium
orglow
. If I want to lower this value, then I have to put in some thread::sleep() myself. Is this correct?It should be determined by your display refresh rate, which often is 60Hz. If you want something lower you need to add
std::thread::sleep
, yes.
What if the Display Refresh rate is higher then 60hz? Like for example 120hz. Then all Apps from egui would look clunky while the browser, other websites and apps would look smooth in contrast.
I've created a simple app that renders long scrollable images grid with some animations and I'm really excited by performance of this library: on my laptop I got about 1500 FPS using glow backend. But there is one thing bothering me: CPU & GPU usage go up to 90% when I run my app, so I want to set FPS limit to save energy and get rid of cooler noise. For now I'm using
std::thread::sleep
inupdate
function, but this solution doesn't seems clear.Describe the solution you'd like Since FPS limiting is not backend-dependent, we can add
fps_limit(&self) -> Option<u32>
function toApp
trait.Describe alternatives you've considered Also, we can add
set_fps_limit(&self, limit: u32)
toFrame
, so it should be able to call it fromApp::setup
.