Open inodentry opened 3 years ago
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:
(Chime in if my understanding is wrong; I haven't poked at this much myself.)
"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.
"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).
Yeah I think the next step forward here is decoupling rendering from "game updates", which we have discussed a bit here: https://github.com/bevyengine/bevy/issues/1098#issuecomment-759047862
This opens up the door to "frame rate limiting", "pipelined rendering", disabling rendering when minimized / unfocused, etc.
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.
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.
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.
I created a prototype framerate limiter here https://github.com/bevyengine/bevy/issues/3317#issuecomment-997513775 with the goal of reducing input latency.
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));
}
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.
support non-game gui applications that should sleep when idle.
The quoted part of the issue is solved by #3974.
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))
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.
@siler does this not work for you?
MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(
std::time::Duration::from_secs_f64(1.0 / UPDATES_PER_SECOND),
))
JMS55 removed this from the 0.14 milestone 2 days ago
@JMS55, does that mean this will go into 0.15?
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.
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.