Skip to content

Commit 597d2d7

Browse files
hymminodentry
authored andcommitted
Update time by sending frame instant through a channel (bevyengine#4744)
# Objective - The time update is currently done in the wrong part of the schedule. For a single frame the current order of things is update input, update time (First stage), other stages, render stage (frame presentation). So when we update the time it includes the input processing of the current frame and the frame presentation of the previous frame. This is a problem when vsync is on. When input processing takes a longer amount of time for a frame, the vsync wait time gets shorter. So when these are not paired correctly we can potentially have a long input processing time added to the normal vsync wait time in the previous frame. This leads to inaccurate frame time reporting and more variance of the time than actually exists. For more details of why this is an issue see the linked issue below. - Helps with bevyengine#4669 - Supercedes bevyengine#4728 and bevyengine#4735. This PR should be less controversial than those because it doesn't add to the API surface. ## Solution - The most accurate frame time would come from hardware. We currently don't have access to that for multiple reasons, so the next best thing we can do is measure the frame time as close to frame presentation as possible. This PR gets the Instant::now() for the time immediately after frame presentation in the render system and then sends that time to the app world through a channel. - implements suggestion from @aevyrie from here bevyengine#4728 (comment) ## Statistics ![image](https://user-images.githubusercontent.com/2180432/168410265-f249f66e-ea9d-45d1-b3d8-7207a7bc536c.png) --- ## Changelog - Make frame time reporting more accurate. ## Migration Guide `time.delta()` now reports zero for 2 frames on startup instead of 1 frame.
1 parent 94229ee commit 597d2d7

File tree

5 files changed

+51
-2
lines changed

5 files changed

+51
-2
lines changed

crates/bevy_render/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ bevy_math = { path = "../bevy_math", version = "0.8.0-dev" }
3939
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.8.0-dev" }
4040
bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", features = ["bevy"] }
4141
bevy_render_macros = { path = "macros", version = "0.8.0-dev" }
42+
bevy_time = { path = "../bevy_time", version = "0.8.0-dev" }
4243
bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" }
4344
bevy_window = { path = "../bevy_window", version = "0.8.0-dev" }
4445
bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" }

crates/bevy_render/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ impl Plugin for RenderPlugin {
200200
.insert_resource(asset_server)
201201
.init_resource::<RenderGraph>();
202202

203+
let (sender, receiver) = bevy_time::create_time_channels();
204+
app.insert_resource(receiver);
205+
render_app.insert_resource(sender);
206+
203207
app.add_sub_app(RenderApp, render_app, move |app_world, render_app| {
204208
#[cfg(feature = "trace")]
205209
let _render_span = bevy_utils::tracing::info_span!("renderer subapp").entered();

crates/bevy_render/src/renderer/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use crate::{
1111
view::{ExtractedWindows, ViewTarget},
1212
};
1313
use bevy_ecs::prelude::*;
14+
use bevy_time::TimeSender;
15+
use bevy_utils::Instant;
1416
use std::sync::Arc;
1517
use wgpu::{AdapterInfo, CommandEncoder, Instance, Queue, RequestAdapterOptions};
1618

@@ -73,6 +75,12 @@ pub fn render_system(world: &mut World) {
7375
tracy.frame_mark = true
7476
);
7577
}
78+
79+
// update the time and send it to the app world
80+
let time_sender = world.resource::<TimeSender>();
81+
time_sender.0.try_send(Instant::now()).expect(
82+
"The TimeSender channel should always be empty during render. You might need to add the bevy::core::time_system to your app.",
83+
);
7684
}
7785

7886
/// This queue is used to enqueue tasks for the GPU to execute asynchronously.

crates/bevy_time/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ bevy_app = { path = "../bevy_app", version = "0.8.0-dev" }
1515
bevy_ecs = { path = "../bevy_ecs", version = "0.8.0-dev", features = ["bevy_reflect"] }
1616
bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", features = ["bevy"] }
1717
bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" }
18+
19+
# other
20+
crossbeam-channel = "0.5.0"

crates/bevy_time/src/lib.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ pub use stopwatch::*;
99
pub use time::*;
1010
pub use timer::*;
1111

12+
use bevy_ecs::system::{Local, Res, ResMut};
13+
use bevy_utils::{tracing::warn, Instant};
14+
use crossbeam_channel::{Receiver, Sender};
15+
1216
pub mod prelude {
1317
//! The Bevy Time Prelude.
1418
#[doc(hidden)]
@@ -41,6 +45,35 @@ impl Plugin for TimePlugin {
4145
}
4246
}
4347

44-
fn time_system(mut time: ResMut<Time>) {
45-
time.update();
48+
/// Channel resource used to receive time from render world
49+
pub struct TimeReceiver(pub Receiver<Instant>);
50+
/// Channel resource used to send time from render world
51+
pub struct TimeSender(pub Sender<Instant>);
52+
53+
/// Creates channels used for sending time between render world and app world
54+
pub fn create_time_channels() -> (TimeSender, TimeReceiver) {
55+
// bound the channel to 2 since when pipelined the render phase can finish before
56+
// the time system runs.
57+
let (s, r) = crossbeam_channel::bounded::<Instant>(2);
58+
(TimeSender(s), TimeReceiver(r))
59+
}
60+
61+
/// The system used to update the [`Time`] used by app logic. If there is a render world the time is sent from
62+
/// there to this system through channels. Otherwise the time is updated in this system.
63+
fn time_system(
64+
mut time: ResMut<Time>,
65+
time_recv: Option<Res<TimeReceiver>>,
66+
mut has_received_time: Local<bool>,
67+
) {
68+
if let Some(time_recv) = time_recv {
69+
// TODO: Figure out how to handle this when using pipelined rendering.
70+
if let Ok(new_time) = time_recv.0.try_recv() {
71+
time.update_with_instant(new_time);
72+
*has_received_time = true;
73+
} else if *has_received_time {
74+
warn!("time_system did not receive the time from the render world! Calculations depending on the time may be incorrect.");
75+
}
76+
} else {
77+
time.update();
78+
}
4679
}

0 commit comments

Comments
 (0)