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

Frame rate limiting #1343

Open
Tracked by #76
inodentry opened this issue Jan 29, 2021 · 16 comments · May be fixed by #9034
Open
Tracked by #76

Frame rate limiting #1343

inodentry opened this issue Jan 29, 2021 · 16 comments · May be fixed by #9034
Labels
A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible

Comments

@inodentry
Copy link
Contributor

Add a way to limit the frame rate.

Ideally, allow for more flexible control over updates, to support non-game gui applications that should sleep when idle.

@alice-i-cecile
Copy link
Member

alice-i-cecile commented Jan 29, 2021

In this discussion, I think it's important to clearly distinguish between "frame rate" (how often your screen draws), "system tick rate" (how often a given system runs) and "game tick rate" (how often the main game loop runs). I think these are all important to be able to control and limit ergonomically, but the details are a bit distinct.

Currently, as I understand it, there are three ways to tackle this sort of functionality:

  1. Adding a Timer to each of the relevant criteria. Controls system tick rate.
  2. The FixedTimeStep run criteria. Controls system tick rate.
  3. Enabling vsync. Controls frame rate, and incidentally limits game tick rate.

(Chime in if my understanding is wrong; I haven't poked at this much myself.)

@inodentry
Copy link
Contributor Author

"system tick rate"

This is not relevant to this issue. That's something to be discussed with Run Criteria (as you point out).

Here we are talking about the global refresh rate.

"game tick rate" is coupled with rendering frame rate, by design, so they are practically synonymous.

vsync

VSync is orthogonal. There needs to be a frame rate limiter independent of vsync. VSync is about presenting the frames with the correct timing (synchronized to the monitor), not about framerate. It is to ensure the frames are displayed correctly on the screen, without visual artifacts (tearing).

Vsync can (and preferably should, when supported; we are already doing this in bevy) be implemented in a way that does not limit frame rate. See "mailbox vsync". Also the various vendor/driver provided solutions that go by various names like "adaptive sync", "fast sync", whatever.


As for GUI applications, you don't want to refresh at all if there is nothing to do. Unlike games, they are mostly idle. Therefore, for proper power savings for GUI apps, there needs to be more flexible and aggressive throttle control, to only refresh when there are things to do: handle external events / input, ongoing animations, any change that needs to update the UI / screen.

@alice-i-cecile
Copy link
Member

"game tick rate" is coupled with rendering frame rate, by design, so they are practically synonymous.

I don't think this is a particularly good design, merely a convenient one.

If we're discussing ways in which users can control and limit the performance of their game, I think that drawing this distinction and working towards the decoupling of the concepts is an important part of the puzzle.

For example, relevant to your point on non-game applications: we may want to allow the main logic loop to proceed unimpeded, while the rendering is paused completely until there is something new to draw (or the window is focused).

@cart
Copy link
Member

cart commented Jan 29, 2021

Yeah I think the next step forward here is decoupling rendering from "game updates", which we have discussed a bit here: #1098 (comment)

This opens up the door to "frame rate limiting", "pipelined rendering", disabling rendering when minimized / unfocused, etc.

@karroffel karroffel added C-Feature A new feature, making something new possible A-Rendering Drawing game state to the screen labels Jan 30, 2021
This was referenced Feb 3, 2021
cart added a commit to cart/bevy that referenced this issue Feb 7, 2021
the mailbox option doesn't do framelimiting on some devices. we need to rely on vsync for framelimiting until bevy supports framelimiting internally. bevyengine#1343
cart added a commit that referenced this issue Feb 9, 2021
the mailbox option doesn't do framelimiting on some devices. we need to rely on vsync for framelimiting until bevy supports framelimiting internally. #1343
rmsc pushed a commit to rmsc/bevy that referenced this issue Feb 10, 2021
the mailbox option doesn't do framelimiting on some devices. we need to rely on vsync for framelimiting until bevy supports framelimiting internally. bevyengine#1343
@Toqozz
Copy link

Toqozz commented Apr 16, 2021

I'm assuming that by "decoupling rendering" you are referring to a separate render thread.

Please note that rendering in a separate thread (or in many cases, decoupling rendering in general) from game updates will inherently introduce a degree of latency:

Objects move in systems -> wait some time -> picked up by the renderer and frame is rendered with that data.

I think anything that adds latency is important to consider carefully. Personally, I feel that decoupling rendering may be over-engineering here and serves mostly to confuse users with another layer to think about.

I understand that there are advantages as well, so it may be worthwhile, but so far I don't see huge benefit.

@forbjok
Copy link
Contributor

forbjok commented Jun 2, 2021

but so far I don't see huge benefit

One significant benefit is that if you can run non-rendering systems at a much higher framerate, it reduces the chance of ignored inputs. This is already an issue in Bevy, even when running at a full 60fps, and the lower the framerate is the worse it will become. Maybe there are other better ways to fix this (some way of guaranteeing that that the .pressed and .just_pressed checks return true whenever the key was pressed at any point since the previous update cycle, even if it was released before the current?), but being able to run update cycles at for example 120fps instead of 60, while still rendering at 60, would mean keypresses are checked more frequently, making the problem less noticeable and annoying to the player.

@Toqozz
Copy link

Toqozz commented Jun 3, 2021

One significant benefit is that if you can run non-rendering systems at a much higher framerate ...

Input is definitely an interesting case here, and I agree that there should be a way to poll input at a faster rate than the framerate. I'm still unconvinced that decoupled rendering is the answer here though. To me it feels like more of a specific problem which should be solved independently.

@aevyrie
Copy link
Member

aevyrie commented Dec 20, 2021

I created a prototype framerate limiter here #3317 (comment) with the goal of reducing input latency.

@mwbryant
Copy link
Contributor

This is the (janky) solution I've used for my project so far, just a nothing system that sleeps to hold up the frame for a set period of time. This is my temp fix until something better comes along (the change to fifo present mode doesn't seem to work on my laptop)

