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#[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#[derive(Component, Debug, Deref, DerefMut, Reflect, Clone)]
179#[reflect(opaque)]
180#[reflect(Component, Debug, Clone)]
181pub struct CameraRenderGraph(pub InternedScheduleLabel);
182
183impl CameraRenderGraph {
184 #[inline]
186 pub fn new<T: ScheduleLabel>(schedule: T) -> Self {
187 Self(schedule.intern())
188 }
189
190 #[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 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 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 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 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
342pub 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 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 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#[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 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 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 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
705fn 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#[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 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 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#[derive(Component, Clone, Default, Reflect)]
792#[reflect(Default, Component, Clone)]
793pub struct TemporalJitter {
794 pub offset: Vec2,
796}
797
798impl TemporalJitter {
799 pub fn jitter_projection(&self, clip_from_view: &mut Mat4, view_size: Vec2) {
800 let mut jitter = (self.offset * vec2(2.0, -2.0)) / view_size;
802
803 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#[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#[derive(Clone, Resource, Default)]
838pub struct DirtySpecializations {
839 pub changed_renderables: MainEntityHashSet,
841
842 pub removed_renderables: MainEntityHashSet,
849
850 pub views: HashSet<RetainedViewEntity>,
855}
856
857impl DirtySpecializations {
858 pub fn must_wipe_specializations_for_view(&self, view: RetainedViewEntity) -> bool {
861 self.views.contains(&view)
862 }
863
864 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 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 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 None
893 }
894
895 pub fn iter_to_despecialize<'a>(&'a self) -> impl Iterator<Item = &'a MainEntity> {
898 self.changed_renderables
901 .iter()
902 .chain(self.removed_renderables.iter())
903 }
904
905 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 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 Either::Left(
965 render_visible_mesh_entities
966 .iter_visible()
967 .map(|(_, main_entity)| main_entity),
968 )
969 } else {
970 Either::Right(
972 self.changed_renderables
973 .iter()
974 .chain(self.removed_renderables.iter()),
975 )
976 })
977 }
978
979 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 (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#[derive(Clone, Resource, Default, Deref, DerefMut)]
1039pub struct DirtyWireframeSpecializations(pub DirtySpecializations);
1040
1041pub 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
1049pub 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
1059pub 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
1072pub 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#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
1092pub enum DirtySpecializationSystems {
1093 Clear,
1096
1097 CheckForChanges,
1100
1101 CheckForRemovals,
1112}
1113
1114#[derive(Default, Deref, DerefMut)]
1125pub struct PendingQueues(pub HashMap<RetainedViewEntity, ViewPendingQueues>);
1126
1127#[derive(Default)]
1133pub struct ViewPendingQueues {
1134 pub current_frame: HashSet<(Entity, MainEntity)>,
1138
1139 pub prev_frame: HashSet<(Entity, MainEntity)>,
1144}
1145
1146impl PendingQueues {
1147 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 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 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}