bevy_core_pipeline/core_3d/
mod.rs

1mod camera_3d;
2mod main_opaque_pass_3d_node;
3mod main_transmissive_pass_3d_node;
4mod main_transparent_pass_3d_node;
5
6pub mod graph {
7    use bevy_render::render_graph::{RenderLabel, RenderSubGraph};
8
9    #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderSubGraph)]
10    pub struct Core3d;
11
12    pub mod input {
13        pub const VIEW_ENTITY: &str = "view_entity";
14    }
15
16    #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
17    pub enum Node3d {
18        MsaaWriteback,
19        Prepass,
20        DeferredPrepass,
21        CopyDeferredLightingId,
22        EndPrepasses,
23        StartMainPass,
24        MainOpaquePass,
25        MainTransmissivePass,
26        MainTransparentPass,
27        EndMainPass,
28        Taa,
29        MotionBlur,
30        Bloom,
31        AutoExposure,
32        DepthOfField,
33        PostProcessing,
34        Tonemapping,
35        Fxaa,
36        Smaa,
37        Upscaling,
38        ContrastAdaptiveSharpening,
39        EndMainPassPostProcessing,
40    }
41}
42
43// PERF: vulkan docs recommend using 24 bit depth for better performance
44pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
45
46/// True if multisampled depth textures are supported on this platform.
47///
48/// In theory, Naga supports depth textures on WebGL 2. In practice, it doesn't,
49/// because of a silly bug whereby Naga assumes that all depth textures are
50/// `sampler2DShadow` and will cheerfully generate invalid GLSL that tries to
51/// perform non-percentage-closer-filtering with such a sampler. Therefore we
52/// disable depth of field and screen space reflections entirely on WebGL 2.
53#[cfg(not(any(feature = "webgpu", not(target_arch = "wasm32"))))]
54pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = false;
55
56/// True if multisampled depth textures are supported on this platform.
57///
58/// In theory, Naga supports depth textures on WebGL 2. In practice, it doesn't,
59/// because of a silly bug whereby Naga assumes that all depth textures are
60/// `sampler2DShadow` and will cheerfully generate invalid GLSL that tries to
61/// perform non-percentage-closer-filtering with such a sampler. Therefore we
62/// disable depth of field and screen space reflections entirely on WebGL 2.
63#[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
64pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true;
65
66use core::ops::Range;
67
68pub use camera_3d::*;
69pub use main_opaque_pass_3d_node::*;
70pub use main_transparent_pass_3d_node::*;
71
72use bevy_app::{App, Plugin, PostUpdate};
73use bevy_asset::{AssetId, UntypedAssetId};
74use bevy_color::LinearRgba;
75use bevy_ecs::{entity::EntityHashSet, prelude::*};
76use bevy_image::{BevyDefault, Image};
77use bevy_math::FloatOrd;
78use bevy_render::sync_world::MainEntity;
79use bevy_render::{
80    camera::{Camera, ExtractedCamera},
81    extract_component::ExtractComponentPlugin,
82    prelude::Msaa,
83    render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
84    render_phase::{
85        sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId,
86        DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases,
87        ViewSortedRenderPhases,
88    },
89    render_resource::{
90        BindGroupId, CachedRenderPipelineId, Extent3d, FilterMode, Sampler, SamplerDescriptor,
91        Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
92    },
93    renderer::RenderDevice,
94    sync_world::RenderEntity,
95    texture::{ColorAttachment, TextureCache},
96    view::{ExtractedView, ViewDepthTexture, ViewTarget},
97    Extract, ExtractSchedule, Render, RenderApp, RenderSet,
98};
99use bevy_utils::{tracing::warn, HashMap};
100
101use crate::{
102    core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode,
103    deferred::{
104        copy_lighting_id::CopyDeferredLightingIdNode, node::DeferredGBufferPrepassNode,
105        AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT,
106        DEFERRED_PREPASS_FORMAT,
107    },
108    dof::DepthOfFieldNode,
109    prepass::{
110        node::PrepassNode, AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass,
111        NormalPrepass, Opaque3dPrepass, OpaqueNoLightmap3dBinKey, ViewPrepassTextures,
112        MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT,
113    },
114    skybox::SkyboxPlugin,
115    tonemapping::TonemappingNode,
116    upscaling::UpscalingNode,
117};
118
119use self::graph::{Core3d, Node3d};
120
121pub struct Core3dPlugin;
122
123impl Plugin for Core3dPlugin {
124    fn build(&self, app: &mut App) {
125        app.register_type::<Camera3d>()
126            .register_type::<ScreenSpaceTransmissionQuality>()
127            .add_plugins((SkyboxPlugin, ExtractComponentPlugin::<Camera3d>::default()))
128            .add_systems(PostUpdate, check_msaa);
129
130        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
131            return;
132        };
133        render_app
134            .init_resource::<DrawFunctions<Opaque3d>>()
135            .init_resource::<DrawFunctions<AlphaMask3d>>()
136            .init_resource::<DrawFunctions<Transmissive3d>>()
137            .init_resource::<DrawFunctions<Transparent3d>>()
138            .init_resource::<DrawFunctions<Opaque3dPrepass>>()
139            .init_resource::<DrawFunctions<AlphaMask3dPrepass>>()
140            .init_resource::<DrawFunctions<Opaque3dDeferred>>()
141            .init_resource::<DrawFunctions<AlphaMask3dDeferred>>()
142            .init_resource::<ViewBinnedRenderPhases<Opaque3d>>()
143            .init_resource::<ViewBinnedRenderPhases<AlphaMask3d>>()
144            .init_resource::<ViewBinnedRenderPhases<Opaque3dPrepass>>()
145            .init_resource::<ViewBinnedRenderPhases<AlphaMask3dPrepass>>()
146            .init_resource::<ViewBinnedRenderPhases<Opaque3dDeferred>>()
147            .init_resource::<ViewBinnedRenderPhases<AlphaMask3dDeferred>>()
148            .init_resource::<ViewSortedRenderPhases<Transmissive3d>>()
149            .init_resource::<ViewSortedRenderPhases<Transparent3d>>()
150            .add_systems(ExtractSchedule, extract_core_3d_camera_phases)
151            .add_systems(ExtractSchedule, extract_camera_prepass_phase)
152            .add_systems(
153                Render,
154                (
155                    sort_phase_system::<Transmissive3d>.in_set(RenderSet::PhaseSort),
156                    sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
157                    prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources),
158                    prepare_core_3d_transmission_textures.in_set(RenderSet::PrepareResources),
159                    prepare_prepass_textures.in_set(RenderSet::PrepareResources),
160                ),
161            );
162
163        render_app
164            .add_render_sub_graph(Core3d)
165            .add_render_graph_node::<ViewNodeRunner<PrepassNode>>(Core3d, Node3d::Prepass)
166            .add_render_graph_node::<ViewNodeRunner<DeferredGBufferPrepassNode>>(
167                Core3d,
168                Node3d::DeferredPrepass,
169            )
170            .add_render_graph_node::<ViewNodeRunner<CopyDeferredLightingIdNode>>(
171                Core3d,
172                Node3d::CopyDeferredLightingId,
173            )
174            .add_render_graph_node::<EmptyNode>(Core3d, Node3d::EndPrepasses)
175            .add_render_graph_node::<EmptyNode>(Core3d, Node3d::StartMainPass)
176            .add_render_graph_node::<ViewNodeRunner<MainOpaquePass3dNode>>(
177                Core3d,
178                Node3d::MainOpaquePass,
179            )
180            .add_render_graph_node::<ViewNodeRunner<MainTransmissivePass3dNode>>(
181                Core3d,
182                Node3d::MainTransmissivePass,
183            )
184            .add_render_graph_node::<ViewNodeRunner<MainTransparentPass3dNode>>(
185                Core3d,
186                Node3d::MainTransparentPass,
187            )
188            .add_render_graph_node::<EmptyNode>(Core3d, Node3d::EndMainPass)
189            .add_render_graph_node::<ViewNodeRunner<DepthOfFieldNode>>(Core3d, Node3d::DepthOfField)
190            .add_render_graph_node::<ViewNodeRunner<TonemappingNode>>(Core3d, Node3d::Tonemapping)
191            .add_render_graph_node::<EmptyNode>(Core3d, Node3d::EndMainPassPostProcessing)
192            .add_render_graph_node::<ViewNodeRunner<UpscalingNode>>(Core3d, Node3d::Upscaling)
193            .add_render_graph_edges(
194                Core3d,
195                (
196                    Node3d::Prepass,
197                    Node3d::DeferredPrepass,
198                    Node3d::CopyDeferredLightingId,
199                    Node3d::EndPrepasses,
200                    Node3d::StartMainPass,
201                    Node3d::MainOpaquePass,
202                    Node3d::MainTransmissivePass,
203                    Node3d::MainTransparentPass,
204                    Node3d::EndMainPass,
205                    Node3d::Tonemapping,
206                    Node3d::EndMainPassPostProcessing,
207                    Node3d::Upscaling,
208                ),
209            );
210    }
211}
212
213/// Opaque 3D [`BinnedPhaseItem`]s.
214pub struct Opaque3d {
215    /// The key, which determines which can be batched.
216    pub key: Opaque3dBinKey,
217    /// An entity from which data will be fetched, including the mesh if
218    /// applicable.
219    pub representative_entity: (Entity, MainEntity),
220    /// The ranges of instances.
221    pub batch_range: Range<u32>,
222    /// An extra index, which is either a dynamic offset or an index in the
223    /// indirect parameters list.
224    pub extra_index: PhaseItemExtraIndex,
225}
226
227/// Data that must be identical in order to batch phase items together.
228#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
229pub struct Opaque3dBinKey {
230    /// The identifier of the render pipeline.
231    pub pipeline: CachedRenderPipelineId,
232
233    /// The function used to draw.
234    pub draw_function: DrawFunctionId,
235
236    /// The asset that this phase item is associated with.
237    ///
238    /// Normally, this is the ID of the mesh, but for non-mesh items it might be
239    /// the ID of another type of asset.
240    pub asset_id: UntypedAssetId,
241
242    /// The ID of a bind group specific to the material.
243    ///
244    /// In the case of PBR, this is the `MaterialBindGroupId`.
245    pub material_bind_group_id: Option<BindGroupId>,
246
247    /// The lightmap, if present.
248    pub lightmap_image: Option<AssetId<Image>>,
249}
250
251impl PhaseItem for Opaque3d {
252    #[inline]
253    fn entity(&self) -> Entity {
254        self.representative_entity.0
255    }
256
257    #[inline]
258    fn main_entity(&self) -> MainEntity {
259        self.representative_entity.1
260    }
261
262    #[inline]
263    fn draw_function(&self) -> DrawFunctionId {
264        self.key.draw_function
265    }
266
267    #[inline]
268    fn batch_range(&self) -> &Range<u32> {
269        &self.batch_range
270    }
271
272    #[inline]
273    fn batch_range_mut(&mut self) -> &mut Range<u32> {
274        &mut self.batch_range
275    }
276
277    fn extra_index(&self) -> PhaseItemExtraIndex {
278        self.extra_index
279    }
280
281    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
282        (&mut self.batch_range, &mut self.extra_index)
283    }
284}
285
286impl BinnedPhaseItem for Opaque3d {
287    type BinKey = Opaque3dBinKey;
288
289    #[inline]
290    fn new(
291        key: Self::BinKey,
292        representative_entity: (Entity, MainEntity),
293        batch_range: Range<u32>,
294        extra_index: PhaseItemExtraIndex,
295    ) -> Self {
296        Opaque3d {
297            key,
298            representative_entity,
299            batch_range,
300            extra_index,
301        }
302    }
303}
304
305impl CachedRenderPipelinePhaseItem for Opaque3d {
306    #[inline]
307    fn cached_pipeline(&self) -> CachedRenderPipelineId {
308        self.key.pipeline
309    }
310}
311
312pub struct AlphaMask3d {
313    pub key: OpaqueNoLightmap3dBinKey,
314    pub representative_entity: (Entity, MainEntity),
315    pub batch_range: Range<u32>,
316    pub extra_index: PhaseItemExtraIndex,
317}
318
319impl PhaseItem for AlphaMask3d {
320    #[inline]
321    fn entity(&self) -> Entity {
322        self.representative_entity.0
323    }
324
325    fn main_entity(&self) -> MainEntity {
326        self.representative_entity.1
327    }
328
329    #[inline]
330    fn draw_function(&self) -> DrawFunctionId {
331        self.key.draw_function
332    }
333
334    #[inline]
335    fn batch_range(&self) -> &Range<u32> {
336        &self.batch_range
337    }
338
339    #[inline]
340    fn batch_range_mut(&mut self) -> &mut Range<u32> {
341        &mut self.batch_range
342    }
343
344    #[inline]
345    fn extra_index(&self) -> PhaseItemExtraIndex {
346        self.extra_index
347    }
348
349    #[inline]
350    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
351        (&mut self.batch_range, &mut self.extra_index)
352    }
353}
354
355impl BinnedPhaseItem for AlphaMask3d {
356    type BinKey = OpaqueNoLightmap3dBinKey;
357
358    #[inline]
359    fn new(
360        key: Self::BinKey,
361        representative_entity: (Entity, MainEntity),
362        batch_range: Range<u32>,
363        extra_index: PhaseItemExtraIndex,
364    ) -> Self {
365        Self {
366            key,
367            representative_entity,
368            batch_range,
369            extra_index,
370        }
371    }
372}
373
374impl CachedRenderPipelinePhaseItem for AlphaMask3d {
375    #[inline]
376    fn cached_pipeline(&self) -> CachedRenderPipelineId {
377        self.key.pipeline
378    }
379}
380
381pub struct Transmissive3d {
382    pub distance: f32,
383    pub pipeline: CachedRenderPipelineId,
384    pub entity: (Entity, MainEntity),
385    pub draw_function: DrawFunctionId,
386    pub batch_range: Range<u32>,
387    pub extra_index: PhaseItemExtraIndex,
388}
389
390impl PhaseItem for Transmissive3d {
391    /// For now, automatic batching is disabled for transmissive items because their rendering is
392    /// split into multiple steps depending on [`Camera3d::screen_space_specular_transmission_steps`],
393    /// which the batching system doesn't currently know about.
394    ///
395    /// Having batching enabled would cause the same item to be drawn multiple times across different
396    /// steps, whenever the batching range crossed a step boundary.
397    ///
398    /// Eventually, we could add support for this by having the batching system break up the batch ranges
399    /// using the same logic as the transmissive pass, but for now it's simpler to just disable batching.
400    const AUTOMATIC_BATCHING: bool = false;
401
402    #[inline]
403    fn entity(&self) -> Entity {
404        self.entity.0
405    }
406
407    #[inline]
408    fn main_entity(&self) -> MainEntity {
409        self.entity.1
410    }
411
412    #[inline]
413    fn draw_function(&self) -> DrawFunctionId {
414        self.draw_function
415    }
416
417    #[inline]
418    fn batch_range(&self) -> &Range<u32> {
419        &self.batch_range
420    }
421
422    #[inline]
423    fn batch_range_mut(&mut self) -> &mut Range<u32> {
424        &mut self.batch_range
425    }
426
427    #[inline]
428    fn extra_index(&self) -> PhaseItemExtraIndex {
429        self.extra_index
430    }
431
432    #[inline]
433    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
434        (&mut self.batch_range, &mut self.extra_index)
435    }
436}
437
438impl SortedPhaseItem for Transmissive3d {
439    // NOTE: Values increase towards the camera. Back-to-front ordering for transmissive means we need an ascending sort.
440    type SortKey = FloatOrd;
441
442    #[inline]
443    fn sort_key(&self) -> Self::SortKey {
444        FloatOrd(self.distance)
445    }
446
447    #[inline]
448    fn sort(items: &mut [Self]) {
449        radsort::sort_by_key(items, |item| item.distance);
450    }
451}
452
453impl CachedRenderPipelinePhaseItem for Transmissive3d {
454    #[inline]
455    fn cached_pipeline(&self) -> CachedRenderPipelineId {
456        self.pipeline
457    }
458}
459
460pub struct Transparent3d {
461    pub distance: f32,
462    pub pipeline: CachedRenderPipelineId,
463    pub entity: (Entity, MainEntity),
464    pub draw_function: DrawFunctionId,
465    pub batch_range: Range<u32>,
466    pub extra_index: PhaseItemExtraIndex,
467}
468
469impl PhaseItem for Transparent3d {
470    #[inline]
471    fn entity(&self) -> Entity {
472        self.entity.0
473    }
474
475    fn main_entity(&self) -> MainEntity {
476        self.entity.1
477    }
478
479    #[inline]
480    fn draw_function(&self) -> DrawFunctionId {
481        self.draw_function
482    }
483
484    #[inline]
485    fn batch_range(&self) -> &Range<u32> {
486        &self.batch_range
487    }
488
489    #[inline]
490    fn batch_range_mut(&mut self) -> &mut Range<u32> {
491        &mut self.batch_range
492    }
493
494    #[inline]
495    fn extra_index(&self) -> PhaseItemExtraIndex {
496        self.extra_index
497    }
498
499    #[inline]
500    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
501        (&mut self.batch_range, &mut self.extra_index)
502    }
503}
504
505impl SortedPhaseItem for Transparent3d {
506    // NOTE: Values increase towards the camera. Back-to-front ordering for transparent means we need an ascending sort.
507    type SortKey = FloatOrd;
508
509    #[inline]
510    fn sort_key(&self) -> Self::SortKey {
511        FloatOrd(self.distance)
512    }
513
514    #[inline]
515    fn sort(items: &mut [Self]) {
516        radsort::sort_by_key(items, |item| item.distance);
517    }
518}
519
520impl CachedRenderPipelinePhaseItem for Transparent3d {
521    #[inline]
522    fn cached_pipeline(&self) -> CachedRenderPipelineId {
523        self.pipeline
524    }
525}
526
527pub fn extract_core_3d_camera_phases(
528    mut opaque_3d_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
529    mut alpha_mask_3d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
530    mut transmissive_3d_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
531    mut transparent_3d_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
532    cameras_3d: Extract<Query<(RenderEntity, &Camera), With<Camera3d>>>,
533    mut live_entities: Local<EntityHashSet>,
534) {
535    live_entities.clear();
536
537    for (entity, camera) in &cameras_3d {
538        if !camera.is_active {
539            continue;
540        }
541
542        opaque_3d_phases.insert_or_clear(entity);
543        alpha_mask_3d_phases.insert_or_clear(entity);
544        transmissive_3d_phases.insert_or_clear(entity);
545        transparent_3d_phases.insert_or_clear(entity);
546
547        live_entities.insert(entity);
548    }
549
550    opaque_3d_phases.retain(|entity, _| live_entities.contains(entity));
551    alpha_mask_3d_phases.retain(|entity, _| live_entities.contains(entity));
552    transmissive_3d_phases.retain(|entity, _| live_entities.contains(entity));
553    transparent_3d_phases.retain(|entity, _| live_entities.contains(entity));
554}
555
556// Extract the render phases for the prepass
557pub fn extract_camera_prepass_phase(
558    mut commands: Commands,
559    mut opaque_3d_prepass_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
560    mut alpha_mask_3d_prepass_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
561    mut opaque_3d_deferred_phases: ResMut<ViewBinnedRenderPhases<Opaque3dDeferred>>,
562    mut alpha_mask_3d_deferred_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
563    cameras_3d: Extract<
564        Query<
565            (
566                RenderEntity,
567                &Camera,
568                Has<DepthPrepass>,
569                Has<NormalPrepass>,
570                Has<MotionVectorPrepass>,
571                Has<DeferredPrepass>,
572            ),
573            With<Camera3d>,
574        >,
575    >,
576    mut live_entities: Local<EntityHashSet>,
577) {
578    live_entities.clear();
579
580    for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in
581        cameras_3d.iter()
582    {
583        if !camera.is_active {
584            continue;
585        }
586
587        if depth_prepass || normal_prepass || motion_vector_prepass {
588            opaque_3d_prepass_phases.insert_or_clear(entity);
589            alpha_mask_3d_prepass_phases.insert_or_clear(entity);
590        } else {
591            opaque_3d_prepass_phases.remove(&entity);
592            alpha_mask_3d_prepass_phases.remove(&entity);
593        }
594
595        if deferred_prepass {
596            opaque_3d_deferred_phases.insert_or_clear(entity);
597            alpha_mask_3d_deferred_phases.insert_or_clear(entity);
598        } else {
599            opaque_3d_deferred_phases.remove(&entity);
600            alpha_mask_3d_deferred_phases.remove(&entity);
601        }
602        live_entities.insert(entity);
603
604        commands
605            .get_entity(entity)
606            .expect("Camera entity wasn't synced.")
607            .insert_if(DepthPrepass, || depth_prepass)
608            .insert_if(NormalPrepass, || normal_prepass)
609            .insert_if(MotionVectorPrepass, || motion_vector_prepass)
610            .insert_if(DeferredPrepass, || deferred_prepass);
611    }
612
613    opaque_3d_prepass_phases.retain(|entity, _| live_entities.contains(entity));
614    alpha_mask_3d_prepass_phases.retain(|entity, _| live_entities.contains(entity));
615    opaque_3d_deferred_phases.retain(|entity, _| live_entities.contains(entity));
616    alpha_mask_3d_deferred_phases.retain(|entity, _| live_entities.contains(entity));
617}
618
619#[allow(clippy::too_many_arguments)]
620pub fn prepare_core_3d_depth_textures(
621    mut commands: Commands,
622    mut texture_cache: ResMut<TextureCache>,
623    render_device: Res<RenderDevice>,
624    opaque_3d_phases: Res<ViewBinnedRenderPhases<Opaque3d>>,
625    alpha_mask_3d_phases: Res<ViewBinnedRenderPhases<AlphaMask3d>>,
626    transmissive_3d_phases: Res<ViewSortedRenderPhases<Transmissive3d>>,
627    transparent_3d_phases: Res<ViewSortedRenderPhases<Transparent3d>>,
628    views_3d: Query<(
629        Entity,
630        &ExtractedCamera,
631        Option<&DepthPrepass>,
632        &Camera3d,
633        &Msaa,
634    )>,
635) {
636    let mut render_target_usage = HashMap::default();
637    for (view, camera, depth_prepass, camera_3d, _msaa) in &views_3d {
638        if !opaque_3d_phases.contains_key(&view)
639            || !alpha_mask_3d_phases.contains_key(&view)
640            || !transmissive_3d_phases.contains_key(&view)
641            || !transparent_3d_phases.contains_key(&view)
642        {
643            continue;
644        };
645
646        // Default usage required to write to the depth texture
647        let mut usage: TextureUsages = camera_3d.depth_texture_usages.into();
648        if depth_prepass.is_some() {
649            // Required to read the output of the prepass
650            usage |= TextureUsages::COPY_SRC;
651        }
652        render_target_usage
653            .entry(camera.target.clone())
654            .and_modify(|u| *u |= usage)
655            .or_insert_with(|| usage);
656    }
657
658    let mut textures = HashMap::default();
659    for (entity, camera, _, camera_3d, msaa) in &views_3d {
660        let Some(physical_target_size) = camera.physical_target_size else {
661            continue;
662        };
663
664        let cached_texture = textures
665            .entry((camera.target.clone(), msaa))
666            .or_insert_with(|| {
667                // The size of the depth texture
668                let size = Extent3d {
669                    depth_or_array_layers: 1,
670                    width: physical_target_size.x,
671                    height: physical_target_size.y,
672                };
673
674                let usage = *render_target_usage
675                    .get(&camera.target.clone())
676                    .expect("The depth texture usage should already exist for this target");
677
678                let descriptor = TextureDescriptor {
679                    label: Some("view_depth_texture"),
680                    size,
681                    mip_level_count: 1,
682                    sample_count: msaa.samples(),
683                    dimension: TextureDimension::D2,
684                    format: CORE_3D_DEPTH_FORMAT,
685                    usage,
686                    view_formats: &[],
687                };
688
689                texture_cache.get(&render_device, descriptor)
690            })
691            .clone();
692
693        commands.entity(entity).insert(ViewDepthTexture::new(
694            cached_texture,
695            match camera_3d.depth_load_op {
696                Camera3dDepthLoadOp::Clear(v) => Some(v),
697                Camera3dDepthLoadOp::Load => None,
698            },
699        ));
700    }
701}
702
703#[derive(Component)]
704pub struct ViewTransmissionTexture {
705    pub texture: Texture,
706    pub view: TextureView,
707    pub sampler: Sampler,
708}
709
710#[allow(clippy::too_many_arguments)]
711pub fn prepare_core_3d_transmission_textures(
712    mut commands: Commands,
713    mut texture_cache: ResMut<TextureCache>,
714    render_device: Res<RenderDevice>,
715    opaque_3d_phases: Res<ViewBinnedRenderPhases<Opaque3d>>,
716    alpha_mask_3d_phases: Res<ViewBinnedRenderPhases<AlphaMask3d>>,
717    transmissive_3d_phases: Res<ViewSortedRenderPhases<Transmissive3d>>,
718    transparent_3d_phases: Res<ViewSortedRenderPhases<Transparent3d>>,
719    views_3d: Query<(Entity, &ExtractedCamera, &Camera3d, &ExtractedView)>,
720) {
721    let mut textures = HashMap::default();
722    for (entity, camera, camera_3d, view) in &views_3d {
723        if !opaque_3d_phases.contains_key(&entity)
724            || !alpha_mask_3d_phases.contains_key(&entity)
725            || !transparent_3d_phases.contains_key(&entity)
726        {
727            continue;
728        };
729
730        let Some(transmissive_3d_phase) = transmissive_3d_phases.get(&entity) else {
731            continue;
732        };
733
734        let Some(physical_target_size) = camera.physical_target_size else {
735            continue;
736        };
737
738        // Don't prepare a transmission texture if the number of steps is set to 0
739        if camera_3d.screen_space_specular_transmission_steps == 0 {
740            continue;
741        }
742
743        // Don't prepare a transmission texture if there are no transmissive items to render
744        if transmissive_3d_phase.items.is_empty() {
745            continue;
746        }
747
748        let cached_texture = textures
749            .entry(camera.target.clone())
750            .or_insert_with(|| {
751                let usage = TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST;
752
753                // The size of the transmission texture
754                let size = Extent3d {
755                    depth_or_array_layers: 1,
756                    width: physical_target_size.x,
757                    height: physical_target_size.y,
758                };
759
760                let format = if view.hdr {
761                    ViewTarget::TEXTURE_FORMAT_HDR
762                } else {
763                    TextureFormat::bevy_default()
764                };
765
766                let descriptor = TextureDescriptor {
767                    label: Some("view_transmission_texture"),
768                    size,
769                    mip_level_count: 1,
770                    sample_count: 1, // No need for MSAA, as we'll only copy the main texture here
771                    dimension: TextureDimension::D2,
772                    format,
773                    usage,
774                    view_formats: &[],
775                };
776
777                texture_cache.get(&render_device, descriptor)
778            })
779            .clone();
780
781        let sampler = render_device.create_sampler(&SamplerDescriptor {
782            label: Some("view_transmission_sampler"),
783            mag_filter: FilterMode::Linear,
784            min_filter: FilterMode::Linear,
785            ..Default::default()
786        });
787
788        commands.entity(entity).insert(ViewTransmissionTexture {
789            texture: cached_texture.texture,
790            view: cached_texture.default_view,
791            sampler,
792        });
793    }
794}
795
796// Disable MSAA and warn if using deferred rendering
797pub fn check_msaa(mut deferred_views: Query<&mut Msaa, (With<Camera>, With<DeferredPrepass>)>) {
798    for mut msaa in deferred_views.iter_mut() {
799        match *msaa {
800            Msaa::Off => (),
801            _ => {
802                warn!("MSAA is incompatible with deferred rendering and has been disabled.");
803                *msaa = Msaa::Off;
804            }
805        };
806    }
807}
808
809// Prepares the textures used by the prepass
810#[allow(clippy::too_many_arguments)]
811pub fn prepare_prepass_textures(
812    mut commands: Commands,
813    mut texture_cache: ResMut<TextureCache>,
814    render_device: Res<RenderDevice>,
815    opaque_3d_prepass_phases: Res<ViewBinnedRenderPhases<Opaque3dPrepass>>,
816    alpha_mask_3d_prepass_phases: Res<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
817    opaque_3d_deferred_phases: Res<ViewBinnedRenderPhases<Opaque3dDeferred>>,
818    alpha_mask_3d_deferred_phases: Res<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
819    views_3d: Query<(
820        Entity,
821        &ExtractedCamera,
822        &Msaa,
823        Has<DepthPrepass>,
824        Has<NormalPrepass>,
825        Has<MotionVectorPrepass>,
826        Has<DeferredPrepass>,
827    )>,
828) {
829    let mut depth_textures = HashMap::default();
830    let mut normal_textures = HashMap::default();
831    let mut deferred_textures = HashMap::default();
832    let mut deferred_lighting_id_textures = HashMap::default();
833    let mut motion_vectors_textures = HashMap::default();
834    for (
835        entity,
836        camera,
837        msaa,
838        depth_prepass,
839        normal_prepass,
840        motion_vector_prepass,
841        deferred_prepass,
842    ) in &views_3d
843    {
844        if !opaque_3d_prepass_phases.contains_key(&entity)
845            && !alpha_mask_3d_prepass_phases.contains_key(&entity)
846            && !opaque_3d_deferred_phases.contains_key(&entity)
847            && !alpha_mask_3d_deferred_phases.contains_key(&entity)
848        {
849            continue;
850        };
851
852        let Some(physical_target_size) = camera.physical_target_size else {
853            continue;
854        };
855
856        let size = Extent3d {
857            depth_or_array_layers: 1,
858            width: physical_target_size.x,
859            height: physical_target_size.y,
860        };
861
862        let cached_depth_texture = depth_prepass.then(|| {
863            depth_textures
864                .entry(camera.target.clone())
865                .or_insert_with(|| {
866                    let descriptor = TextureDescriptor {
867                        label: Some("prepass_depth_texture"),
868                        size,
869                        mip_level_count: 1,
870                        sample_count: msaa.samples(),
871                        dimension: TextureDimension::D2,
872                        format: CORE_3D_DEPTH_FORMAT,
873                        usage: TextureUsages::COPY_DST
874                            | TextureUsages::RENDER_ATTACHMENT
875                            | TextureUsages::TEXTURE_BINDING,
876                        view_formats: &[],
877                    };
878                    texture_cache.get(&render_device, descriptor)
879                })
880                .clone()
881        });
882
883        let cached_normals_texture = normal_prepass.then(|| {
884            normal_textures
885                .entry(camera.target.clone())
886                .or_insert_with(|| {
887                    texture_cache.get(
888                        &render_device,
889                        TextureDescriptor {
890                            label: Some("prepass_normal_texture"),
891                            size,
892                            mip_level_count: 1,
893                            sample_count: msaa.samples(),
894                            dimension: TextureDimension::D2,
895                            format: NORMAL_PREPASS_FORMAT,
896                            usage: TextureUsages::RENDER_ATTACHMENT
897                                | TextureUsages::TEXTURE_BINDING,
898                            view_formats: &[],
899                        },
900                    )
901                })
902                .clone()
903        });
904
905        let cached_motion_vectors_texture = motion_vector_prepass.then(|| {
906            motion_vectors_textures
907                .entry(camera.target.clone())
908                .or_insert_with(|| {
909                    texture_cache.get(
910                        &render_device,
911                        TextureDescriptor {
912                            label: Some("prepass_motion_vectors_textures"),
913                            size,
914                            mip_level_count: 1,
915                            sample_count: msaa.samples(),
916                            dimension: TextureDimension::D2,
917                            format: MOTION_VECTOR_PREPASS_FORMAT,
918                            usage: TextureUsages::RENDER_ATTACHMENT
919                                | TextureUsages::TEXTURE_BINDING,
920                            view_formats: &[],
921                        },
922                    )
923                })
924                .clone()
925        });
926
927        let cached_deferred_texture = deferred_prepass.then(|| {
928            deferred_textures
929                .entry(camera.target.clone())
930                .or_insert_with(|| {
931                    texture_cache.get(
932                        &render_device,
933                        TextureDescriptor {
934                            label: Some("prepass_deferred_texture"),
935                            size,
936                            mip_level_count: 1,
937                            sample_count: 1,
938                            dimension: TextureDimension::D2,
939                            format: DEFERRED_PREPASS_FORMAT,
940                            usage: TextureUsages::RENDER_ATTACHMENT
941                                | TextureUsages::TEXTURE_BINDING,
942                            view_formats: &[],
943                        },
944                    )
945                })
946                .clone()
947        });
948
949        let cached_deferred_lighting_pass_id_texture = deferred_prepass.then(|| {
950            deferred_lighting_id_textures
951                .entry(camera.target.clone())
952                .or_insert_with(|| {
953                    texture_cache.get(
954                        &render_device,
955                        TextureDescriptor {
956                            label: Some("deferred_lighting_pass_id_texture"),
957                            size,
958                            mip_level_count: 1,
959                            sample_count: 1,
960                            dimension: TextureDimension::D2,
961                            format: DEFERRED_LIGHTING_PASS_ID_FORMAT,
962                            usage: TextureUsages::RENDER_ATTACHMENT
963                                | TextureUsages::TEXTURE_BINDING,
964                            view_formats: &[],
965                        },
966                    )
967                })
968                .clone()
969        });
970
971        commands.entity(entity).insert(ViewPrepassTextures {
972            depth: cached_depth_texture
973                .map(|t| ColorAttachment::new(t, None, Some(LinearRgba::BLACK))),
974            normal: cached_normals_texture
975                .map(|t| ColorAttachment::new(t, None, Some(LinearRgba::BLACK))),
976            // Red and Green channels are X and Y components of the motion vectors
977            // Blue channel doesn't matter, but set to 0.0 for possible faster clear
978            // https://gpuopen.com/performance/#clears
979            motion_vectors: cached_motion_vectors_texture
980                .map(|t| ColorAttachment::new(t, None, Some(LinearRgba::BLACK))),
981            deferred: cached_deferred_texture
982                .map(|t| ColorAttachment::new(t, None, Some(LinearRgba::BLACK))),
983            deferred_lighting_pass_id: cached_deferred_lighting_pass_id_texture
984                .map(|t| ColorAttachment::new(t, None, Some(LinearRgba::BLACK))),
985            size,
986        });
987    }
988}