fn frame_limiter_system() {
    use std::{thread, time};
    thread::sleep(time::Duration::from_millis(10));
}

@aevyrie
Copy link
Member

aevyrie commented Feb 21, 2022

Shameless plug for https://github.com/aevyrie/bevy_framepace, since my last post.

I've done quite a bit of experimentation in this area for the work we are doing at Foresight with desktop applications. The nice thing about frame pacing combined with framerate limiting, is that it has much better latency than Fifo vsync alone, latency as good as Immediate, with no frame tearing, and much less CPU/GPU use than any of Fifo/Mailbox/Immediate.

@aevyrie
Copy link
Member

aevyrie commented Mar 4, 2022

support non-game gui applications that should sleep when idle.

The quoted part of the issue is solved by #3974.

@JMS55 JMS55 added this to the 0.11 milestone Feb 17, 2023
@JMS55 JMS55 modified the milestones: 0.11, 0.12 Jun 11, 2023
@B-head B-head linked a pull request Jul 4, 2023 that will close this issue
8 tasks
@JMS55 JMS55 modified the milestones: 0.12, 0.13 Sep 28, 2023
@alexniver
Copy link

Can we define some thing like Update in app.add_systems(Update, xx_xx);

such as UpdateByDuration(Duration), then use it by app.add_systems(UpdateByDuration(Duration::from_seconds(1.0), xx_xx))

@siler
Copy link

siler commented Jan 9, 2024

I would like a way to globally cap the rate at which the main schedule runs similar to FixedUpdate (or lock it in sync with FixedUpdate).

My use case is: I have a headless server running MinimalPlugins which shares Bevy code with a client through a library including some plugins and systems. While there is FixedUpdate which some systems use, there are also shared systems that use Update via plugin configuration. The client would like these systems to run at frame speed while the server would like these systems to run at fixed update, and it would be nice if I could just configure the current schedule to run less often.

I'm mostly reporting this here because the bevy_framepace plugin is currently being considered as a solution for this, but it doesn't work for my use case because it depends on the render subsystem existing.

@alice-i-cecile alice-i-cecile modified the milestones: 0.13, 0.14 Jan 24, 2024
@tbillington
Copy link
Contributor

@siler does this not work for you?

MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(
    std::time::Duration::from_secs_f64(1.0 / UPDATES_PER_SECOND),
))

@JMS55 JMS55 removed this from the 0.14 milestone Apr 6, 2024
@Maximetinu
Copy link
Contributor

JMS55 removed this from the 0.14 milestone 2 days ago

@JMS55, does that mean this will go into 0.15?

@JMS55
Copy link
Contributor

JMS55 commented Apr 8, 2024

There's no set date I can give you.

At the start of a release cycle I typically add tasks I think would be good to do to the milestone. As we get towards the end of a cycle, I remove the ones that won't realistically be completed in time or aren't even started.

No one's taken up frame limiting, so I've removed it from the milestone.

github-merge-queue bot pushed a commit that referenced this issue Jul 14, 2024
…4155)

# Objective

- Fixes #14135 

## Solution

- If no windows are visible, app updates will run regardless of redraw
call result.

This a relatively dirty fix, a more robust solution is desired in the
long run:
#1343 (comment)

https://discord.com/channels/691052431525675048/1253771396832821270/1258805997011730472
The solution would disconnect rendering from app updates.

## Testing

- `window_settings` now works

## Other platforms

Not a problem on Linux:
https://discord.com/channels/691052431525675048/692572690833473578/1259526650622640160
Not a problem on MacOS:
https://discord.com/channels/691052431525675048/692572690833473578/1259563986148659272
mockersf pushed a commit that referenced this issue Aug 2, 2024
…4155)

# Objective

- Fixes #14135 

## Solution

- If no windows are visible, app updates will run regardless of redraw
call result.

This a relatively dirty fix, a more robust solution is desired in the
long run:
#1343 (comment)

https://discord.com/channels/691052431525675048/1253771396832821270/1258805997011730472
The solution would disconnect rendering from app updates.

## Testing

- `window_settings` now works

## Other platforms

Not a problem on Linux:
https://discord.com/channels/691052431525675048/692572690833473578/1259526650622640160
Not a problem on MacOS:
https://discord.com/channels/691052431525675048/692572690833473578/1259563986148659272
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible
Projects
None yet
Development

Successfully merging a pull request may close this issue.