Skip to main content

bevy_render/
camera.rs

1use core::mem;
2
3use crate::{
4    batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
5    extract_component::{ExtractComponent, ExtractComponentPlugin},
6    extract_resource::{extract_resource, ExtractResource, ExtractResourcePlugin},
7    render_asset::RenderAssets,
8    render_resource::TextureView,
9    sync_component::SyncComponent,
10    sync_world::{MainEntity, MainEntityHashSet, RenderEntity, SyncToRenderWorld},
11    texture::{GpuImage, ManualTextureViews},
12    view::{
13        ColorGrading, ExtractedView, ExtractedWindow, Msaa, NoIndirectDrawing,
14        RenderExtractedVisibleEntities, RenderVisibleEntities, RenderVisibleEntitiesClass,
15        ResolvedCompositingSpace, RetainedViewEntity, ViewUniformOffset,
16        VisibilityExtractionSystemParam,
17    },
18    Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
19};
20
21use bevy_app::{App, Plugin, PostStartup, PostUpdate};
22use bevy_asset::{AssetEvent, AssetEventSystems, AssetId, Assets};
23use bevy_camera::{
24    primitives::Frustum,
25    visibility::{self, RenderLayers, VisibleEntities},
26    Camera, Camera2d, Camera3d, CameraMainTextureUsages, CameraOutputMode, CameraUpdateSystems,
27    ClearColor, ClearColorConfig, CompositingSpace, Exposure, Hdr, ManualTextureViewHandle,
28    MsaaWriteback, NormalizedRenderTarget, Projection, RenderTarget, RenderTargetInfo, Viewport,
29};
30use bevy_derive::{Deref, DerefMut};
31use bevy_ecs::{
32    change_detection::DetectChanges,
33    component::Component,
34    entity::{ContainsEntity, Entity, EntityHashMap, EntityHashSet},
35    error::BevyError,
36    lifecycle::HookContext,
37    message::MessageReader,
38    prelude::With,
39    query::{Has, QueryItem},
40    reflect::ReflectComponent,
41    resource::Resource,
42    schedule::{InternedScheduleLabel, IntoScheduleConfigs, ScheduleLabel, SystemSet},
43    system::{Commands, Query, Res, ResMut},
44    world::DeferredWorld,
45};
46use bevy_image::Image;
47use bevy_log::warn;
48use bevy_log::warn_once;
49use bevy_math::{uvec2, vec2, Mat4, URect, UVec2, UVec4, Vec2};
50use bevy_platform::collections::{HashMap, HashSet};
51use bevy_reflect::prelude::*;
52use bevy_transform::components::GlobalTransform;
53use bevy_window::{PrimaryWindow, Window, WindowCreated, WindowResized, WindowScaleFactorChanged};
54use itertools::Either;
55use wgpu::TextureFormat;
56
57/// Main-pass color [`TextureFormat`] keyed by camera render entity.
58#[derive(Resource, Default, Deref, DerefMut)]
59pub struct CameraMainPassTextureFormats(pub EntityHashMap<TextureFormat>);
60
61#[derive(Default)]
62pub struct CameraPlugin;
63
64impl Plugin for CameraPlugin {
65    fn build(&self, app: &mut App) {
66        app.register_required_components::<Camera, Msaa>()
67            .register_required_components::<Camera, SyncToRenderWorld>()
68            .register_required_components::<Camera3d, ColorGrading>()
69            .register_required_components::<Camera3d, Exposure>()
70            .add_plugins((
71                ExtractResourcePlugin::<ClearColor>::default(),
72                ExtractComponentPlugin::<CameraMainTextureUsages>::default(),
73            ))
74            .add_systems(PostStartup, camera_system.in_set(CameraUpdateSystems))
75            .add_systems(
76                PostUpdate,
77                camera_system
78                    .in_set(CameraUpdateSystems)
79                    .before(AssetEventSystems)
80                    .before(visibility::update_frusta),
81            );
82        app.world_mut()
83            .register_component_hooks::<Camera>()
84            .on_add(warn_on_no_render_graph);
85
86        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
87            render_app
88                .init_resource::<CameraMainPassTextureFormats>()
89                .init_resource::<SortedCameras>()
90                .init_resource::<DirtySpecializations>()
91                .init_resource::<DirtyWireframeSpecializations>()
92                .allow_ambiguous_resource::<DirtySpecializations>()
93                .allow_ambiguous_resource::<DirtyWireframeSpecializations>()
94                .configure_sets(
95                    ExtractSchedule,
96                    (
97                        DirtySpecializationSystems::Clear
98                            .before_weak(DirtySpecializationSystems::CheckForChanges),
99                        DirtySpecializationSystems::CheckForChanges
100                            .before_weak(DirtySpecializationSystems::CheckForRemovals),
101                    ),
102                )
103                .add_systems(
104                    ExtractSchedule,
105                    (
106                        extract_cameras
107                            .after(extract_resource::<ManualTextureViews, RenderApp, ()>),
108                        clear_dirty_specializations.in_set(DirtySpecializationSystems::Clear),
109                        clear_dirty_wireframe_specializations
110                            .in_set(DirtySpecializationSystems::Clear),
111                        expire_specializations_for_views.in_set(RenderSystems::Cleanup),
112                        expire_wireframe_specializations_for_views.in_set(RenderSystems::Cleanup),
113                    ),
114                )
115                .add_systems(Render, sort_cameras.in_set(RenderSystems::CreateViews));
116        }
117    }
118}
119
120fn warn_on_no_render_graph(world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) {
121    if !world.entity(entity).contains::<CameraRenderGraph>() {
122        warn!("{}Entity {entity} has a `Camera` component, but it doesn't have a render graph configured. Usually, adding a `Camera2d` or `Camera3d` component will work.
123        However, you may instead need to enable `bevy_core_pipeline`, or may want to manually add a `CameraRenderGraph` component to create a custom render graph.", caller.map(|location|format!("{location}: ")).unwrap_or_default());
124    }
125}
126
127impl ExtractResource<RenderApp> for ClearColor {
128    type Source = Self;
129
130    fn extract_resource(source: &Self::Source) -> Self {
131        source.clone()
132    }
133}
134
135impl SyncComponent<RenderApp> for CameraMainTextureUsages {
136    type Target = Self;
137}
138
139impl ExtractComponent<RenderApp> for CameraMainTextureUsages {
140    type QueryData = &'static Self;
141    type QueryFilter = ();
142    type Out = Self;
143
144    fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
145        Some(*item)
146    }
147}
148
149impl SyncComponent<RenderApp> for Camera2d {
150    type Target = Self;
151}
152
153impl ExtractComponent<RenderApp> for Camera2d {
154    type QueryData = &'static Self;
155    type QueryFilter = With<Camera>;
156    type Out = Self;
157
158    fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
159        Some(item.clone())
160    }
161}
162
163impl SyncComponent<RenderApp> for Camera3d {
164    type Target = Self;
165}
166
167impl ExtractComponent<RenderApp> for Camera3d {
168    type QueryData = &'static Self;
169    type QueryFilter = With<Camera>;
170    type Out = Self;
171
172    fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
173        Some(item.clone())
174    }
175}
176
177/// Configures the render schedule to be run for a given [`Camera`] entity.
178#[derive(Component, Debug, Deref, DerefMut, Reflect, Clone)]
179#[reflect(opaque)]
180#[reflect(Component, Debug, Clone)]
181pub struct CameraRenderGraph(pub InternedScheduleLabel);
182
183impl CameraRenderGraph {
184    /// Creates a new [`CameraRenderGraph`] from a schedule label.
185    #[inline]
186    pub fn new<T: ScheduleLabel>(schedule: T) -> Self {
187        Self(schedule.intern())
188    }
189
190    /// Sets the schedule.
191    #[inline]
192    pub fn set<T: ScheduleLabel>(&mut self, schedule: T) {
193        self.0 = schedule.intern();
194    }
195}
196
197pub trait NormalizedRenderTargetExt {
198    fn get_texture_view<'a>(
199        &self,
200        windows: &'a Query<(MainEntity, &ExtractedWindow)>,
201        images: &'a RenderAssets<GpuImage>,
202        manual_texture_views: &'a ManualTextureViews,
203    ) -> Option<&'a TextureView>;
204
205    /// Retrieves the [`TextureFormat`] of this render target, if it exists.
206    fn get_texture_view_format<'a>(
207        &self,
208        windows: &'a Query<(MainEntity, &ExtractedWindow)>,
209        images: &'a RenderAssets<GpuImage>,
210        manual_texture_views: &'a ManualTextureViews,
211    ) -> Option<TextureFormat>;
212
213    fn get_render_target_info<'a>(
214        &self,
215        resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
216        images: &Assets<Image>,
217        manual_texture_views: &ManualTextureViews,
218    ) -> Result<RenderTargetInfo, MissingRenderTargetInfoError>;
219
220    // Check if this render target is contained in the given changed windows or images.
221    fn is_changed(
222        &self,
223        changed_window_ids: &EntityHashSet,
224        changed_image_handles: &HashSet<&AssetId<Image>>,
225    ) -> bool;
226}
227
228impl NormalizedRenderTargetExt for NormalizedRenderTarget {
229    fn get_texture_view<'a>(
230        &self,
231        windows: &'a Query<(MainEntity, &ExtractedWindow)>,
232        images: &'a RenderAssets<GpuImage>,
233        manual_texture_views: &'a ManualTextureViews,
234    ) -> Option<&'a TextureView> {
235        match self {
236            NormalizedRenderTarget::Window(window_ref) => windows
237                .iter()
238                .find(|(e, _)| *e == window_ref.entity())
239                .and_then(|(_, window)| window.swap_chain_texture_view.as_ref()),
240            NormalizedRenderTarget::Image(image_target) => images
241                .get(&image_target.handle)
242                .map(|image| &image.texture_view),
243            NormalizedRenderTarget::TextureView(id) => {
244                manual_texture_views.get(id).map(|tex| &tex.texture_view)
245            }
246            NormalizedRenderTarget::None { .. } => None,
247        }
248    }
249
250    /// Retrieves the texture view's [`TextureFormat`] of this render target, if it exists.
251    fn get_texture_view_format<'a>(
252        &self,
253        windows: &'a Query<(MainEntity, &ExtractedWindow)>,
254        images: &'a RenderAssets<GpuImage>,
255        manual_texture_views: &'a ManualTextureViews,
256    ) -> Option<TextureFormat> {
257        match self {
258            NormalizedRenderTarget::Window(window_ref) => windows
259                .iter()
260                .find(|(e, _)| *e == window_ref.entity())
261                .and_then(|(_, window)| window.swap_chain_texture_view_format),
262            NormalizedRenderTarget::Image(image_target) => {
263                images.get(&image_target.handle).map(GpuImage::view_format)
264            }
265            NormalizedRenderTarget::TextureView(id) => {
266                manual_texture_views.get(id).map(|tex| tex.view_format)
267            }
268            NormalizedRenderTarget::None { .. } => None,
269        }
270    }
271
272    fn get_render_target_info<'a>(
273        &self,
274        resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
275        images: &Assets<Image>,
276        manual_texture_views: &ManualTextureViews,
277    ) -> Result<RenderTargetInfo, MissingRenderTargetInfoError> {
278        match self {
279            NormalizedRenderTarget::Window(window_ref) => resolutions
280                .into_iter()
281                .find(|(entity, _)| *entity == window_ref.entity())
282                .map(|(_, window)| RenderTargetInfo {
283                    physical_size: window.physical_size(),
284                    scale_factor: window.resolution.scale_factor(),
285                })
286                .ok_or(MissingRenderTargetInfoError::Window {
287                    window: window_ref.entity(),
288                }),
289            NormalizedRenderTarget::Image(image_target) => images
290                .get(&image_target.handle)
291                .map(|image| RenderTargetInfo {
292                    physical_size: image.size(),
293                    scale_factor: image_target.scale_factor,
294                })
295                .ok_or(MissingRenderTargetInfoError::Image {
296                    image: image_target.handle.id(),
297                }),
298            NormalizedRenderTarget::TextureView(id) => manual_texture_views
299                .get(id)
300                .map(|tex| RenderTargetInfo {
301                    physical_size: tex.size,
302                    scale_factor: 1.0,
303                })
304                .ok_or(MissingRenderTargetInfoError::TextureView { texture_view: *id }),
305            NormalizedRenderTarget::None { width, height } => Ok(RenderTargetInfo {
306                physical_size: uvec2(*width, *height),
307                scale_factor: 1.0,
308            }),
309        }
310    }
311
312    // Check if this render target is contained in the given changed windows or images.
313    fn is_changed(
314        &self,
315        changed_window_ids: &EntityHashSet,
316        changed_image_handles: &HashSet<&AssetId<Image>>,
317    ) -> bool {
318        match self {
319            NormalizedRenderTarget::Window(window_ref) => {
320                changed_window_ids.contains(&window_ref.entity())
321            }
322            NormalizedRenderTarget::Image(image_target) => {
323                changed_image_handles.contains(&image_target.handle.id())
324            }
325            NormalizedRenderTarget::TextureView(_) | NormalizedRenderTarget::None { .. } => true,
326        }
327    }
328}
329
330#[derive(Debug, thiserror::Error)]
331pub enum MissingRenderTargetInfoError {
332    #[error("RenderTarget::Window missing ({window:?}): Make sure the provided entity has a Window component.")]
333    Window { window: Entity },
334    #[error("RenderTarget::Image missing ({image:?}): Make sure the Image's usages include RenderAssetUsages::MAIN_WORLD.")]
335    Image { image: AssetId<Image> },
336    #[error("RenderTarget::TextureView missing ({texture_view:?}): make sure the texture view handle was not removed.")]
337    TextureView {
338        texture_view: ManualTextureViewHandle,
339    },
340}
341
342/// System in charge of updating a [`Camera`] when its window or projection changes.
343///
344/// The system detects window creation, resize, and scale factor change events to update the camera
345/// [`Projection`] if needed.
346///
347/// ## World Resources
348///
349/// [`Res<Assets<Image>>`](Assets<Image>) -- For cameras that render to an image, this resource is used to
350/// inspect information about the render target. This system will not access any other image assets.
351///
352/// [`OrthographicProjection`]: bevy_camera::OrthographicProjection
353/// [`PerspectiveProjection`]: bevy_camera::PerspectiveProjection
354pub fn camera_system(
355    mut window_resized_reader: MessageReader<WindowResized>,
356    mut window_created_reader: MessageReader<WindowCreated>,
357    mut window_scale_factor_changed_reader: MessageReader<WindowScaleFactorChanged>,
358    mut image_asset_event_reader: MessageReader<AssetEvent<Image>>,
359    primary_window: Query<Entity, With<PrimaryWindow>>,
360    windows: Query<(Entity, &Window)>,
361    images: Res<Assets<Image>>,
362    manual_texture_views: Res<ManualTextureViews>,
363    mut cameras: Query<(&mut Camera, &RenderTarget, &mut Projection)>,
364) -> Result<(), BevyError> {
365    let primary_window = primary_window.iter().next();
366
367    let mut changed_window_ids = EntityHashSet::default();
368    changed_window_ids.extend(window_created_reader.read().map(|event| event.window));
369    changed_window_ids.extend(window_resized_reader.read().map(|event| event.window));
370    let scale_factor_changed_window_ids: EntityHashSet = window_scale_factor_changed_reader
371        .read()
372        .map(|event| event.window)
373        .collect();
374    changed_window_ids.extend(scale_factor_changed_window_ids.clone());
375
376    let changed_image_handles: HashSet<&AssetId<Image>> = image_asset_event_reader
377        .read()
378        .filter_map(|event| match event {
379            AssetEvent::Modified { id } | AssetEvent::Added { id } => Some(id),
380            _ => None,
381        })
382        .collect();
383
384    for (mut camera, render_target, mut camera_projection) in &mut cameras {
385        let mut viewport_size = camera
386            .viewport
387            .as_ref()
388            .map(|viewport| viewport.physical_size);
389
390        if let Some(normalized_target) = render_target.normalize(primary_window)
391            && (normalized_target.is_changed(&changed_window_ids, &changed_image_handles)
392                || camera.is_added()
393                || camera_projection.is_changed()
394                || camera.computed.old_viewport_size != viewport_size
395                || camera.computed.old_sub_camera_view != camera.sub_camera_view)
396        {
397            let new_computed_target_info = normalized_target.get_render_target_info(
398                windows,
399                &images,
400                &manual_texture_views,
401            )?;
402            // Check for the scale factor changing, and resize the viewport if needed.
403            // This can happen when the window is moved between monitors with different DPIs.
404            // Without this, the viewport will take a smaller portion of the window moved to
405            // a higher DPI monitor.
406            if normalized_target.is_changed(&scale_factor_changed_window_ids, &HashSet::default())
407                && let Some(old_scale_factor) = camera
408                    .computed
409                    .target_info
410                    .as_ref()
411                    .map(|info| info.scale_factor)
412            {
413                let resize_factor = new_computed_target_info.scale_factor / old_scale_factor;
414                if let Some(ref mut viewport) = camera.viewport {
415                    let resize = |vec: UVec2| (vec.as_vec2() * resize_factor).as_uvec2();
416                    viewport.physical_position = resize(viewport.physical_position);
417                    viewport.physical_size = resize(viewport.physical_size);
418                    viewport_size = Some(viewport.physical_size);
419                }
420            }
421            // This check is needed because when changing WindowMode to Fullscreen, the viewport may have invalid
422            // arguments due to a sudden change on the window size to a lower value.
423            // If the size of the window is lower, the viewport will match that lower value.
424            if let Some(viewport) = &mut camera.viewport {
425                viewport.clamp_to_size(new_computed_target_info.physical_size);
426            }
427            camera.computed.target_info = Some(new_computed_target_info);
428            if let Some(size) = camera.logical_viewport_size()
429                && size.x != 0.0
430                && size.y != 0.0
431            {
432                camera_projection.update(size.x, size.y);
433                camera.computed.clip_from_view = match &camera.sub_camera_view {
434                    Some(sub_view) => camera_projection.get_clip_from_view_for_sub(sub_view),
435                    None => camera_projection.get_clip_from_view(),
436                }
437            }
438        }
439
440        if camera.computed.old_viewport_size != viewport_size {
441            camera.computed.old_viewport_size = viewport_size;
442        }
443
444        if camera.computed.old_sub_camera_view != camera.sub_camera_view {
445            camera.computed.old_sub_camera_view = camera.sub_camera_view;
446        }
447    }
448    Ok(())
449}
450
451/// Describes a [`Camera`] in the render world.
452///
453/// Every `ExtractedCamera` also has an [`ExtractedView`], but not every
454/// view comes from a camera. For example, views can come from lights,
455/// for drawing shadow maps.
456#[derive(Component, Debug)]
457#[require(RenderVisibleEntities)]
458pub struct ExtractedCamera {
459    pub target: Option<NormalizedRenderTarget>,
460    pub physical_viewport_size: Option<UVec2>,
461    pub physical_target_size: Option<UVec2>,
462    pub viewport: Option<Viewport>,
463    pub schedule: InternedScheduleLabel,
464    pub order: isize,
465    pub output_mode: CameraOutputMode,
466    pub msaa_writeback: MsaaWriteback,
467    pub clear_color: ClearColorConfig,
468    pub sorted_camera_index_for_target: usize,
469    pub exposure: f32,
470    pub hdr: bool,
471}
472
473pub fn extract_cameras(
474    mut commands: Commands,
475    mut main_pass_formats: ResMut<CameraMainPassTextureFormats>,
476    query: Extract<
477        Query<(
478            Entity,
479            RenderEntity,
480            &Camera,
481            &RenderTarget,
482            &CameraRenderGraph,
483            &GlobalTransform,
484            &VisibleEntities,
485            &Frustum,
486            (
487                Has<Hdr>,
488                Option<&CompositingSpace>,
489                Option<&ColorGrading>,
490                Option<&Exposure>,
491                Option<&TemporalJitter>,
492                Option<&MipBias>,
493                Option<&RenderLayers>,
494                Option<&Projection>,
495                Has<NoIndirectDrawing>,
496            ),
497        )>,
498    >,
499    primary_window: Extract<Query<Entity, With<PrimaryWindow>>>,
500    manual_texture_views: Res<ManualTextureViews>,
501    images: Res<RenderAssets<GpuImage>>,
502    mut existing_render_visible_entities_cpu_culling: Query<
503        &mut RenderExtractedVisibleEntities,
504        With<RenderVisibleEntities>,
505    >,
506    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
507    visibility_extraction_system_param: VisibilityExtractionSystemParam,
508    extracted_swap_chains: Query<(MainEntity, &ExtractedWindow)>,
509) {
510    main_pass_formats.clear();
511    let primary_window = primary_window.iter().next();
512    type ExtractedCameraComponents = (
513        ExtractedCamera,
514        ExtractedView,
515        ResolvedCompositingSpace,
516        TemporalJitter,
517        MipBias,
518        RenderLayers,
519        Projection,
520        NoIndirectDrawing,
521        ViewUniformOffset,
522    );
523
524    for (
525        main_entity,
526        render_entity,
527        camera,
528        render_target,
529        camera_render_graph,
530        transform,
531        visible_entities,
532        frustum,
533        (
534            hdr,
535            compositing_space,
536            color_grading,
537            exposure,
538            temporal_jitter,
539            mip_bias,
540            render_layers,
541            projection,
542            no_indirect_drawing,
543        ),
544    ) in query.iter()
545    {
546        if !camera.is_active {
547            // Note: `RenderVisibleEntities` is here because several other retained data `ViewBinnedRenderPhase<Opaque3d>` will be removed when camera is not active
548            commands
549                .entity(render_entity)
550                .remove::<(ExtractedCameraComponents, RenderVisibleEntities)>();
551            continue;
552        }
553
554        let color_grading = color_grading.unwrap_or(&ColorGrading::default()).clone();
555
556        if let (
557            Some(URect {
558                min: viewport_origin,
559                ..
560            }),
561            Some(viewport_size),
562            Some(target_size),
563        ) = (
564            camera.physical_viewport_rect(),
565            camera.physical_viewport_size(),
566            camera.physical_target_size(),
567        ) {
568            if target_size.x == 0 || target_size.y == 0 {
569                commands
570                    .entity(render_entity)
571                    .remove::<ExtractedCameraComponents>();
572                continue;
573            }
574
575            let mut render_visible_entities_cpu_culling =
576                match existing_render_visible_entities_cpu_culling.get_mut(render_entity) {
577                    Ok(ref mut existing_render_visible_entities_cpu_culling) => {
578                        mem::take(&mut **existing_render_visible_entities_cpu_culling)
579                    }
580                    Err(_) => RenderExtractedVisibleEntities::default(),
581                };
582
583            for (visibility_class, visible_mesh_entities) in visible_entities.entities.iter() {
584                let render_view_visible_entities = render_visible_entities_cpu_culling
585                    .classes
586                    .entry(*visibility_class)
587                    .or_default();
588                render_view_visible_entities.entities.clear();
589                for main_entity in visible_mesh_entities {
590                    let render_entity =
591                        match visibility_extraction_system_param.mapper.get(*main_entity) {
592                            Ok(render_entity) => render_entity.entity(),
593                            Err(_) => Entity::PLACEHOLDER,
594                        };
595                    render_view_visible_entities
596                        .entities
597                        .push((render_entity, MainEntity::from(*main_entity)));
598                }
599            }
600
601            // Don't delete "unused" visibility classes from
602            // `RenderVisibleEntities`. Even if a visibility class seems empty
603            // *now*, phases need to be able to find the entities that were just
604            // removed from it.
605
606            let target = render_target.normalize(primary_window);
607            let output_texture_format = target
608                .as_ref()
609                .and_then(|target| {
610                    target
611                        .get_texture_view_format(
612                            &extracted_swap_chains,
613                            &images,
614                            &manual_texture_views,
615                        )
616                        .map(|format| normalize_bgra8(target, format))
617                })
618                .unwrap_or(TextureFormat::Rgba8UnormSrgb);
619            let target_format = if hdr {
620                TextureFormat::Rgba16Float
621            } else if compositing_space.is_some_and(|s| *s == CompositingSpace::Srgb) {
622                TextureFormat::Rgba8Unorm
623            } else {
624                output_texture_format
625            };
626            main_pass_formats.insert(render_entity, target_format);
627
628            let mut commands = commands.entity(render_entity);
629            commands.insert((
630                ExtractedCamera {
631                    target,
632                    viewport: camera.viewport.clone(),
633                    physical_viewport_size: Some(viewport_size),
634                    physical_target_size: Some(target_size),
635                    schedule: camera_render_graph.0,
636                    order: camera.order,
637                    output_mode: camera.output_mode,
638                    msaa_writeback: camera.msaa_writeback,
639                    clear_color: camera.clear_color,
640                    // this will be set in sort_cameras
641                    sorted_camera_index_for_target: 0,
642                    exposure: exposure
643                        .map(Exposure::exposure)
644                        .unwrap_or_else(|| Exposure::default().exposure()),
645                    hdr,
646                },
647                ResolvedCompositingSpace(compositing_space.copied()),
648                ExtractedView {
649                    retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0),
650                    clip_from_view: camera.clip_from_view(),
651                    world_from_view: *transform,
652                    clip_from_world: None,
653                    target_format,
654                    viewport: UVec4::new(
655                        viewport_origin.x,
656                        viewport_origin.y,
657                        viewport_size.x,
658                        viewport_size.y,
659                    ),
660                    color_grading,
661                    invert_culling: camera.invert_culling,
662                },
663                render_visible_entities_cpu_culling,
664                *frustum,
665            ));
666
667            if let Some(temporal_jitter) = temporal_jitter {
668                commands.insert(temporal_jitter.clone());
669            } else {
670                commands.remove::<TemporalJitter>();
671            }
672
673            if let Some(mip_bias) = mip_bias {
674                commands.insert(mip_bias.clone());
675            } else {
676                commands.remove::<MipBias>();
677            }
678
679            if let Some(render_layers) = render_layers {
680                commands.insert(render_layers.clone());
681            } else {
682                commands.remove::<RenderLayers>();
683            }
684
685            if let Some(projection) = projection {
686                commands.insert(projection.clone());
687            } else {
688                commands.remove::<Projection>();
689            }
690
691            if no_indirect_drawing
692                || !matches!(
693                    gpu_preprocessing_support.max_supported_mode,
694                    GpuPreprocessingMode::Culling
695                )
696            {
697                commands.insert(NoIndirectDrawing);
698            } else {
699                commands.remove::<NoIndirectDrawing>();
700            }
701        };
702    }
703}
704
705/// Bgra8 needs an optional feature to support storage binding, and only supports write-only.
706/// We force Rgba8 so that we can always use storage bindings, and rely on the final blit to
707/// convert at the end if needed. See <https://github.com/gpuweb/gpuweb/issues/2748>
708/// Checking just `Bgra8UnormSrgb` and not `Bgra8Unorm` is fine here, because this is the texture
709/// view we already guaranteed to be srgb space if possible. See `ExtractedWindow::set_swapchain_texture`
710fn normalize_bgra8(target: &NormalizedRenderTarget, format: TextureFormat) -> TextureFormat {
711    if matches!(target, NormalizedRenderTarget::Window(_))
712        && format == TextureFormat::Bgra8UnormSrgb
713    {
714        return TextureFormat::Rgba8UnormSrgb;
715    }
716    format
717}
718
719/// Cameras sorted by their order field. This is updated in the [`sort_cameras`] system.
720#[derive(Resource, Default)]
721pub struct SortedCameras(pub Vec<SortedCamera>);
722
723pub struct SortedCamera {
724    pub entity: Entity,
725    pub order: isize,
726    pub target: Option<NormalizedRenderTarget>,
727    pub output_mode: CameraOutputMode,
728}
729
730pub fn sort_cameras(
731    mut sorted_cameras: ResMut<SortedCameras>,
732    mut cameras: Query<(Entity, &mut ExtractedCamera)>,
733) {
734    sorted_cameras.0.clear();
735    for (entity, camera) in cameras.iter() {
736        sorted_cameras.0.push(SortedCamera {
737            entity,
738            order: camera.order,
739            target: camera.target.clone(),
740            output_mode: camera.output_mode,
741        });
742    }
743    // sort by order and ensure within an order, RenderTargets of the same type are packed together
744    sorted_cameras
745        .0
746        .sort_by(|c1, c2| (c1.order, &c1.target).cmp(&(c2.order, &c2.target)));
747    let mut previous_order_target = None;
748    let mut ambiguities = <HashSet<_>>::default();
749    let mut target_counts = <HashMap<_, _>>::default();
750    for sorted_camera in &mut sorted_cameras.0 {
751        let new_order_target = (sorted_camera.order, sorted_camera.target.clone());
752        if let Some(previous_order_target) = previous_order_target
753            && previous_order_target == new_order_target
754        {
755            ambiguities.insert(new_order_target.clone());
756        }
757        if let Some(target) = &sorted_camera.target {
758            // Cameras that share a render target are indexed bottom to top. The index
759            // does not affect render graph ordering. It is read at the end of
760            // rendering, when each camera's image is written out to the target. By
761            // default the bottom camera replaces what is there and every camera above
762            // it alpha-blends on top.
763            //
764            // The map has to be keyed by only the target, and not by anything else.
765            // Splitting the count by a camera setting such as `Hdr` would leave a
766            // mixed stack with two bottom cameras, and the top one would overwrite
767            // the base instead of blending over it. So we don't do that.
768            let count = target_counts.entry(target.clone()).or_insert(0usize);
769            let (_, mut camera) = cameras.get_mut(sorted_camera.entity).unwrap();
770            camera.sorted_camera_index_for_target = *count;
771            *count += 1;
772        }
773        previous_order_target = Some(new_order_target);
774    }
775
776    if !ambiguities.is_empty() {
777        warn_once!(
778            "Camera order ambiguities detected for active cameras with the following priorities: {:?}. \
779            To fix this, ensure there is exactly one Camera entity spawned with a given order for a given RenderTarget. \
780            Ambiguities should be resolved because either (1) multiple active cameras were spawned accidentally, which will \
781            result in rendering multiple instances of the scene or (2) for cases where multiple active cameras is intentional, \
782            ambiguities could result in unpredictable render results.",
783            ambiguities
784        );
785    }
786}
787
788/// A subpixel offset to jitter a perspective camera's frustum by.
789///
790/// Useful for temporal rendering techniques.
791#[derive(Component, Clone, Default, Reflect)]
792#[reflect(Default, Component, Clone)]
793pub struct TemporalJitter {
794    /// Offset is in range [-0.5, 0.5].
795    pub offset: Vec2,
796}
797
798impl TemporalJitter {
799    pub fn jitter_projection(&self, clip_from_view: &mut Mat4, view_size: Vec2) {
800        // https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/d7531ae47d8b36a5d4025663e731a47a38be882f/docs/techniques/media/super-resolution-temporal/jitter-space.svg
801        let mut jitter = (self.offset * vec2(2.0, -2.0)) / view_size;
802
803        // orthographic
804        if clip_from_view.w_axis.w == 1.0 {
805            jitter *= vec2(clip_from_view.x_axis.x, clip_from_view.y_axis.y) * 0.5;
806        }
807
808        clip_from_view.z_axis.x += jitter.x;
809        clip_from_view.z_axis.y += jitter.y;
810    }
811}
812
813/// Camera component specifying a mip bias to apply when sampling from material textures.
814///
815/// Often used in conjunction with antialiasing post-process effects to reduce textures blurriness.
816#[derive(Component, Reflect, Clone)]
817#[reflect(Default, Component)]
818pub struct MipBias(pub f32);
819
820impl Default for MipBias {
821    fn default() -> Self {
822        Self(-1.0)
823    }
824}
825
826/// Stores information about all entities that have changed in such a way as to
827/// potentially require their pipelines to be re-specialized.
828///
829/// This is conservative; there's no harm, other than performance, in having an
830/// entity in this list that doesn't actually need to be re-specialized. Note
831/// that the presence of an entity in this list doesn't mean that a new shader
832/// will necessarily be compiled; the pipeline cache is checked first.
833///
834/// This handles 2D meshes, 3D meshes, and sprites. For 2D and 3D wireframes,
835/// see [`DirtyWireframeSpecializations`]. The reason for having two separate
836/// lists is that a single entity can have both a mesh and a wireframe.
837#[derive(Clone, Resource, Default)]
838pub struct DirtySpecializations {
839    /// All renderable objects that must be re-specialized this frame.
840    pub changed_renderables: MainEntityHashSet,
841
842    /// All renderable objects that need their specializations removed this
843    /// frame.
844    ///
845    /// Note that this may include entities in [`Self::changed_renderables`].
846    /// This is fine, as old specializations are removed before new ones are
847    /// added.
848    pub removed_renderables: MainEntityHashSet,
849
850    /// Views that must be respecialized this frame.
851    ///
852    /// The presence of a view in this list causes all entities that it renders
853    /// to be re-specialized.
854    pub views: HashSet<RetainedViewEntity>,
855}
856
857impl DirtySpecializations {
858    /// Returns true if the view has changed in such a way that all specialized
859    /// pipelines for entities visible from it must be regenerated.
860    pub fn must_wipe_specializations_for_view(&self, view: RetainedViewEntity) -> bool {
861        self.views.contains(&view)
862    }
863
864    /// Given a main entity known to be visible, returns it alongside any render
865    /// entity it corresponds to.
866    ///
867    /// If no render entity corresponds to the given main entity, the render
868    /// entity returned will be [`Entity::PLACEHOLDER`].
869    fn entity_pair_from_visible_main_entity<'a>(
870        &'a self,
871        render_visible_mesh_entities: &'a RenderVisibleEntitiesClass,
872        main_entity: &'a MainEntity,
873    ) -> Option<(&'a Entity, &'a MainEntity)> {
874        // Check entities with CPU culling.
875        if let Ok(index) = render_visible_mesh_entities
876            .entities_cpu_culling
877            .binary_search_by_key(main_entity, |(_, main_entity)| *main_entity)
878        {
879            let (key, value) = &render_visible_mesh_entities.entities_cpu_culling[index];
880            return Some((key, value));
881        }
882
883        // Check entities that opted out of CPU culling.
884        if let Some(entity) = render_visible_mesh_entities
885            .entities_gpu_culling
886            .get(main_entity)
887        {
888            return Some((entity, main_entity));
889        }
890
891        // We didn't find the entity, so return `None`.
892        None
893    }
894
895    /// Iterates over all entities that need their specializations cleared in
896    /// this frame.
897    pub fn iter_to_despecialize<'a>(&'a self) -> impl Iterator<Item = &'a MainEntity> {
898        // Entities that changed or were removed must be
899        // de-specialized.
900        self.changed_renderables
901            .iter()
902            .chain(self.removed_renderables.iter())
903    }
904
905    /// Iterates over all entities that need to have their pipelines
906    /// re-specialized this frame.
907    ///
908    /// `last_frame_view_pending_queues` should be the contents of the
909    /// [`ViewPendingQueues::prev_frame`] list.
910    pub fn iter_to_specialize<'a>(
911        &'a self,
912        view: RetainedViewEntity,
913        render_view_visible_mesh_entities: &'a RenderVisibleEntitiesClass,
914        last_frame_view_pending_queues: &'a HashSet<(Entity, MainEntity)>,
915    ) -> impl Iterator<Item = (&'a Entity, &'a MainEntity)> {
916        (if self.must_wipe_specializations_for_view(view) {
917            Either::Left(render_view_visible_mesh_entities.iter_visible())
918        } else {
919            Either::Right(
920                render_view_visible_mesh_entities
921                    .added_entities()
922                    .iter()
923                    .map(|(entity, main_entity)| (entity, main_entity))
924                    .chain(self.changed_renderables.iter().filter_map(|main_entity| {
925                        self.entity_pair_from_visible_main_entity(
926                            render_view_visible_mesh_entities,
927                            main_entity,
928                        )
929                    })),
930            )
931        })
932        .chain(last_frame_view_pending_queues.iter().filter_map(
933            |(entity, main_entity)| {
934                if render_view_visible_mesh_entities.entity_pair_is_visible(*entity, *main_entity) {
935                    Some((entity, main_entity))
936                } else {
937                    None
938                }
939            },
940        ))
941    }
942
943    /// Iterates over all renderables that should be removed from the phase.
944    ///
945    /// This includes renderables that became invisible this frame, renderables
946    /// that are in [`DirtySpecializations::changed_renderables`], and
947    /// renderables that are in [`DirtySpecializations::removed_renderables`].
948    /// If this view must itself be re-specialized, this will iterate over all
949    /// visible entities in addition to those that became invisible.
950    pub fn iter_to_dequeue<'a>(
951        &'a self,
952        view: RetainedViewEntity,
953        render_visible_mesh_entities: &'a RenderVisibleEntitiesClass,
954    ) -> impl Iterator<Item = &'a MainEntity> {
955        render_visible_mesh_entities
956            .removed_entities
957            .iter()
958            .map(|(_, main_entity)| main_entity)
959            .chain(if self.must_wipe_specializations_for_view(view) {
960                // All visible entities must be removed.
961                // Note that this includes potentially-invisible entities, but
962                // that's OK as they shouldn't be in the caller's bins in the
963                // first place.
964                Either::Left(
965                    render_visible_mesh_entities
966                        .iter_visible()
967                        .map(|(_, main_entity)| main_entity),
968                )
969            } else {
970                // Only entities that changed must be removed.
971                Either::Right(
972                    self.changed_renderables
973                        .iter()
974                        .chain(self.removed_renderables.iter()),
975                )
976            })
977    }
978
979    /// Iterates over all renderables that potentially need to be re-queued.
980    ///
981    /// This includes both renderables that became visible and those that are in
982    /// [`DirtySpecializations::changed_renderables`]. If this view must itself
983    /// be re-specialized, this will iterate over all visible renderables.
984    ///
985    /// `last_frame_view_pending_queues` should be the contents of the
986    /// [`ViewPendingQueues::prev_frame`] list.
987    /// `mesh_instances_queued_this_iteration_scratch_space` should be a
988    /// `Local<MainEntityHashSet>`; it's used internally to avoid yielding the
989    /// same mesh instance multiple times.
990    pub fn iter_to_queue<'a>(
991        &'a self,
992        view: RetainedViewEntity,
993        render_visible_mesh_entities: &'a RenderVisibleEntitiesClass,
994        last_frame_view_pending_queues: &'a HashSet<(Entity, MainEntity)>,
995        mesh_instances_queued_this_iteration_scratch_space: &'a mut MainEntityHashSet,
996    ) -> impl Iterator<Item = (&'a Entity, &'a MainEntity)> {
997        mesh_instances_queued_this_iteration_scratch_space.clear();
998
999        // Use `mesh_instances_queued_this_iteration_scratch_space` to avoid
1000        // yielding the same mesh instance twice.
1001        // Yielding a mesh instance twice would result in binning it twice,
1002        // which is illegal.
1003        (if self.must_wipe_specializations_for_view(view) {
1004            Either::Left(render_visible_mesh_entities.iter_visible())
1005        } else {
1006            Either::Right(
1007                render_visible_mesh_entities
1008                    .added_entities()
1009                    .iter()
1010                    .map(|(entity, main_entity)| (entity, main_entity))
1011                    .chain(self.changed_renderables.iter().filter_map(|main_entity| {
1012                        self.entity_pair_from_visible_main_entity(
1013                            render_visible_mesh_entities,
1014                            main_entity,
1015                        )
1016                    })),
1017            )
1018        })
1019        .chain(
1020            last_frame_view_pending_queues
1021                .iter()
1022                .map(|(entity, main_entity)| (entity, main_entity)),
1023        )
1024        .filter(|(_, main_entity)| {
1025            mesh_instances_queued_this_iteration_scratch_space.insert(**main_entity)
1026        })
1027    }
1028}
1029
1030/// Stores information about all entities that have changed in such a way as to
1031/// potentially require their wireframe pipelines to be re-specialized.
1032///
1033/// This is separate from [`DirtySpecializations`] because a single entity can
1034/// have both a mesh and a wireframe on it, and the pipelines are treated
1035/// separately.
1036///
1037/// See [`DirtySpecializations`] for more information.
1038#[derive(Clone, Resource, Default, Deref, DerefMut)]
1039pub struct DirtyWireframeSpecializations(pub DirtySpecializations);
1040
1041/// Clears out the [`DirtySpecializations`] resource in preparation for a new
1042/// frame.
1043pub fn clear_dirty_specializations(mut dirty_specializations: ResMut<DirtySpecializations>) {
1044    dirty_specializations.changed_renderables.clear();
1045    dirty_specializations.removed_renderables.clear();
1046    dirty_specializations.views.clear();
1047}
1048
1049/// Clears out the [`DirtyWireframeSpecializations`] resource in preparation for
1050/// a new frame.
1051pub fn clear_dirty_wireframe_specializations(
1052    mut dirty_wireframe_specializations: ResMut<DirtyWireframeSpecializations>,
1053) {
1054    dirty_wireframe_specializations.changed_renderables.clear();
1055    dirty_wireframe_specializations.removed_renderables.clear();
1056    dirty_wireframe_specializations.views.clear();
1057}
1058
1059/// A system that removes views that don't exist any longer from
1060/// [`DirtySpecializations`].
1061pub fn expire_specializations_for_views(
1062    views: Query<&ExtractedView>,
1063    mut dirty_specializations: ResMut<DirtySpecializations>,
1064) {
1065    let all_live_retained_view_entities: HashSet<_> =
1066        views.iter().map(|view| view.retained_view_entity).collect();
1067    dirty_specializations.views.retain(|retained_view_entity| {
1068        all_live_retained_view_entities.contains(retained_view_entity)
1069    });
1070}
1071
1072/// A system that removes views that don't exist any longer from
1073/// [`DirtyWireframeSpecializations`].
1074pub fn expire_wireframe_specializations_for_views(
1075    views: Query<&ExtractedView>,
1076    mut dirty_wireframe_specializations: ResMut<DirtyWireframeSpecializations>,
1077) {
1078    let all_live_retained_view_entities: HashSet<_> =
1079        views.iter().map(|view| view.retained_view_entity).collect();
1080    dirty_wireframe_specializations
1081        .views
1082        .retain(|retained_view_entity| {
1083            all_live_retained_view_entities.contains(retained_view_entity)
1084        });
1085}
1086
1087/// A [`SystemSet`] that contains all systems that mutate the
1088/// [`DirtySpecializations`] resource and other resources that wrap that type.
1089///
1090/// These systems must run in order.
1091#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
1092pub enum DirtySpecializationSystems {
1093    /// Systems that clear out [`DirtySpecializations`] types in preparation for
1094    /// a new frame.
1095    Clear,
1096
1097    /// Systems that add entities that need to be re-specialized to
1098    /// [`DirtySpecializations`].
1099    CheckForChanges,
1100
1101    /// Systems that determine which entities need to be removed from render
1102    /// phases and write the results to [`DirtySpecializations`].
1103    ///
1104    /// The set of entities that need to be removed from the render phases can
1105    /// only be determined after all systems in
1106    /// [`DirtySpecializationSystems::CheckForChanges`] have run. That's because
1107    /// these systems check `RemovedComponents` resources, and they have to be
1108    /// able to distinguish between the case in which an entity was truly made
1109    /// unrenderable and the case in which an entity appeared in a
1110    /// `RemovedComponents` table simply because its material *type* changed.
1111    CheckForRemovals,
1112}
1113
1114/// Holds all entities that couldn't be specialized and/or queued because their
1115/// materials or other dependent resources hadn't loaded yet.
1116///
1117/// We might not be able to specialize and/or enqueue a renderable entity if a
1118/// dependent resource like a material isn't available. In that case, we add the
1119/// entity to the appropriate list so that we attempt to re-specialize and
1120/// re-queue it on subsequent frames.
1121///
1122/// This type is expected to be placed in a newtype wrapper and stored as a
1123/// resource: e.g. `PendingMeshMaterialQueues`.
1124#[derive(Default, Deref, DerefMut)]
1125pub struct PendingQueues(pub HashMap<RetainedViewEntity, ViewPendingQueues>);
1126
1127/// Holds all entities that couldn't be specialized and/or queued because their
1128/// materials and/or other dependent resources hadn't loaded yet for a single
1129/// view.
1130///
1131/// See the documentation of [`PendingQueues`] for more information.
1132#[derive(Default)]
1133pub struct ViewPendingQueues {
1134    /// The entities that couldn't be specialized and/or queued this frame.
1135    ///
1136    /// We add to this list during pipeline specialization and queuing.
1137    pub current_frame: HashSet<(Entity, MainEntity)>,
1138
1139    /// The entities that we need to re-examine in this frame.
1140    ///
1141    /// We attempt to specialize and queue entities in this list every frame, as
1142    /// long as those entities are still visible.
1143    pub prev_frame: HashSet<(Entity, MainEntity)>,
1144}
1145
1146impl PendingQueues {
1147    /// Initializes the pending queues for a new frame.
1148    ///
1149    /// This method is called during specialization. It creates the queues for
1150    /// the view if necessary and initializes them.
1151    pub fn prepare_for_new_frame(
1152        &mut self,
1153        retained_view_entity: RetainedViewEntity,
1154    ) -> &mut ViewPendingQueues {
1155        let view_pending_queues = self.entry(retained_view_entity).or_default();
1156        mem::swap(
1157            &mut view_pending_queues.current_frame,
1158            &mut view_pending_queues.prev_frame,
1159        );
1160        view_pending_queues.current_frame.clear();
1161        view_pending_queues
1162    }
1163
1164    /// Removes any pending queues that belong to views not in the supplied
1165    /// `all_views` table.
1166    ///
1167    /// Specialization systems for phases should call this before returning in
1168    /// order to clean up resources relating to views that no longer exist.
1169    pub fn expire_stale_views(&mut self, all_views: &HashSet<RetainedViewEntity>) {
1170        self.retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
1171    }
1172}
1173
1174#[cfg(test)]
1175mod tests {
1176    use super::*;
1177    use bevy_app::Main;
1178    use bevy_ecs::{system::RunSystemOnce, world::World};
1179
1180    fn extracted_camera(
1181        order: isize,
1182        hdr: bool,
1183        target: NormalizedRenderTarget,
1184    ) -> ExtractedCamera {
1185        ExtractedCamera {
1186            target: Some(target),
1187            physical_viewport_size: None,
1188            physical_target_size: None,
1189            viewport: None,
1190            schedule: Main.intern(),
1191            order,
1192            output_mode: CameraOutputMode::default(),
1193            msaa_writeback: MsaaWriteback::default(),
1194            clear_color: ClearColorConfig::Default,
1195            sorted_camera_index_for_target: 0,
1196            exposure: 1.0,
1197            hdr,
1198        }
1199    }
1200
1201    #[test]
1202    fn sort_cameras_assigns_sequential_indices_for_mixed_hdr_shared_target() {
1203        let mut world = World::new();
1204        world.init_resource::<SortedCameras>();
1205
1206        let shared = NormalizedRenderTarget::TextureView(ManualTextureViewHandle(0));
1207        let other = NormalizedRenderTarget::TextureView(ManualTextureViewHandle(1));
1208        // Spawn the upper camera first, so the indices prove camera order beats spawn order.
1209        let upper = world.spawn(extracted_camera(1, true, shared.clone())).id();
1210        let lower = world.spawn(extracted_camera(0, false, shared)).id();
1211        let solo = world.spawn(extracted_camera(0, true, other)).id();
1212
1213        world.run_system_once(sort_cameras).unwrap();
1214
1215        let index = |entity: Entity| {
1216            world
1217                .entity(entity)
1218                .get::<ExtractedCamera>()
1219                .unwrap()
1220                .sorted_camera_index_for_target
1221        };
1222        assert_eq!(index(lower), 0);
1223        assert_eq!(index(upper), 1);
1224        assert_eq!(index(solo), 0);
1225    }
1226}