Incorporate viewport position into clamping in camera_system (#18242)

# Objective

In its existing form, the clamping that's done in `camera_system`
doesn't work well when the `physical_position` of the associated
viewport is nonzero. In such cases, it may produce invalid viewport
rectangles (i.e. not lying inside the render target), which may result
in crashes during the render pass.

The goal of this PR is to eliminate this possibility by making the
clamping behavior always result in a valid viewport rectangle when
possible.

## Solution

Incorporate the `physical_position` information into the clamping
behavior. In particular, always cut off enough so that it's contained in
the render target rather than clamping it to the same dimensions as the
target itself. In weirder situations, still try to produce a valid
viewport rectangle to avoid crashes.

## Testing

Tested these changes on my work branch where I encountered the crash.
This commit is contained in:
Matty Weatherley 2025-03-17 14:10:11 -04:00 committed by GitHub
parent fecf2d2591
commit af61ec2bc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -76,6 +76,42 @@ impl Default for Viewport {
}
}
impl Viewport {
/// Cut the viewport rectangle so that it lies inside a rectangle of the
/// given size.
///
/// If either of the viewport's position coordinates lies outside the given
/// dimensions, it will be moved just inside first. If either of the given
/// dimensions is zero, the position and size of the viewport rectangle will
/// both be set to zero in that dimension.
pub fn clamp_to_size(&mut self, size: UVec2) {
// If the origin of the viewport rect is outside, then adjust so that
// it's just barely inside. Then, cut off the part that is outside.
if self.physical_size.x + self.physical_position.x > size.x {
if self.physical_position.x < size.x {
self.physical_size.x = size.x - self.physical_position.x;
} else if size.x > 0 {
self.physical_position.x = size.x - 1;
self.physical_size.x = 1;
} else {
self.physical_position.x = 0;
self.physical_size.x = 0;
}
}
if self.physical_size.y + self.physical_position.y > size.y {
if self.physical_position.y < size.y {
self.physical_size.y = size.y - self.physical_position.y;
} else if size.y > 0 {
self.physical_position.y = size.y - 1;
self.physical_size.y = 1;
} else {
self.physical_position.y = 0;
self.physical_size.y = 0;
}
}
}
}
/// Settings to define a camera sub view.
///
/// When [`Camera::sub_camera_view`] is `Some`, only the sub-section of the
@ -991,12 +1027,7 @@ pub fn camera_system(
if let Some(viewport) = &mut camera.viewport {
let target_info = &new_computed_target_info;
if let Some(target) = target_info {
if viewport.physical_size.x > target.physical_size.x {
viewport.physical_size.x = target.physical_size.x;
}
if viewport.physical_size.y > target.physical_size.y {
viewport.physical_size.y = target.physical_size.y;
}
viewport.clamp_to_size(target.physical_size);
}
}
camera.computed.target_info = new_computed_target_info;