bevy_render/camera/
camera_driver_node.rs

1use crate::{
2    camera::{ClearColor, 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
45                    .windows
46                    .get(&window_entity)
47                    .is_some_and(|w| w.physical_width > 0 && w.physical_height > 0)
48                {
49                    camera_windows.insert(window_entity);
50                } else {
51                    // The window doesn't exist anymore or zero-sized so we don't need to run the graph
52                    run_graph = false;
53                }
54            }
55            if run_graph {
56                graph.run_sub_graph(camera.render_graph, vec![], Some(sorted_camera.entity))?;
57            }
58        }
59
60        let clear_color_global = world.resource::<ClearColor>();
61
62        // wgpu (and some backends) require doing work for swap chains if you call `get_current_texture()` and `present()`
63        // This ensures that Bevy doesn't crash, even when there are no cameras (and therefore no work submitted).
64        for (id, window) in world.resource::<ExtractedWindows>().iter() {
65            if camera_windows.contains(id) {
66                continue;
67            }
68
69            let Some(swap_chain_texture) = &window.swap_chain_texture_view else {
70                continue;
71            };
72
73            #[cfg(feature = "trace")]
74            let _span = bevy_utils::tracing::info_span!("no_camera_clear_pass").entered();
75            let pass_descriptor = RenderPassDescriptor {
76                label: Some("no_camera_clear_pass"),
77                color_attachments: &[Some(RenderPassColorAttachment {
78                    view: swap_chain_texture,
79                    resolve_target: None,
80                    ops: Operations {
81                        load: LoadOp::Clear(clear_color_global.to_linear().into()),
82                        store: StoreOp::Store,
83                    },
84                })],
85                depth_stencil_attachment: None,
86                timestamp_writes: None,
87                occlusion_query_set: None,
88            };
89
90            render_context
91                .command_encoder()
92                .begin_render_pass(&pass_descriptor);
93        }
94
95        Ok(())
96    }
97}