bevy_render/camera/
camera_driver_node.rs

1use crate::{
2    camera::{ExtractedCamera, NormalizedRenderTarget, SortedCameras},
3    render_graph::{Node, NodeRunError, RenderGraphContext},
4    renderer::RenderContext,
5    view::ExtractedWindows,
6};
7use bevy_ecs::{prelude::QueryState, world::World};
8use bevy_utils::HashSet;
9use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp};
10
11pub struct CameraDriverNode {
12    cameras: QueryState<&'static ExtractedCamera>,
13}
14
15impl CameraDriverNode {
16    pub fn new(world: &mut World) -> Self {
17        Self {
18            cameras: world.query(),
19        }
20    }
21}
22
23impl Node for CameraDriverNode {
24    fn update(&mut self, world: &mut World) {
25        self.cameras.update_archetypes(world);
26    }
27    fn run(
28        &self,
29        graph: &mut RenderGraphContext,
30        render_context: &mut RenderContext,
31        world: &World,
32    ) -> Result<(), NodeRunError> {
33        let sorted_cameras = world.resource::<SortedCameras>();
34        let windows = world.resource::<ExtractedWindows>();
35        let mut camera_windows = HashSet::new();
36        for sorted_camera in &sorted_cameras.0 {
37            let Ok(camera) = self.cameras.get_manual(world, sorted_camera.entity) else {
38                continue;
39            };
40
41            let mut run_graph = true;
42            if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.target {
43                let window_entity = window_ref.entity();
44                if windows.windows.get(&window_entity).is_some() {
45                    camera_windows.insert(window_entity);
46                } else {
47                    // The window doesn't exist anymore so we don't need to run the graph
48                    run_graph = false;
49                }
50            }
51            if run_graph {
52                graph.run_sub_graph(camera.render_graph, vec![], Some(sorted_camera.entity))?;
53            }
54        }
55
56        // wgpu (and some backends) require doing work for swap chains if you call `get_current_texture()` and `present()`
57        // This ensures that Bevy doesn't crash, even when there are no cameras (and therefore no work submitted).
58        for (id, window) in world.resource::<ExtractedWindows>().iter() {
59            if camera_windows.contains(id) {
60                continue;
61            }
62
63            let Some(swap_chain_texture) = &window.swap_chain_texture_view else {
64                continue;
65            };
66
67            #[cfg(feature = "trace")]
68            let _span = bevy_utils::tracing::info_span!("no_camera_clear_pass").entered();
69            let pass_descriptor = RenderPassDescriptor {
70                label: Some("no_camera_clear_pass"),
71                color_attachments: &[Some(RenderPassColorAttachment {
72                    view: swap_chain_texture,
73                    resolve_target: None,
74                    ops: Operations {
75                        load: LoadOp::Clear(wgpu::Color::BLACK),
76                        store: StoreOp::Store,
77                    },
78                })],
79                depth_stencil_attachment: None,
80                timestamp_writes: None,
81                occlusion_query_set: None,
82            };
83
84            render_context
85                .command_encoder()
86                .begin_render_pass(&pass_descriptor);
87        }
88
89        Ok(())
90    }
91}