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#[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#[derive(Component, Debug, Deref, DerefMut, Reflect, Clone)]
177#[reflect(opaque)]
178#[reflect(Component, Debug, Clone)]
179pub struct CameraRenderGraph(pub InternedScheduleLabel);
180
181impl CameraRenderGraph {
182 #[inline]
184 pub fn new<T: ScheduleLabel>(schedule: T) -> Self {
185 Self(schedule.intern())
186 }
187
188 #[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 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 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 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 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
339pub 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 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 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#[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 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 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 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
700fn 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#[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 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#[derive(Component, Clone, Default, Reflect)]
781#[reflect(Default, Component, Clone)]
782pub struct TemporalJitter {
783 pub offset: Vec2,
785}
786
787impl TemporalJitter {
788 pub fn jitter_projection(&self, clip_from_view: &mut Mat4, view_size: Vec2) {
789 let mut jitter = (self.offset * vec2(2.0, -2.0)) / view_size;
791
792 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#[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#[derive(Clone, Resource, Default)]
827pub struct DirtySpecializations {
828 pub changed_renderables: MainEntityHashSet,
830
831 pub removed_renderables: MainEntityHashSet,
838
839 pub views: HashSet<RetainedViewEntity>,
844}
845
846impl DirtySpecializations {
847 pub fn must_wipe_specializations_for_view(&self, view: RetainedViewEntity) -> bool {
850 self.views.contains(&view)
851 }
852
853 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 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 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 None
882 }
883
884 pub fn iter_to_despecialize<'a>(&'a self) -> impl Iterator<Item = &'a MainEntity> {
887 self.changed_renderables
890 .iter()
891 .chain(self.removed_renderables.iter())
892 }
893
894 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 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 Either::Left(
954 render_visible_mesh_entities
955 .iter_visible()
956 .map(|(_, main_entity)| main_entity),
957 )
958 } else {
959 Either::Right(
961 self.changed_renderables
962 .iter()
963 .chain(self.removed_renderables.iter()),
964 )
965 })
966 }
967
968 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 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#[derive(Clone, Resource, Default, Deref, DerefMut)]
1034pub struct DirtyWireframeSpecializations(pub DirtySpecializations);
1035
1036pub 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
1044pub 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
1054pub 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
1067pub 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#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
1087pub enum DirtySpecializationSystems {
1088 Clear,
1091
1092 CheckForChanges,
1095
1096 CheckForRemovals,
1107}
1108
1109#[derive(Default, Deref, DerefMut)]
1120pub struct PendingQueues(pub HashMap<RetainedViewEntity, ViewPendingQueues>);
1121
1122#[derive(Default)]
1128pub struct ViewPendingQueues {
1129 pub current_frame: HashSet<(Entity, MainEntity)>,
1133
1134 pub prev_frame: HashSet<(Entity, MainEntity)>,
1139}
1140
1141impl PendingQueues {
1142 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 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}