Skip to main content

bevy_core_pipeline/
schedule.rs

1//! The core rendering pipelines schedules. These schedules define the "default" render graph
2//! for 2D and 3D rendering in Bevy.
3//!
4//! Rendering in Bevy is "camera driven", meaning that for each camera in the world, its
5//! associated rendering schedule is executed. This allows different cameras to have different
6//! rendering pipelines, for example a 3D camera with post-processing effects and a 2D camera
7//! with a simple clear and sprite rendering.
8//!
9//! The [`camera_driver`] system is responsible for iterating over all cameras in the world
10//! and executing their associated schedules. In this way, the schedule for each camera is a
11//! sub-schedule or sub-graph of the root render graph schedule.
12use core::fmt::{self, Display, Formatter};
13
14use bevy_camera::{ClearColor, NormalizedRenderTarget};
15use bevy_ecs::{
16    entity::EntityHashSet,
17    prelude::*,
18    schedule::{InternedScheduleLabel, IntoScheduleConfigs, Schedule, ScheduleLabel, SystemSet},
19};
20#[cfg(feature = "trace")]
21use bevy_log::info_span;
22use bevy_reflect::Reflect;
23use bevy_render::{
24    camera::{ExtractedCamera, SortedCameras},
25    render_resource::{
26        CommandEncoderDescriptor, LoadOp, Operations, RenderPassColorAttachment,
27        RenderPassDescriptor, StoreOp,
28    },
29    renderer::{CurrentView, PendingCommandBuffers, RenderDevice, RenderQueue},
30    view::ExtractedWindows,
31};
32
33/// Schedule label for the Core 3D rendering pipeline.
34#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)]
35pub struct Core3d;
36
37/// System sets for the Core 3D rendering pipeline, defining the main stages of rendering.
38/// These stages include and run in the following order:
39/// - `Prepass`: Initial rendering operations, such as depth pre-pass.
40/// - `MainPass`: The primary rendering operations, including drawing opaque and transparent objects.
41/// - `EarlyPostProcess`: Initial post processing effects.
42/// - `PostProcess`: Final rendering operations, such as post-processing effects.
43///
44/// Additional systems can be added to these sets to customize the rendering pipeline, or additional
45/// sets can be created relative to these core sets.
46#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
47pub enum Core3dSystems {
48    Prepass,
49    MainPass,
50    EarlyPostProcess,
51    PostProcess,
52}
53
54impl Core3d {
55    pub fn base_schedule() -> Schedule {
56        use bevy_ecs::schedule::ScheduleBuildSettings;
57        use Core3dSystems::*;
58
59        let mut schedule = Schedule::new(Self);
60
61        schedule.set_build_settings(ScheduleBuildSettings {
62            auto_insert_apply_deferred: false,
63            ..Default::default()
64        });
65
66        schedule.configure_sets((Prepass, MainPass, EarlyPostProcess, PostProcess).chain());
67
68        schedule
69    }
70}
71
72/// Schedule label for the Core 2D rendering pipeline.
73#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)]
74pub struct Core2d;
75
76/// System sets for the Core 2D rendering pipeline, defining the main stages of rendering.
77/// These stages include and run in the following order:
78/// - `Prepass`: Initial rendering operations, such as depth pre-pass.
79/// - `MainPass`: The primary rendering operations, including drawing 2D sprites and meshes.
80/// - `EarlyPostProcess`: Initial post processing effects.
81/// - `PostProcess`: Final rendering operations, such as post-processing effects.
82///
83/// Additional systems can be added to these sets to customize the rendering pipeline, or additional
84/// sets can be created relative to these core sets.
85#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
86pub enum Core2dSystems {
87    Prepass,
88    MainPass,
89    EarlyPostProcess,
90    PostProcess,
91}
92
93impl Core2d {
94    pub fn base_schedule() -> Schedule {
95        use bevy_ecs::schedule::ScheduleBuildSettings;
96        use Core2dSystems::*;
97
98        let mut schedule = Schedule::new(Self);
99
100        schedule.set_build_settings(ScheduleBuildSettings {
101            auto_insert_apply_deferred: false,
102            ..Default::default()
103        });
104
105        schedule.configure_sets((Prepass, MainPass, EarlyPostProcess, PostProcess).chain());
106
107        schedule
108    }
109}
110
111/// Holds the entity of windows that are a render target for a camera
112#[derive(Resource)]
113struct CameraWindows(EntityHashSet);
114
115/// A render-world marker component for a view that corresponds to neither a
116/// camera nor a camera-associated shadow map.
117///
118/// This is used for point light and spot light shadow maps, since these aren't
119/// associated with views.
120#[derive(Clone, Copy, Component, Debug, Reflect)]
121#[reflect(Clone, Component)]
122#[reflect(from_reflect = false)]
123pub struct RootNonCameraView(#[reflect(ignore)] pub InternedScheduleLabel);
124
125/// The default entry point for camera driven rendering added to the root [`bevy_render::renderer::RenderGraph`]
126/// schedule. This system iterates over all cameras in the world, executing their associated
127/// rendering schedules defined by the [`bevy_render::camera::CameraRenderGraph`] component.
128///
129/// After executing all camera schedules, it submits any pending command buffers to the GPU
130/// and clears any swap chains that were not covered by a camera. Users can order any additional
131/// operations (e.g. one-off compute passes) before or after this system in the root render
132/// graph schedule.
133pub fn camera_driver(world: &mut World) {
134    // Gather up all cameras and auxiliary views not associated with a camera.
135    let root_views: Vec<_> = {
136        let mut auxiliary_views = world.query_filtered::<Entity, With<RootNonCameraView>>();
137        let sorted = world.resource::<SortedCameras>();
138        auxiliary_views
139            .iter(world)
140            .map(RootView::Auxiliary)
141            .chain(sorted.0.iter().map(|c| RootView::Camera {
142                entity: c.entity,
143                order: c.order,
144            }))
145            .collect()
146    };
147
148    let mut camera_windows = EntityHashSet::default();
149
150    for root_view in root_views {
151        let mut run_schedule = true;
152        let (schedule, view_entity);
153
154        match root_view {
155            RootView::Camera {
156                entity: camera_entity,
157                ..
158            } => {
159                let Some(camera) = world.get::<ExtractedCamera>(camera_entity) else {
160                    continue;
161                };
162
163                schedule = camera.schedule;
164                let target = camera.target.clone();
165
166                if let Some(NormalizedRenderTarget::Window(window_ref)) = &target {
167                    let window_entity = window_ref.entity();
168                    let windows = world.resource::<ExtractedWindows>();
169                    if windows
170                        .windows
171                        .get(&window_entity)
172                        .is_some_and(|w| w.physical_width > 0 && w.physical_height > 0)
173                    {
174                        camera_windows.insert(window_entity);
175                    } else {
176                        run_schedule = false;
177                    }
178                }
179
180                view_entity = camera_entity;
181            }
182
183            RootView::Auxiliary(auxiliary_view_entity) => {
184                let Some(root_view) = world.get::<RootNonCameraView>(auxiliary_view_entity) else {
185                    continue;
186                };
187
188                view_entity = auxiliary_view_entity;
189                schedule = root_view.0;
190            }
191        }
192
193        if run_schedule {
194            world.insert_resource(CurrentView(view_entity));
195
196            #[cfg(feature = "trace")]
197            let _span =
198                bevy_log::info_span!("camera_schedule", camera = root_view.to_string()).entered();
199
200            world.run_schedule(schedule);
201        }
202    }
203    world.remove_resource::<CurrentView>();
204
205    world.insert_resource(CameraWindows(camera_windows));
206}
207
208/// A view not associated with any other camera.
209enum RootView {
210    /// A camera.
211    Camera { entity: Entity, order: isize },
212
213    /// An auxiliary view not associated with a camera.
214    ///
215    /// This is currently used for point and spot light shadow maps.
216    Auxiliary(Entity),
217}
218
219impl Display for RootView {
220    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
221        match *self {
222            RootView::Camera { entity, order } => write!(f, "Camera {} ({:?})", order, entity),
223            RootView::Auxiliary(entity) => write!(f, "Auxiliary View {:?}", entity),
224        }
225    }
226}
227
228pub(crate) fn submit_pending_command_buffers(world: &mut World) {
229    let mut pending = world.resource_mut::<PendingCommandBuffers>();
230    #[cfg(feature = "trace")]
231    let buffer_count = pending.len();
232    let buffers = pending.take();
233
234    if !buffers.is_empty() {
235        #[cfg(feature = "trace")]
236        let _span = info_span!("queue_submit", count = buffer_count).entered();
237        let queue = world.resource::<RenderQueue>();
238        queue.submit(buffers);
239    }
240}
241
242pub(crate) fn handle_uncovered_swap_chains(world: &mut World) {
243    let windows_to_clear: Vec<_> = {
244        let clear_color = world.resource::<ClearColor>().0.to_linear();
245        let Some(camera_windows) = world.remove_resource::<CameraWindows>() else {
246            return;
247        };
248        let windows = world.resource::<ExtractedWindows>();
249        windows
250            .iter()
251            .filter_map(|(window_entity, window)| {
252                if camera_windows.0.contains(window_entity) {
253                    return None;
254                }
255                let swap_chain_texture = window.swap_chain_texture_view.as_ref()?;
256                Some((swap_chain_texture.clone(), clear_color))
257            })
258            .collect()
259    };
260
261    if windows_to_clear.is_empty() {
262        return;
263    }
264
265    let render_device = world.resource::<RenderDevice>();
266    let render_queue = world.resource::<RenderQueue>();
267
268    let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor::default());
269
270    for (swap_chain_texture, clear_color) in &windows_to_clear {
271        #[cfg(feature = "trace")]
272        let _span = bevy_log::info_span!("no_camera_clear_pass").entered();
273
274        let pass_descriptor = RenderPassDescriptor {
275            label: Some("no_camera_clear_pass"),
276            color_attachments: &[Some(RenderPassColorAttachment {
277                view: swap_chain_texture,
278                depth_slice: None,
279                resolve_target: None,
280                ops: Operations {
281                    load: LoadOp::Clear((*clear_color).into()),
282                    store: StoreOp::Store,
283                },
284            })],
285            depth_stencil_attachment: None,
286            timestamp_writes: None,
287            occlusion_query_set: None,
288            multiview_mask: None,
289        };
290
291        encoder.begin_render_pass(&pass_descriptor);
292    }
293
294    render_queue.submit([encoder.finish()]);
295}