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

Clear way to set FPS limit #1109

Open
dvec opened this issue Jan 14, 2022 · 14 comments
Open

Clear way to set FPS limit #1109

dvec opened this issue Jan 14, 2022 · 14 comments
Labels
feature New feature or request

Comments

@dvec
Copy link
Contributor

dvec commented Jan 14, 2022

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 in update 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 to App trait.

Describe alternatives you've considered
Also, we can add set_fps_limit(&self, limit: u32) to Frame, so it should be able to call it from App::setup.

@dvec dvec added the feature New feature or request label Jan 14, 2022
@sourcebox
Copy link

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?

@dvec
Copy link
Contributor Author

dvec commented Jan 15, 2022

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.

@wucke13
Copy link
Contributor

wucke13 commented Jan 16, 2022

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());
}

@jgarvin
Copy link

jgarvin commented Jan 16, 2022

@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.

@emilk
Copy link
Owner

emilk commented Jan 17, 2022

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.

@Blisto91
Copy link

Without knowing much about the technical details would the approaches of the spin_sleep crate be able to provide any guidance?

@emilk
Copy link
Owner

emilk commented Jan 18, 2022

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).

@sourcebox
Copy link

@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?

@sourcebox
Copy link

BTW: is there an internal frame count that can be queried?

@Blisto91
Copy link

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
Copy link
Owner

emilk commented Jan 18, 2022

@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?

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.

@vkahl
Copy link

vkahl commented Feb 2, 2024

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!

@NtLoadDriverEx
Copy link

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.

@iMonZ
Copy link

iMonZ commented Oct 21, 2024

@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?

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.

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

No branches or pull requests

9 participants