bevy_render/render_graph/
camera_driver_node.rs

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