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};
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#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)]
35pub struct Core3d;
36
37#[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#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)]
74pub struct Core2d;
75
76#[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#[derive(Resource)]
113struct CameraWindows(EntityHashSet);
114
115#[derive(Clone, Copy, Component, Debug, Reflect)]
121#[reflect(Clone, Component)]
122#[reflect(from_reflect = false)]
123pub struct RootNonCameraView(#[reflect(ignore)] pub InternedScheduleLabel);
124
125pub fn camera_driver(world: &mut World) {
134 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
208enum RootView {
210 Camera { entity: Entity, order: isize },
212
213 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}