Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 45 additions & 30 deletions crates/bevy_render/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -167,7 +168,7 @@ pub trait NormalizedRenderTargetExt {
resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
images: &Assets<Image>,
manual_texture_views: &ManualTextureViews,
) -> Option<RenderTargetInfo>;
) -> Result<RenderTargetInfo, MissingRenderTargetInfoError>;

// Check if this render target is contained in the given changed windows or images.
fn is_changed(
Expand Down Expand Up @@ -222,28 +223,34 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget {
resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
images: &Assets<Image>,
manual_texture_views: &ManualTextureViews,
) -> Option<RenderTargetInfo> {
) -> Result<RenderTargetInfo, MissingRenderTargetInfoError> {
match self {
NormalizedRenderTarget::Window(window_ref) => resolutions
.into_iter()
.find(|(entity, _)| *entity == window_ref.entity())
.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 }),
}
}

Expand All @@ -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<Image> },
#[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
Expand All @@ -287,7 +306,7 @@ pub fn camera_system(
images: Res<Assets<Image>>,
manual_texture_views: Res<ManualTextureViews>,
mut cameras: Query<(&mut Camera, &mut Projection)>,
) {
) -> Result<(), BevyError> {
let primary_window = primary_window.iter().next();

let mut changed_window_ids = <HashSet<_>>::default();
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -376,6 +390,7 @@ pub fn camera_system(
camera.computed.old_sub_camera_view = camera.sub_camera_view;
}
}
Ok(())
}

#[derive(Component, Debug)]
Expand Down
11 changes: 11 additions & 0 deletions release-content/migration-guides/render_target_info_error.md
Original file line number Diff line number Diff line change
@@ -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.
Loading