diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 6e2c910e11760..d014acc6590b8 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1,5 +1,6 @@ use crate::{ camera::CameraProjection, + camera::{ManualTextureViewHandle, ManualTextureViews}, prelude::Image, render_asset::RenderAssets, render_resource::TextureView, @@ -26,7 +27,6 @@ use bevy_utils::{HashMap, HashSet}; use bevy_window::{ NormalizedWindowRef, PrimaryWindow, Window, WindowCreated, WindowRef, WindowResized, }; - use std::{borrow::Cow, ops::Range}; use wgpu::{BlendState, Extent3d, LoadOp, TextureFormat}; @@ -376,6 +376,9 @@ pub enum RenderTarget { Window(WindowRef), /// Image to which the camera's view is rendered. Image(Handle), + /// 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), } /// Normalized version of the render target. @@ -387,6 +390,9 @@ pub enum NormalizedRenderTarget { Window(NormalizedWindowRef), /// Image to which the camera's view is rendered. Image(Handle), + /// 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), } impl Default for RenderTarget { @@ -403,6 +409,7 @@ impl RenderTarget { .normalize(primary_window) .map(NormalizedRenderTarget::Window), RenderTarget::Image(handle) => Some(NormalizedRenderTarget::Image(handle.clone())), + RenderTarget::TextureView(id) => Some(NormalizedRenderTarget::TextureView(*id)), } } } @@ -412,6 +419,7 @@ impl NormalizedRenderTarget { &self, windows: &'a ExtractedWindows, images: &'a RenderAssets, + manual_texture_views: &'a ManualTextureViews, ) -> Option<&'a TextureView> { match self { NormalizedRenderTarget::Window(window_ref) => windows @@ -420,6 +428,9 @@ impl NormalizedRenderTarget { NormalizedRenderTarget::Image(image_handle) => { images.get(image_handle).map(|image| &image.texture_view) } + NormalizedRenderTarget::TextureView(id) => { + manual_texture_views.get(id).map(|tex| &tex.texture_view) + } } } @@ -428,6 +439,7 @@ impl NormalizedRenderTarget { &self, windows: &'a ExtractedWindows, images: &'a RenderAssets, + manual_texture_views: &'a ManualTextureViews, ) -> Option { match self { NormalizedRenderTarget::Window(window_ref) => windows @@ -436,6 +448,9 @@ impl NormalizedRenderTarget { NormalizedRenderTarget::Image(image_handle) => { images.get(image_handle).map(|image| image.texture_format) } + NormalizedRenderTarget::TextureView(id) => { + manual_texture_views.get(id).map(|tex| tex.format) + } } } @@ -443,6 +458,7 @@ impl NormalizedRenderTarget { &self, resolutions: impl IntoIterator, images: &Assets, + manual_texture_views: &ManualTextureViews, ) -> Option { match self { NormalizedRenderTarget::Window(window_ref) => resolutions @@ -463,6 +479,12 @@ impl NormalizedRenderTarget { scale_factor: 1.0, }) } + NormalizedRenderTarget::TextureView(id) => { + manual_texture_views.get(id).map(|tex| RenderTargetInfo { + physical_size: tex.size, + scale_factor: 1.0, + }) + } } } @@ -479,6 +501,7 @@ impl NormalizedRenderTarget { NormalizedRenderTarget::Image(image_handle) => { changed_image_handles.contains(&image_handle) } + NormalizedRenderTarget::TextureView(_) => true, } } } @@ -502,6 +525,7 @@ impl NormalizedRenderTarget { /// [`OrthographicProjection`]: crate::camera::OrthographicProjection /// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection /// [`Projection`]: crate::camera::Projection +#[allow(clippy::too_many_arguments)] pub fn camera_system( mut window_resized_events: EventReader, mut window_created_events: EventReader, @@ -509,6 +533,7 @@ pub fn camera_system( primary_window: Query>, windows: Query<(Entity, &Window)>, images: Res>, + manual_texture_views: Res, mut cameras: Query<(&mut Camera, &mut T)>, ) { let primary_window = primary_window.iter().next(); @@ -540,8 +565,11 @@ pub fn camera_system( || camera_projection.is_changed() || camera.computed.old_viewport_size != viewport_size { - camera.computed.target_info = - normalized_target.get_render_target_info(&windows, &images); + camera.computed.target_info = normalized_target.get_render_target_info( + &windows, + &images, + &manual_texture_views, + ); if let Some(size) = camera.logical_viewport_size() { camera_projection.update(size.x, size.y); camera.computed.projection_matrix = camera_projection.get_projection_matrix(); diff --git a/crates/bevy_render/src/camera/manual_texture_view.rs b/crates/bevy_render/src/camera/manual_texture_view.rs new file mode 100644 index 0000000000000..ca35c14b87842 --- /dev/null +++ b/crates/bevy_render/src/camera/manual_texture_view.rs @@ -0,0 +1,64 @@ +use crate::extract_resource::ExtractResource; +use crate::render_resource::TextureView; +use crate::texture::BevyDefault; +use bevy_ecs::system::Resource; +use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; +use bevy_math::UVec2; +use bevy_reflect::prelude::*; +use bevy_reflect::FromReflect; +use bevy_utils::HashMap; +use wgpu::TextureFormat; + +/// A unique id that corresponds to a specific [`ManualTextureView`] in the [`ManualTextureViews`] collection. +#[derive( + Default, + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Component, + Reflect, + FromReflect, +)] +#[reflect(Component, Default)] +pub struct ManualTextureViewHandle(pub u32); + +/// A manually managed [`TextureView`] for use as a [`crate::camera::RenderTarget`]. +#[derive(Debug, Clone, Component)] +pub struct ManualTextureView { + pub texture_view: TextureView, + pub size: UVec2, + pub format: TextureFormat, +} + +impl ManualTextureView { + pub fn with_default_format(texture_view: TextureView, size: UVec2) -> Self { + Self { + texture_view, + size, + format: TextureFormat::bevy_default(), + } + } +} + +/// Stores manually managed [`ManualTextureView`]s for use as a [`crate::camera::RenderTarget`]. +#[derive(Default, Clone, Resource, ExtractResource)] +pub struct ManualTextureViews(HashMap); + +impl std::ops::Deref for ManualTextureViews { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for ManualTextureViews { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 44d72cb03e886..3829d392c0311 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -1,13 +1,18 @@ #[allow(clippy::module_inception)] mod camera; mod camera_driver_node; +mod manual_texture_view; mod projection; pub use camera::*; pub use camera_driver_node::*; +pub use manual_texture_view::*; pub use projection::*; -use crate::{render_graph::RenderGraph, ExtractSchedule, Render, RenderApp, RenderSet}; +use crate::{ + extract_resource::ExtractResourcePlugin, render_graph::RenderGraph, ExtractSchedule, Render, + RenderApp, RenderSet, +}; use bevy_app::{App, Plugin}; use bevy_ecs::schedule::IntoSystemConfigs; @@ -22,9 +27,11 @@ impl Plugin for CameraPlugin { .register_type::() .register_type::() .register_type::() + .init_resource::() .add_plugin(CameraProjectionPlugin::::default()) .add_plugin(CameraProjectionPlugin::::default()) - .add_plugin(CameraProjectionPlugin::::default()); + .add_plugin(CameraProjectionPlugin::::default()) + .add_plugin(ExtractResourcePlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index c602dd7628f12..fd8d00ac15811 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -6,7 +6,7 @@ pub use visibility::*; pub use window::*; use crate::{ - camera::{ExtractedCamera, TemporalJitter}, + camera::{ExtractedCamera, ManualTextureViews, TemporalJitter}, extract_resource::{ExtractResource, ExtractResourcePlugin}, prelude::{Image, Shader}, render_asset::RenderAssets, @@ -374,13 +374,14 @@ fn prepare_view_targets( render_device: Res, mut texture_cache: ResMut, cameras: Query<(Entity, &ExtractedCamera, &ExtractedView)>, + manual_texture_views: Res, ) { let mut textures = HashMap::default(); for (entity, camera, view) in cameras.iter() { if let (Some(target_size), Some(target)) = (camera.physical_target_size, &camera.target) { if let (Some(out_texture_view), Some(out_texture_format)) = ( - target.get_texture_view(&windows, &images), - target.get_texture_format(&windows, &images), + target.get_texture_view(&windows, &images, &manual_texture_views), + target.get_texture_format(&windows, &images, &manual_texture_views), ) { let size = Extent3d { width: target_size.x,