Skip to content

Commit

Permalink
Support scale factor for image render targets (#16796)
Browse files Browse the repository at this point in the history
# Objective

I have something of a niche use case. I have a camera rendering pixel
art with a scale factor set, and another camera that renders to an
off-screen texture which is supposed to match the main camera exactly.
However, when computing camera target info, Bevy [hardcodes a scale
factor of
1.0](https://github.com/bevyengine/bevy/blob/116c2b02fe8a7589d1777af7dabd84dc756b5b0d/crates/bevy_render/src/camera/camera.rs#L828)
for image targets which means that my main camera and my image target
camera get different `OrthographicProjections` calculated.

## Solution

This PR adds an `ImageRenderTarget` struct which allows scale factors to
be specified.

## Testing

I tested the affected examples on macOS and they still work. This is an
additive change and should not break any existing code, apart from what
is trivially fixable by following compiler error messages.

---

## Migration Guide

`RenderTarget::Image` now takes an `ImageRenderTarget` instead of a
`Handle<Image>`. You can call `handle.into()` to construct an
`ImageRenderTarget` using the same settings as before.
  • Loading branch information
msvbg authored Dec 17, 2024
1 parent d51dee6 commit 39f9e07
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 22 deletions.
57 changes: 41 additions & 16 deletions crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use bevy_ecs::{
world::DeferredWorld,
};
use bevy_image::Image;
use bevy_math::{ops, vec2, Dir3, Mat4, Ray3d, Rect, URect, UVec2, UVec4, Vec2, Vec3};
use bevy_math::{ops, vec2, Dir3, FloatOrd, Mat4, Ray3d, Rect, URect, UVec2, UVec4, Vec2, Vec3};
use bevy_reflect::prelude::*;
use bevy_render_macros::ExtractComponent;
use bevy_transform::components::{GlobalTransform, Transform};
Expand Down Expand Up @@ -718,12 +718,37 @@ pub enum RenderTarget {
/// Window to which the camera's view is rendered.
Window(WindowRef),
/// Image to which the camera's view is rendered.
Image(Handle<Image>),
Image(ImageRenderTarget),
/// Texture View to which the camera's view is rendered.
/// Useful when the texture view needs to be created outside of Bevy, for example OpenXR.
TextureView(ManualTextureViewHandle),
}

/// A render target that renders to an [`Image`].
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ImageRenderTarget {
/// The image to render to.
pub handle: Handle<Image>,
/// The scale factor of the render target image, corresponding to the scale
/// factor for a window target. This should almost always be 1.0.
pub scale_factor: FloatOrd,
}

impl From<Handle<Image>> for RenderTarget {
fn from(handle: Handle<Image>) -> Self {
Self::Image(handle.into())
}
}

impl From<Handle<Image>> for ImageRenderTarget {
fn from(handle: Handle<Image>) -> Self {
Self {
handle,
scale_factor: FloatOrd(1.0),
}
}
}

impl Default for RenderTarget {
fn default() -> Self {
Self::Window(Default::default())
Expand All @@ -738,7 +763,7 @@ pub enum NormalizedRenderTarget {
/// Window to which the camera's view is rendered.
Window(NormalizedWindowRef),
/// Image to which the camera's view is rendered.
Image(Handle<Image>),
Image(ImageRenderTarget),
/// Texture View to which the camera's view is rendered.
/// Useful when the texture view needs to be created outside of Bevy, for example OpenXR.
TextureView(ManualTextureViewHandle),
Expand All @@ -759,8 +784,8 @@ impl RenderTarget {
/// Get a handle to the render target's image,
/// or `None` if the render target is another variant.
pub fn as_image(&self) -> Option<&Handle<Image>> {
if let Self::Image(handle) = self {
Some(handle)
if let Self::Image(image_target) = self {
Some(&image_target.handle)
} else {
None
}
Expand All @@ -778,9 +803,9 @@ impl NormalizedRenderTarget {
NormalizedRenderTarget::Window(window_ref) => windows
.get(&window_ref.entity())
.and_then(|window| window.swap_chain_texture_view.as_ref()),
NormalizedRenderTarget::Image(image_handle) => {
images.get(image_handle).map(|image| &image.texture_view)
}
NormalizedRenderTarget::Image(image_target) => images
.get(&image_target.handle)
.map(|image| &image.texture_view),
NormalizedRenderTarget::TextureView(id) => {
manual_texture_views.get(id).map(|tex| &tex.texture_view)
}
Expand All @@ -798,9 +823,9 @@ impl NormalizedRenderTarget {
NormalizedRenderTarget::Window(window_ref) => windows
.get(&window_ref.entity())
.and_then(|window| window.swap_chain_texture_format),
NormalizedRenderTarget::Image(image_handle) => {
images.get(image_handle).map(|image| image.texture_format)
}
NormalizedRenderTarget::Image(image_target) => images
.get(&image_target.handle)
.map(|image| image.texture_format),
NormalizedRenderTarget::TextureView(id) => {
manual_texture_views.get(id).map(|tex| tex.format)
}
Expand All @@ -821,11 +846,11 @@ impl NormalizedRenderTarget {
physical_size: window.physical_size(),
scale_factor: window.resolution.scale_factor(),
}),
NormalizedRenderTarget::Image(image_handle) => {
let image = images.get(image_handle)?;
NormalizedRenderTarget::Image(image_target) => {
let image = images.get(&image_target.handle)?;
Some(RenderTargetInfo {
physical_size: image.size(),
scale_factor: 1.0,
scale_factor: image_target.scale_factor.0,
})
}
NormalizedRenderTarget::TextureView(id) => {
Expand All @@ -847,8 +872,8 @@ impl NormalizedRenderTarget {
NormalizedRenderTarget::Window(window_ref) => {
changed_window_ids.contains(&window_ref.entity())
}
NormalizedRenderTarget::Image(image_handle) => {
changed_image_handles.contains(&image_handle.id())
NormalizedRenderTarget::Image(image_target) => {
changed_image_handles.contains(&image_target.handle.id())
}
NormalizedRenderTarget::TextureView(_) => true,
}
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_render/src/view/window/screenshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl Screenshot {

/// Capture a screenshot of the provided render target image.
pub fn image(image: Handle<Image>) -> Self {
Self(RenderTarget::Image(image))
Self(RenderTarget::Image(image.into()))
}

/// Capture a screenshot of the provided manual texture view.
Expand Down Expand Up @@ -297,7 +297,7 @@ fn prepare_screenshots(
);
}
NormalizedRenderTarget::Image(image) => {
let Some(gpu_image) = images.get(image) else {
let Some(gpu_image) = images.get(&image.handle) else {
warn!("Unknown image for screenshot, skipping: {:?}", image);
continue;
};
Expand Down Expand Up @@ -533,7 +533,7 @@ pub(crate) fn submit_screenshot_commands(world: &World, encoder: &mut CommandEnc
);
}
NormalizedRenderTarget::Image(image) => {
let Some(gpu_image) = gpu_images.get(image) else {
let Some(gpu_image) = gpu_images.get(&image.handle) else {
warn!("Unknown image for screenshot, skipping: {:?}", image);
continue;
};
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/pixel_grid_snap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ fn setup_camera(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
Camera {
// render before the "main pass" camera
order: -1,
target: RenderTarget::Image(image_handle.clone()),
target: RenderTarget::Image(image_handle.clone().into()),
..default()
},
Msaa::Off,
Expand Down
2 changes: 1 addition & 1 deletion examples/app/headless_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ fn setup_render_target(

scene_controller.state = SceneState::Render(pre_roll_frames);
scene_controller.name = scene_name;
RenderTarget::Image(render_target_image_handle)
RenderTarget::Image(render_target_image_handle.into())
}

/// Setups image saver
Expand Down
2 changes: 1 addition & 1 deletion examples/ui/render_ui_to_texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ fn setup(
.spawn((
Camera2d,
Camera {
target: RenderTarget::Image(image_handle.clone()),
target: RenderTarget::Image(image_handle.clone().into()),
..default()
},
))
Expand Down

0 comments on commit 39f9e07

Please sign in to comment.