bevy_core_pipeline/deferred/
node.rs

1use bevy_ecs::{prelude::*, query::QueryItem};
2use bevy_render::experimental::occlusion_culling::OcclusionCulling;
3use bevy_render::render_graph::ViewNode;
4
5use bevy_render::view::{ExtractedView, NoIndirectDrawing};
6use bevy_render::{
7    camera::ExtractedCamera,
8    render_graph::{NodeRunError, RenderGraphContext},
9    render_phase::{TrackedRenderPass, ViewBinnedRenderPhases},
10    render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp},
11    renderer::RenderContext,
12    view::ViewDepthTexture,
13};
14use tracing::error;
15#[cfg(feature = "trace")]
16use tracing::info_span;
17
18use crate::prepass::ViewPrepassTextures;
19
20use super::{AlphaMask3dDeferred, Opaque3dDeferred};
21
22/// The phase of the deferred prepass that draws meshes that were visible last
23/// frame.
24///
25/// If occlusion culling isn't in use, this prepass simply draws all meshes.
26///
27/// Like all prepass nodes, this is inserted before the main pass in the render
28/// graph.
29#[derive(Default)]
30pub struct EarlyDeferredGBufferPrepassNode;
31
32impl ViewNode for EarlyDeferredGBufferPrepassNode {
33    type ViewQuery = <LateDeferredGBufferPrepassNode as ViewNode>::ViewQuery;
34
35    fn run<'w>(
36        &self,
37        graph: &mut RenderGraphContext,
38        render_context: &mut RenderContext<'w>,
39        view_query: QueryItem<'w, Self::ViewQuery>,
40        world: &'w World,
41    ) -> Result<(), NodeRunError> {
42        run_deferred_prepass(
43            graph,
44            render_context,
45            view_query,
46            false,
47            world,
48            "early deferred prepass",
49        )
50    }
51}
52
53/// The phase of the prepass that runs after occlusion culling against the
54/// meshes that were visible last frame.
55///
56/// If occlusion culling isn't in use, this is a no-op.
57///
58/// Like all prepass nodes, this is inserted before the main pass in the render
59/// graph.
60#[derive(Default)]
61pub struct LateDeferredGBufferPrepassNode;
62
63impl ViewNode for LateDeferredGBufferPrepassNode {
64    type ViewQuery = (
65        &'static ExtractedCamera,
66        &'static ExtractedView,
67        &'static ViewDepthTexture,
68        &'static ViewPrepassTextures,
69        Has<OcclusionCulling>,
70        Has<NoIndirectDrawing>,
71    );
72
73    fn run<'w>(
74        &self,
75        graph: &mut RenderGraphContext,
76        render_context: &mut RenderContext<'w>,
77        view_query: QueryItem<'w, Self::ViewQuery>,
78        world: &'w World,
79    ) -> Result<(), NodeRunError> {
80        let (_, _, _, _, occlusion_culling, no_indirect_drawing) = view_query;
81        if !occlusion_culling || no_indirect_drawing {
82            return Ok(());
83        }
84
85        run_deferred_prepass(
86            graph,
87            render_context,
88            view_query,
89            true,
90            world,
91            "late deferred prepass",
92        )
93    }
94}
95
96/// Runs the deferred prepass that draws all meshes to the depth buffer and
97/// G-buffers.
98///
99/// If occlusion culling isn't in use, and a prepass is enabled, then there's
100/// only one prepass. If occlusion culling is in use, then any prepass is split
101/// into two: an *early* prepass and a *late* prepass. The early prepass draws
102/// what was visible last frame, and the last prepass performs occlusion culling
103/// against a conservative hierarchical Z buffer before drawing unoccluded
104/// meshes.
105fn run_deferred_prepass<'w>(
106    graph: &mut RenderGraphContext,
107    render_context: &mut RenderContext<'w>,
108    (camera, extracted_view, view_depth_texture, view_prepass_textures, _, _): QueryItem<
109        'w,
110        <LateDeferredGBufferPrepassNode as ViewNode>::ViewQuery,
111    >,
112    is_late: bool,
113    world: &'w World,
114    label: &'static str,
115) -> Result<(), NodeRunError> {
116    let (Some(opaque_deferred_phases), Some(alpha_mask_deferred_phases)) = (
117        world.get_resource::<ViewBinnedRenderPhases<Opaque3dDeferred>>(),
118        world.get_resource::<ViewBinnedRenderPhases<AlphaMask3dDeferred>>(),
119    ) else {
120        return Ok(());
121    };
122
123    let (Some(opaque_deferred_phase), Some(alpha_mask_deferred_phase)) = (
124        opaque_deferred_phases.get(&extracted_view.retained_view_entity),
125        alpha_mask_deferred_phases.get(&extracted_view.retained_view_entity),
126    ) else {
127        return Ok(());
128    };
129
130    let mut color_attachments = vec![];
131    color_attachments.push(
132        view_prepass_textures
133            .normal
134            .as_ref()
135            .map(|normals_texture| normals_texture.get_attachment()),
136    );
137    color_attachments.push(
138        view_prepass_textures
139            .motion_vectors
140            .as_ref()
141            .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()),
142    );
143
144    // If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors:
145    // Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format.
146    // Firefox: WebGL warning: clearBufferu?[fi]v: This attachment is of type FLOAT, but this function is of type UINT.
147    // Appears to be unsupported: https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.9
148    // For webgl2 we fallback to manually clearing
149    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
150    if !is_late {
151        if let Some(deferred_texture) = &view_prepass_textures.deferred {
152            render_context.command_encoder().clear_texture(
153                &deferred_texture.texture.texture,
154                &bevy_render::render_resource::ImageSubresourceRange::default(),
155            );
156        }
157    }
158
159    color_attachments.push(
160        view_prepass_textures
161            .deferred
162            .as_ref()
163            .map(|deferred_texture| {
164                if is_late {
165                    deferred_texture.get_attachment()
166                } else {
167                    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
168                    {
169                        bevy_render::render_resource::RenderPassColorAttachment {
170                            view: &deferred_texture.texture.default_view,
171                            resolve_target: None,
172                            ops: bevy_render::render_resource::Operations {
173                                load: bevy_render::render_resource::LoadOp::Load,
174                                store: StoreOp::Store,
175                            },
176                        }
177                    }
178                    #[cfg(any(
179                        not(feature = "webgl"),
180                        not(target_arch = "wasm32"),
181                        feature = "webgpu"
182                    ))]
183                    deferred_texture.get_attachment()
184                }
185            }),
186    );
187
188    color_attachments.push(
189        view_prepass_textures
190            .deferred_lighting_pass_id
191            .as_ref()
192            .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()),
193    );
194
195    // If all color attachments are none: clear the color attachment list so that no fragment shader is required
196    if color_attachments.iter().all(Option::is_none) {
197        color_attachments.clear();
198    }
199
200    let depth_stencil_attachment = Some(view_depth_texture.get_attachment(StoreOp::Store));
201
202    let view_entity = graph.view_entity();
203    render_context.add_command_buffer_generation_task(move |render_device| {
204        #[cfg(feature = "trace")]
205        let _deferred_span = info_span!("deferred_prepass").entered();
206
207        // Command encoder setup
208        let mut command_encoder = render_device.create_command_encoder(&CommandEncoderDescriptor {
209            label: Some("deferred_prepass_command_encoder"),
210        });
211
212        // Render pass setup
213        let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
214            label: Some(label),
215            color_attachments: &color_attachments,
216            depth_stencil_attachment,
217            timestamp_writes: None,
218            occlusion_query_set: None,
219        });
220        let mut render_pass = TrackedRenderPass::new(&render_device, render_pass);
221        if let Some(viewport) = camera.viewport.as_ref() {
222            render_pass.set_camera_viewport(viewport);
223        }
224
225        // Opaque draws
226        if !opaque_deferred_phase.multidrawable_meshes.is_empty()
227            || !opaque_deferred_phase.batchable_meshes.is_empty()
228            || !opaque_deferred_phase.unbatchable_meshes.is_empty()
229        {
230            #[cfg(feature = "trace")]
231            let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered();
232            if let Err(err) = opaque_deferred_phase.render(&mut render_pass, world, view_entity) {
233                error!("Error encountered while rendering the opaque deferred phase {err:?}");
234            }
235        }
236
237        // Alpha masked draws
238        if !alpha_mask_deferred_phase.is_empty() {
239            #[cfg(feature = "trace")]
240            let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred_prepass").entered();
241            if let Err(err) = alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity)
242            {
243                error!("Error encountered while rendering the alpha mask deferred phase {err:?}");
244            }
245        }
246
247        drop(render_pass);
248
249        // After rendering to the view depth texture, copy it to the prepass depth texture
250        if let Some(prepass_depth_texture) = &view_prepass_textures.depth {
251            command_encoder.copy_texture_to_texture(
252                view_depth_texture.texture.as_image_copy(),
253                prepass_depth_texture.texture.texture.as_image_copy(),
254                view_prepass_textures.size,
255            );
256        }
257
258        command_encoder.finish()
259    });
260
261    Ok(())
262}