From 39f9e07b5f953bff2ea549fa40956c444d8f6ad9 Mon Sep 17 00:00:00 2001 From: Martin Svanberg Date: Tue, 17 Dec 2024 21:21:40 +0100 Subject: [PATCH] Support scale factor for image render targets (#16796) # 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`. You can call `handle.into()` to construct an `ImageRenderTarget` using the same settings as before. --- crates/bevy_render/src/camera/camera.rs | 57 +++++++++++++------ .../bevy_render/src/view/window/screenshot.rs | 6 +- examples/2d/pixel_grid_snap.rs | 2 +- examples/app/headless_renderer.rs | 2 +- examples/ui/render_ui_to_texture.rs | 2 +- 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 96a13fd5d504d..d552000d0b5fd 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -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}; @@ -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(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, + /// 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> for RenderTarget { + fn from(handle: Handle) -> Self { + Self::Image(handle.into()) + } +} + +impl From> for ImageRenderTarget { + fn from(handle: Handle) -> Self { + Self { + handle, + scale_factor: FloatOrd(1.0), + } + } +} + impl Default for RenderTarget { fn default() -> Self { Self::Window(Default::default()) @@ -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(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), @@ -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> { - if let Self::Image(handle) = self { - Some(handle) + if let Self::Image(image_target) = self { + Some(&image_target.handle) } else { None } @@ -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) } @@ -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) } @@ -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) => { @@ -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, } diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 5bc194878c4be..aab9b08c680d7 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -95,7 +95,7 @@ impl Screenshot { /// Capture a screenshot of the provided render target image. pub fn image(image: Handle) -> Self { - Self(RenderTarget::Image(image)) + Self(RenderTarget::Image(image.into())) } /// Capture a screenshot of the provided manual texture view. @@ -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; }; @@ -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; }; diff --git a/examples/2d/pixel_grid_snap.rs b/examples/2d/pixel_grid_snap.rs index 3d9de7b84d476..9d701e003cf2b 100644 --- a/examples/2d/pixel_grid_snap.rs +++ b/examples/2d/pixel_grid_snap.rs @@ -117,7 +117,7 @@ fn setup_camera(mut commands: Commands, mut images: ResMut>) { 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, diff --git a/examples/app/headless_renderer.rs b/examples/app/headless_renderer.rs index 2518a55c55515..3c8865148e2c1 100644 --- a/examples/app/headless_renderer.rs +++ b/examples/app/headless_renderer.rs @@ -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 diff --git a/examples/ui/render_ui_to_texture.rs b/examples/ui/render_ui_to_texture.rs index 69083dd2ece14..119230c808073 100644 --- a/examples/ui/render_ui_to_texture.rs +++ b/examples/ui/render_ui_to_texture.rs @@ -57,7 +57,7 @@ fn setup( .spawn(( Camera2d, Camera { - target: RenderTarget::Image(image_handle.clone()), + target: RenderTarget::Image(image_handle.clone().into()), ..default() }, ))