1use 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 system::SystemState,
20};
21#[cfg(feature = "trace")]
22use bevy_log::info_span;
23use bevy_reflect::Reflect;
24use bevy_render::{
25 camera::{ExtractedCamera, SortedCameras},
26 diagnostic::{DiagnosticsRecorder, RecordDiagnostics},
27 render_resource::{
28 CommandEncoderDescriptor, LoadOp, Operations, RenderPassColorAttachment,
29 RenderPassDescriptor, StoreOp,
30 },
31 renderer::{CurrentView, PendingCommandBuffers, RenderDevice, RenderQueue},
32 sync_world::MainEntity,
33 view::ExtractedWindow,
34};
35
36#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)]
38pub struct Core3d;
39
40#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
50pub enum Core3dSystems {
51 Prepass,
52 MainPass,
53 EarlyPostProcess,
54 PostProcess,
55}
56
57impl Core3d {
58 pub fn base_schedule() -> Schedule {
59 use bevy_ecs::schedule::ScheduleBuildSettings;
60 use Core3dSystems::*;
61
62 let mut schedule = Schedule::new(Self);
63
64 schedule.set_build_settings(ScheduleBuildSettings {
65 auto_insert_apply_deferred: false,
66 ..Default::default()
67 });
68
69 schedule.configure_sets((Prepass, MainPass, EarlyPostProcess, PostProcess).chain_weak());
70
71 schedule
72 }
73}
74
75#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)]
77pub struct Core2d;
78
79#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
89pub enum Core2dSystems {
90 Prepass,
91 MainPass,
92 EarlyPostProcess,
93 PostProcess,
94}
95
96impl Core2d {
97 pub fn base_schedule() -> Schedule {
98 use bevy_ecs::schedule::ScheduleBuildSettings;
99 use Core2dSystems::*;
100
101 let mut schedule = Schedule::new(Self);
102
103 schedule.set_build_settings(ScheduleBuildSettings {
104 auto_insert_apply_deferred: false,
105 ..Default::default()
106 });
107
108 schedule.configure_sets((Prepass, MainPass, EarlyPostProcess, PostProcess).chain_weak());
109
110 schedule
111 }
112}
113
114#[derive(Resource)]
116struct CameraWindows(EntityHashSet);
117
118#[derive(Clone, Copy, Component, Debug, Reflect)]
124#[reflect(Clone, Component)]
125#[reflect(from_reflect = false)]
126pub struct RootNonCameraView(#[reflect(ignore)] pub InternedScheduleLabel);
127
128pub fn camera_driver(
137 world: &mut World,
138 state: &mut SystemState<Query<(MainEntity, &ExtractedWindow)>>,
139) {
140 let root_views: Vec<_> = {
142 let mut auxiliary_views = world.query_filtered::<Entity, With<RootNonCameraView>>();
143 let sorted = world.resource::<SortedCameras>();
144 auxiliary_views
145 .iter(world)
146 .map(RootView::Auxiliary)
147 .chain(sorted.0.iter().map(|c| RootView::Camera {
148 entity: c.entity,
149 order: c.order,
150 }))
151 .collect()
152 };
153
154 let mut camera_windows = EntityHashSet::default();
155
156 let diagnostics_enabled = world.contains_resource::<DiagnosticsRecorder>();
157 let mut reached_root_camera = false;
158 if diagnostics_enabled {
159 let device = world.resource::<RenderDevice>().clone();
160 let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor {
161 label: Some("camera_driver_diagnostic_span"),
162 });
163 world
164 .resource::<DiagnosticsRecorder>()
165 .begin_time_span(&mut encoder, "auxiliary_views".into());
166 world
167 .resource_mut::<PendingCommandBuffers>()
168 .push_encoder(encoder, "camera_driver_span_begin");
169 }
170 let end_time_span = |world: &mut World| {
171 let device = world.resource::<RenderDevice>().clone();
172 let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor {
173 label: Some("camera_driver_diagnostic_span"),
174 });
175 world
176 .resource::<DiagnosticsRecorder>()
177 .end_time_span(&mut encoder);
178 world
179 .resource_mut::<PendingCommandBuffers>()
180 .push_encoder(encoder, "camera_driver_span_end");
181 };
182
183 for root_view in root_views {
184 let mut run_schedule = true;
185 let (schedule, view_entity);
186
187 match root_view {
188 RootView::Camera {
189 entity: camera_entity,
190 ..
191 } => {
192 if diagnostics_enabled && !reached_root_camera {
193 reached_root_camera = true;
194 end_time_span(world);
195 }
196 let Some(camera) = world.get::<ExtractedCamera>(camera_entity) else {
197 continue;
198 };
199
200 schedule = camera.schedule;
201 let target = camera.target.clone();
202
203 if let Some(NormalizedRenderTarget::Window(window_ref)) = &target {
204 let window_entity = window_ref.entity();
205
206 if let Ok(windows) = state.get(world)
207 && windows
208 .iter()
209 .find(|(e, _)| *e == window_entity)
210 .is_some_and(|(_, w)| w.physical_width > 0 && w.physical_height > 0)
211 {
212 camera_windows.insert(window_entity);
213 } else {
214 run_schedule = false;
215 }
216 }
217
218 view_entity = camera_entity;
219 }
220
221 RootView::Auxiliary(auxiliary_view_entity) => {
222 let Some(root_view) = world.get::<RootNonCameraView>(auxiliary_view_entity) else {
223 continue;
224 };
225
226 view_entity = auxiliary_view_entity;
227 schedule = root_view.0;
228 }
229 }
230
231 if run_schedule {
232 world.insert_resource(CurrentView(view_entity));
233
234 #[cfg(feature = "trace")]
235 let _span =
236 bevy_log::info_span!("camera_schedule", camera = root_view.to_string()).entered();
237
238 world.run_schedule(schedule);
239 }
240 }
241 if diagnostics_enabled && !reached_root_camera {
242 end_time_span(world);
243 }
244
245 world.remove_resource::<CurrentView>();
246
247 world.insert_resource(CameraWindows(camera_windows));
248}
249
250enum RootView {
252 Camera { entity: Entity, order: isize },
254
255 Auxiliary(Entity),
259}
260
261impl Display for RootView {
262 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
263 match *self {
264 RootView::Camera { entity, order } => write!(f, "Camera {} ({:?})", order, entity),
265 RootView::Auxiliary(entity) => write!(f, "Auxiliary View {:?}", entity),
266 }
267 }
268}
269
270pub(crate) fn submit_pending_command_buffers(
271 world: &mut World,
272 state: &mut SystemState<(ResMut<PendingCommandBuffers>, Res<RenderQueue>)>,
273) {
274 let (mut pending, queue) = state.get_mut(world).unwrap();
275 #[cfg(feature = "trace")]
276 let buffer_count = pending.len();
277 let mut buffers = pending.finish().peekable();
278
279 if buffers.peek().is_some() {
280 #[cfg(feature = "trace")]
281 let _span = info_span!("queue_submit", count = buffer_count).entered();
282 queue.submit(buffers);
283 }
284}
285
286pub(crate) fn handle_uncovered_swap_chains(world: &mut World) {
287 let windows_to_clear: Vec<_> = {
288 let clear_color = world.resource::<ClearColor>().0.to_linear();
289 let Some(camera_windows) = world.remove_resource::<CameraWindows>() else {
290 return;
291 };
292
293 world
294 .query::<(MainEntity, &ExtractedWindow)>()
295 .iter(world)
296 .filter_map(|(window_entity, window)| {
297 if camera_windows.0.contains(&window_entity.entity()) {
298 return None;
299 }
300 let swap_chain_texture = window.swap_chain_texture_view.as_ref()?;
301 Some((swap_chain_texture.clone(), clear_color))
302 })
303 .collect()
304 };
305
306 if windows_to_clear.is_empty() {
307 return;
308 }
309
310 let render_device = world.resource::<RenderDevice>();
311 let render_queue = world.resource::<RenderQueue>();
312
313 let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor::default());
314
315 for (swap_chain_texture, clear_color) in &windows_to_clear {
316 #[cfg(feature = "trace")]
317 let _span = bevy_log::info_span!("no_camera_clear_pass").entered();
318
319 let pass_descriptor = RenderPassDescriptor {
320 label: Some("no_camera_clear_pass"),
321 color_attachments: &[Some(RenderPassColorAttachment {
322 view: swap_chain_texture,
323 depth_slice: None,
324 resolve_target: None,
325 ops: Operations {
326 load: LoadOp::Clear((*clear_color).into()),
327 store: StoreOp::Store,
328 },
329 })],
330 depth_stencil_attachment: None,
331 timestamp_writes: None,
332 occlusion_query_set: None,
333 multiview_mask: None,
334 };
335
336 encoder.begin_render_pass(&pass_descriptor);
337 }
338
339 render_queue.submit([encoder.finish()]);
340}