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