Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/pipelined_rendering.rs
6595 views
1
use async_channel::{Receiver, Sender};
2
3
use bevy_app::{App, AppExit, AppLabel, Plugin, SubApp};
4
use bevy_ecs::{
5
resource::Resource,
6
schedule::MainThreadExecutor,
7
world::{Mut, World},
8
};
9
use bevy_tasks::ComputeTaskPool;
10
11
use crate::RenderApp;
12
13
/// A Label for the sub app that runs the parts of pipelined rendering that need to run on the main thread.
14
///
15
/// The Main schedule of this app can be used to run logic after the render schedule starts, but
16
/// before I/O processing. This can be useful for something like frame pacing.
17
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
18
pub struct RenderExtractApp;
19
20
/// Channels used by the main app to send and receive the render app.
21
#[derive(Resource)]
22
pub struct RenderAppChannels {
23
app_to_render_sender: Sender<SubApp>,
24
render_to_app_receiver: Receiver<SubApp>,
25
render_app_in_render_thread: bool,
26
}
27
28
impl RenderAppChannels {
29
/// Create a `RenderAppChannels` from a [`async_channel::Receiver`] and [`async_channel::Sender`]
30
pub fn new(
31
app_to_render_sender: Sender<SubApp>,
32
render_to_app_receiver: Receiver<SubApp>,
33
) -> Self {
34
Self {
35
app_to_render_sender,
36
render_to_app_receiver,
37
render_app_in_render_thread: false,
38
}
39
}
40
41
/// Send the `render_app` to the rendering thread.
42
pub fn send_blocking(&mut self, render_app: SubApp) {
43
self.app_to_render_sender.send_blocking(render_app).unwrap();
44
self.render_app_in_render_thread = true;
45
}
46
47
/// Receive the `render_app` from the rendering thread.
48
/// Return `None` if the render thread has panicked.
49
pub async fn recv(&mut self) -> Option<SubApp> {
50
let render_app = self.render_to_app_receiver.recv().await.ok()?;
51
self.render_app_in_render_thread = false;
52
Some(render_app)
53
}
54
}
55
56
impl Drop for RenderAppChannels {
57
fn drop(&mut self) {
58
if self.render_app_in_render_thread {
59
// Any non-send data in the render world was initialized on the main thread.
60
// So on dropping the main world and ending the app, we block and wait for
61
// the render world to return to drop it. Which allows the non-send data
62
// drop methods to run on the correct thread.
63
self.render_to_app_receiver.recv_blocking().ok();
64
}
65
}
66
}
67
68
/// The [`PipelinedRenderingPlugin`] can be added to your application to enable pipelined rendering.
69
///
70
/// This moves rendering into a different thread, so that the Nth frame's rendering can
71
/// be run at the same time as the N + 1 frame's simulation.
72
///
73
/// ```text
74
/// |--------------------|--------------------|--------------------|--------------------|
75
/// | simulation thread | frame 1 simulation | frame 2 simulation | frame 3 simulation |
76
/// |--------------------|--------------------|--------------------|--------------------|
77
/// | rendering thread | | frame 1 rendering | frame 2 rendering |
78
/// |--------------------|--------------------|--------------------|--------------------|
79
/// ```
80
///
81
/// The plugin is dependent on the [`RenderApp`] added by [`crate::RenderPlugin`] and so must
82
/// be added after that plugin. If it is not added after, the plugin will do nothing.
83
///
84
/// A single frame of execution looks something like below
85
///
86
/// ```text
87
/// |---------------------------------------------------------------------------|
88
/// | | | RenderExtractApp schedule | winit events | main schedule |
89
/// | sync | extract |----------------------------------------------------------|
90
/// | | | extract commands | rendering schedule |
91
/// |---------------------------------------------------------------------------|
92
/// ```
93
///
94
/// - `sync` is the step where the entity-entity mapping between the main and render world is updated.
95
/// This is run on the main app's thread. For more information checkout [`SyncWorldPlugin`].
96
/// - `extract` is the step where data is copied from the main world to the render world.
97
/// This is run on the main app's thread.
98
/// - On the render thread, we first apply the `extract commands`. This is not run during extract, so the
99
/// main schedule can start sooner.
100
/// - Then the `rendering schedule` is run. See [`RenderSystems`](crate::RenderSystems) for the standard steps in this process.
101
/// - In parallel to the rendering thread the [`RenderExtractApp`] schedule runs. By
102
/// default, this schedule is empty. But it is useful if you need something to run before I/O processing.
103
/// - Next all the `winit events` are processed.
104
/// - And finally the `main app schedule` is run.
105
/// - Once both the `main app schedule` and the `render schedule` are finished running, `extract` is run again.
106
///
107
/// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin
108
#[derive(Default)]
109
pub struct PipelinedRenderingPlugin;
110
111
impl Plugin for PipelinedRenderingPlugin {
112
fn build(&self, app: &mut App) {
113
// Don't add RenderExtractApp if RenderApp isn't initialized.
114
if app.get_sub_app(RenderApp).is_none() {
115
return;
116
}
117
app.insert_resource(MainThreadExecutor::new());
118
119
let mut sub_app = SubApp::new();
120
sub_app.set_extract(renderer_extract);
121
app.insert_sub_app(RenderExtractApp, sub_app);
122
}
123
124
// Sets up the render thread and inserts resources into the main app used for controlling the render thread.
125
fn cleanup(&self, app: &mut App) {
126
// skip setting up when headless
127
if app.get_sub_app(RenderExtractApp).is_none() {
128
return;
129
}
130
131
let (app_to_render_sender, app_to_render_receiver) = async_channel::bounded::<SubApp>(1);
132
let (render_to_app_sender, render_to_app_receiver) = async_channel::bounded::<SubApp>(1);
133
134
let mut render_app = app
135
.remove_sub_app(RenderApp)
136
.expect("Unable to get RenderApp. Another plugin may have removed the RenderApp before PipelinedRenderingPlugin");
137
138
// clone main thread executor to render world
139
let executor = app.world().get_resource::<MainThreadExecutor>().unwrap();
140
render_app.world_mut().insert_resource(executor.clone());
141
142
render_to_app_sender.send_blocking(render_app).unwrap();
143
144
app.insert_resource(RenderAppChannels::new(
145
app_to_render_sender,
146
render_to_app_receiver,
147
));
148
149
std::thread::spawn(move || {
150
#[cfg(feature = "trace")]
151
let _span = tracing::info_span!("render thread").entered();
152
153
let compute_task_pool = ComputeTaskPool::get();
154
loop {
155
// run a scope here to allow main world to use this thread while it's waiting for the render app
156
let sent_app = compute_task_pool
157
.scope(|s| {
158
s.spawn(async { app_to_render_receiver.recv().await });
159
})
160
.pop();
161
let Some(Ok(mut render_app)) = sent_app else {
162
break;
163
};
164
165
{
166
#[cfg(feature = "trace")]
167
let _sub_app_span = tracing::info_span!("sub app", name = ?RenderApp).entered();
168
render_app.update();
169
}
170
171
if render_to_app_sender.send_blocking(render_app).is_err() {
172
break;
173
}
174
}
175
176
tracing::debug!("exiting pipelined rendering thread");
177
});
178
}
179
}
180
181
// This function waits for the rendering world to be received,
182
// runs extract, and then sends the rendering world back to the render thread.
183
fn renderer_extract(app_world: &mut World, _world: &mut World) {
184
app_world.resource_scope(|world, main_thread_executor: Mut<MainThreadExecutor>| {
185
world.resource_scope(|world, mut render_channels: Mut<RenderAppChannels>| {
186
// we use a scope here to run any main thread tasks that the render world still needs to run
187
// while we wait for the render world to be received.
188
if let Some(mut render_app) = ComputeTaskPool::get()
189
.scope_with_executor(true, Some(&*main_thread_executor.0), |s| {
190
s.spawn(async { render_channels.recv().await });
191
})
192
.pop()
193
.unwrap()
194
{
195
render_app.extract(world);
196
197
render_channels.send_blocking(render_app);
198
} else {
199
// Renderer thread panicked
200
world.write_event(AppExit::error());
201
}
202
});
203
});
204
}
205
206