Skip to main content

bevy_core_pipeline/deferred/
node.rs

1use bevy_camera::{MainPassResolutionOverride, Viewport};
2use bevy_ecs::prelude::*;
3use bevy_render::occlusion_culling::OcclusionCulling;
4
5use bevy_log::error;
6#[cfg(feature = "trace")]
7use bevy_log::info_span;
8use bevy_render::view::{ExtractedView, NoIndirectDrawing};
9use bevy_render::{
10    camera::ExtractedCamera,
11    diagnostic::RecordDiagnostics,
12    render_phase::ViewBinnedRenderPhases,
13    render_resource::{RenderPassDescriptor, StoreOp},
14    renderer::{RenderContext, ViewQuery},
15    view::ViewDepthTexture,
16};
17
18use crate::prepass::ViewPrepassTextures;
19
20use super::{AlphaMask3dDeferred, Opaque3dDeferred};
21
22/// Type alias for the deferred prepass view query.
23type DeferredPrepassViewQueryData = (
24    &'static ExtractedCamera,
25    &'static ExtractedView,
26    &'static ViewDepthTexture,
27    &'static ViewPrepassTextures,
28    Option<&'static MainPassResolutionOverride>,
29    Has<OcclusionCulling>,
30    Has<NoIndirectDrawing>,
31);
32
33pub(crate) fn early_deferred_prepass(
34    world: &World,
35    view: ViewQuery<DeferredPrepassViewQueryData>,
36    opaque_deferred_phases: Res<ViewBinnedRenderPhases<Opaque3dDeferred>>,
37    alpha_mask_deferred_phases: Res<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
38    mut ctx: RenderContext,
39) {
40    let view_entity = view.entity();
41    let (
42        camera,
43        extracted_view,
44        view_depth_texture,
45        view_prepass_textures,
46        resolution_override,
47        _,
48        _,
49    ) = view.into_inner();
50
51    run_deferred_prepass_system(
52        world,
53        view_entity,
54        camera,
55        extracted_view,
56        view_depth_texture,
57        view_prepass_textures,
58        resolution_override,
59        false,
60        &opaque_deferred_phases,
61        &alpha_mask_deferred_phases,
62        &mut ctx,
63        "early deferred prepass",
64    );
65}
66
67pub fn late_deferred_prepass(
68    world: &World,
69    view: ViewQuery<DeferredPrepassViewQueryData>,
70    opaque_deferred_phases: Res<ViewBinnedRenderPhases<Opaque3dDeferred>>,
71    alpha_mask_deferred_phases: Res<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
72    mut ctx: RenderContext,
73) {
74    let view_entity = view.entity();
75    let (
76        camera,
77        extracted_view,
78        view_depth_texture,
79        view_prepass_textures,
80        resolution_override,
81        occlusion_culling,
82        no_indirect_drawing,
83    ) = view.into_inner();
84
85    if !occlusion_culling || no_indirect_drawing {
86        return;
87    }
88
89    run_deferred_prepass_system(
90        world,
91        view_entity,
92        camera,
93        extracted_view,
94        view_depth_texture,
95        view_prepass_textures,
96        resolution_override,
97        true,
98        &opaque_deferred_phases,
99        &alpha_mask_deferred_phases,
100        &mut ctx,
101        "late deferred prepass",
102    );
103}
104
105#[expect(
106    clippy::too_many_arguments,
107    reason = "render system with many view components"
108)]
109fn run_deferred_prepass_system(
110    world: &World,
111    view_entity: Entity,
112    camera: &ExtractedCamera,
113    extracted_view: &ExtractedView,
114    view_depth_texture: &ViewDepthTexture,
115    view_prepass_textures: &ViewPrepassTextures,
116    resolution_override: Option<&MainPassResolutionOverride>,
117    is_late: bool,
118    opaque_deferred_phases: &ViewBinnedRenderPhases<Opaque3dDeferred>,
119    alpha_mask_deferred_phases: &ViewBinnedRenderPhases<AlphaMask3dDeferred>,
120    ctx: &mut RenderContext,
121    label: &'static str,
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;
128    };
129
130    #[cfg(feature = "trace")]
131    let _deferred_span = info_span!("deferred_prepass").entered();
132
133    let diagnostics = ctx.diagnostic_recorder();
134    let diagnostics = diagnostics.as_deref();
135
136    let mut color_attachments = vec![];
137    color_attachments.push(
138        view_prepass_textures
139            .normal
140            .as_ref()
141            .map(|normals_texture| normals_texture.get_attachment()),
142    );
143    color_attachments.push(
144        view_prepass_textures
145            .motion_vectors
146            .as_ref()
147            .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()),
148    );
149
150    // If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors:
151    // Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format.
152    // Firefox: WebGL warning: clearBufferu?[fi]v: This attachment is of type FLOAT, but this function is of type UINT.
153    // Appears to be unsupported: https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.9
154    // For webgl2 we fallback to manually clearing
155    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
156    if !is_late {
157        if let Some(deferred_texture) = &view_prepass_textures.deferred {
158            ctx.command_encoder().clear_texture(
159                &deferred_texture.texture.texture,
160                &bevy_render::render_resource::ImageSubresourceRange::default(),
161            );
162        }
163    }
164
165    color_attachments.push(
166        view_prepass_textures
167            .deferred
168            .as_ref()
169            .map(|deferred_texture| {
170                if is_late {
171                    deferred_texture.get_attachment()
172                } else {
173                    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
174                    {
175                        bevy_render::render_resource::RenderPassColorAttachment {
176                            view: &deferred_texture.texture.default_view,
177                            resolve_target: None,
178                            ops: bevy_render::render_resource::Operations {
179                                load: bevy_render::render_resource::LoadOp::Load,
180                                store: StoreOp::Store,
181                            },
182                            depth_slice: None,
183                        }
184                    }
185                    #[cfg(any(
186                        not(feature = "webgl"),
187                        not(target_arch = "wasm32"),
188                        feature = "webgpu"
189                    ))]
190                    deferred_texture.get_attachment()
191                }
192            }),
193    );
194
195    color_attachments.push(
196        view_prepass_textures
197            .deferred_lighting_pass_id
198            .as_ref()
199            .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()),
200    );
201
202    // If all color attachments are none: clear the color attachment list so that no fragment shader is required
203    if color_attachments.iter().all(Option::is_none) {
204        color_attachments.clear();
205    }
206
207    let depth_stencil_attachment = Some(view_depth_texture.get_attachment(StoreOp::Store));
208
209    let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
210        label: Some(label),
211        color_attachments: &color_attachments,
212        depth_stencil_attachment,
213        timestamp_writes: None,
214        occlusion_query_set: None,
215        multiview_mask: None,
216    });
217    let pass_span = diagnostics.pass_span(&mut render_pass, label);
218
219    if let Some(viewport) =
220        Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override)
221    {
222        render_pass.set_camera_viewport(&viewport);
223    }
224
225    if !opaque_deferred_phase.multidrawable_meshes.is_empty()
226        || !opaque_deferred_phase.batchable_meshes.is_empty()
227        || !opaque_deferred_phase.unbatchable_meshes.is_empty()
228    {
229        #[cfg(feature = "trace")]
230        let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered();
231        if let Err(err) = opaque_deferred_phase.render(&mut render_pass, world, view_entity) {
232            error!("Error encountered while rendering the opaque deferred phase {err:?}");
233        }
234    }
235
236    if !alpha_mask_deferred_phase.is_empty() {
237        #[cfg(feature = "trace")]
238        let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred_prepass").entered();
239        if let Err(err) = alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity) {
240            error!("Error encountered while rendering the alpha mask deferred phase {err:?}");
241        }
242    }
243
244    pass_span.end(&mut render_pass);
245    drop(render_pass);
246
247    // After rendering to the view depth texture, copy it to the prepass depth texture
248    if let Some(prepass_depth_texture) = &view_prepass_textures.depth {
249        ctx.command_encoder().copy_texture_to_texture(
250            view_depth_texture.texture.as_image_copy(),
251            prepass_depth_texture.texture.texture.as_image_copy(),
252            view_prepass_textures.size,
253        );
254    }
255}