diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 8fb0dc452bf74..2df4dbbd32895 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -20,14 +20,15 @@ use bevy_camera::{ primitives::Frustum, visibility::{self, RenderLayers, VisibleEntities}, Camera, Camera2d, Camera3d, CameraMainTextureUsages, CameraOutputMode, CameraUpdateSystems, - ClearColor, ClearColorConfig, Exposure, NormalizedRenderTarget, Projection, RenderTargetInfo, - Viewport, + ClearColor, ClearColorConfig, Exposure, ManualTextureViewHandle, NormalizedRenderTarget, + Projection, RenderTargetInfo, Viewport, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, component::Component, entity::{ContainsEntity, Entity}, + error::BevyError, event::EventReader, lifecycle::HookContext, prelude::With, @@ -167,7 +168,7 @@ pub trait NormalizedRenderTargetExt { resolutions: impl IntoIterator, images: &Assets, manual_texture_views: &ManualTextureViews, - ) -> Option; + ) -> Result; // Check if this render target is contained in the given changed windows or images. fn is_changed( @@ -222,7 +223,7 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { resolutions: impl IntoIterator, images: &Assets, manual_texture_views: &ManualTextureViews, - ) -> Option { + ) -> Result { match self { NormalizedRenderTarget::Window(window_ref) => resolutions .into_iter() @@ -230,20 +231,26 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { .map(|(_, window)| RenderTargetInfo { physical_size: window.physical_size(), scale_factor: window.resolution.scale_factor(), + }) + .ok_or(MissingRenderTargetInfoError::Window { + window: window_ref.entity(), }), - NormalizedRenderTarget::Image(image_target) => { - let image = images.get(&image_target.handle)?; - Some(RenderTargetInfo { + NormalizedRenderTarget::Image(image_target) => images + .get(&image_target.handle) + .map(|image| RenderTargetInfo { physical_size: image.size(), scale_factor: image_target.scale_factor.0, }) - } - NormalizedRenderTarget::TextureView(id) => { - manual_texture_views.get(id).map(|tex| RenderTargetInfo { + .ok_or(MissingRenderTargetInfoError::Image { + image: image_target.handle.id(), + }), + NormalizedRenderTarget::TextureView(id) => manual_texture_views + .get(id) + .map(|tex| RenderTargetInfo { physical_size: tex.size, scale_factor: 1.0, }) - } + .ok_or(MissingRenderTargetInfoError::TextureView { texture_view: *id }), } } @@ -265,6 +272,18 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { } } +#[derive(Debug, thiserror::Error)] +pub enum MissingRenderTargetInfoError { + #[error("RenderTarget::Window missing ({window:?}): Make sure the provided entity has a Window component.")] + Window { window: Entity }, + #[error("RenderTarget::Image missing ({image:?}): Make sure the Image's usages include RenderAssetUsages::MAIN_WORLD.")] + Image { image: AssetId }, + #[error("RenderTarget::TextureView missing ({texture_view:?}): make sure the texture view handle was not removed.")] + TextureView { + texture_view: ManualTextureViewHandle, + }, +} + /// System in charge of updating a [`Camera`] when its window or projection changes. /// /// The system detects window creation, resize, and scale factor change events to update the camera @@ -287,7 +306,7 @@ pub fn camera_system( images: Res>, manual_texture_views: Res, mut cameras: Query<(&mut Camera, &mut Projection)>, -) { +) -> Result<(), BevyError> { let primary_window = primary_window.iter().next(); let mut changed_window_ids = >::default(); @@ -320,25 +339,23 @@ pub fn camera_system( || camera.computed.old_viewport_size != viewport_size || camera.computed.old_sub_camera_view != camera.sub_camera_view) { - let new_computed_target_info = - normalized_target.get_render_target_info(windows, &images, &manual_texture_views); + let new_computed_target_info = normalized_target.get_render_target_info( + windows, + &images, + &manual_texture_views, + )?; // Check for the scale factor changing, and resize the viewport if needed. // This can happen when the window is moved between monitors with different DPIs. // Without this, the viewport will take a smaller portion of the window moved to // a higher DPI monitor. if normalized_target.is_changed(&scale_factor_changed_window_ids, &HashSet::default()) - && let (Some(new_scale_factor), Some(old_scale_factor)) = ( - new_computed_target_info - .as_ref() - .map(|info| info.scale_factor), - camera - .computed - .target_info - .as_ref() - .map(|info| info.scale_factor), - ) + && let Some(old_scale_factor) = camera + .computed + .target_info + .as_ref() + .map(|info| info.scale_factor) { - let resize_factor = new_scale_factor / old_scale_factor; + let resize_factor = new_computed_target_info.scale_factor / old_scale_factor; if let Some(ref mut viewport) = camera.viewport { let resize = |vec: UVec2| (vec.as_vec2() * resize_factor).as_uvec2(); viewport.physical_position = resize(viewport.physical_position); @@ -350,12 +367,9 @@ pub fn camera_system( // arguments due to a sudden change on the window size to a lower value. // If the size of the window is lower, the viewport will match that lower value. if let Some(viewport) = &mut camera.viewport { - let target_info = &new_computed_target_info; - if let Some(target) = target_info { - viewport.clamp_to_size(target.physical_size); - } + viewport.clamp_to_size(new_computed_target_info.physical_size); } - camera.computed.target_info = new_computed_target_info; + camera.computed.target_info = Some(new_computed_target_info); if let Some(size) = camera.logical_viewport_size() && size.x != 0.0 && size.y != 0.0 @@ -376,6 +390,7 @@ pub fn camera_system( camera.computed.old_sub_camera_view = camera.sub_camera_view; } } + Ok(()) } #[derive(Component, Debug)] diff --git a/release-content/migration-guides/render_target_info_error.md b/release-content/migration-guides/render_target_info_error.md new file mode 100644 index 0000000000000..e930a54e6353d --- /dev/null +++ b/release-content/migration-guides/render_target_info_error.md @@ -0,0 +1,11 @@ +--- +title: "RenderTarget error handling" +pull_requests: [20503] +--- + +`NormalizedRenderTargetExt::get_render_target_info` now returns a `Result`, +with the `Err` variant indicating which render target (image, window, etc) +failed to load its metadata. + +This should mostly be treated as a hard error, since it indicates the rendering +state of the app is broken.