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