pub mod visibility; pub mod window; use bevy_asset::{load_internal_asset, Handle}; pub use visibility::*; pub use window::*; use crate::{ camera::{ ClearColor, ClearColorConfig, ExtractedCamera, ManualTextureViews, MipBias, TemporalJitter, }, extract_resource::{ExtractResource, ExtractResourcePlugin}, prelude::{Image, Shader}, primitives::Frustum, render_asset::RenderAssets, render_phase::ViewRangefinder3d, render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, CachedTexture, ColorAttachment, DepthAttachment, TextureCache}, Render, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_math::{Mat4, UVec4, Vec3, Vec4, Vec4Swizzles}; use bevy_reflect::Reflect; use bevy_transform::components::GlobalTransform; use bevy_utils::HashMap; use std::sync::{ atomic::{AtomicUsize, Ordering}, Arc, }; use wgpu::{ Extent3d, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }; pub const VIEW_TYPE_HANDLE: Handle = Handle::weak_from_u128(15421373904451797197); pub struct ViewPlugin; impl Plugin for ViewPlugin { fn build(&self, app: &mut App) { load_internal_asset!(app, VIEW_TYPE_HANDLE, "view.wgsl", Shader::from_wgsl); app.register_type::() .register_type::() .register_type::() .register_type::() .register_type::() .register_type::() .register_type::() .register_type::() .init_resource::() // NOTE: windows.is_changed() handles cases where a window was resized .add_plugins((ExtractResourcePlugin::::default(), VisibilityPlugin)); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.init_resource::().add_systems( Render, ( prepare_view_targets .in_set(RenderSet::ManageViews) .after(prepare_windows) .after(crate::render_asset::prepare_assets::) .ambiguous_with(crate::camera::sort_cameras), // doesn't use `sorted_camera_index_for_target` prepare_view_uniforms.in_set(RenderSet::PrepareResources), ), ); } } } /// Configuration resource for [Multi-Sample Anti-Aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing). /// /// The number of samples to run for Multi-Sample Anti-Aliasing. Higher numbers result in /// smoother edges. /// Defaults to 4 samples. /// /// Note that web currently only supports 1 or 4 samples. /// /// # Example /// ``` /// # use bevy_app::prelude::App; /// # use bevy_render::prelude::Msaa; /// App::new() /// .insert_resource(Msaa::default()) /// .run(); /// ``` #[derive( Resource, Default, Clone, Copy, ExtractResource, Reflect, PartialEq, PartialOrd, Debug, )] #[reflect(Resource)] pub enum Msaa { Off = 1, Sample2 = 2, #[default] Sample4 = 4, Sample8 = 8, } impl Msaa { #[inline] pub fn samples(&self) -> u32 { *self as u32 } } #[derive(Component)] pub struct ExtractedView { pub projection: Mat4, pub transform: GlobalTransform, // The view-projection matrix. When provided it is used instead of deriving it from // `projection` and `transform` fields, which can be helpful in cases where numerical // stability matters and there is a more direct way to derive the view-projection matrix. pub view_projection: Option, pub hdr: bool, // uvec4(origin.x, origin.y, width, height) pub viewport: UVec4, pub color_grading: ColorGrading, } impl ExtractedView { /// Creates a 3D rangefinder for a view pub fn rangefinder3d(&self) -> ViewRangefinder3d { ViewRangefinder3d::from_view_matrix(&self.transform.compute_matrix()) } } /// Configures basic color grading parameters to adjust the image appearance. Grading is applied just before/after tonemapping for a given [`Camera`](crate::camera::Camera) entity. #[derive(Component, Reflect, Debug, Copy, Clone, ShaderType)] #[reflect(Component)] pub struct ColorGrading { /// Exposure value (EV) offset, measured in stops. pub exposure: f32, /// Non-linear luminance adjustment applied before tonemapping. y = pow(x, gamma) pub gamma: f32, /// Saturation adjustment applied before tonemapping. /// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image /// with luminance defined by ITU-R BT.709. /// Values above 1.0 increase saturation. pub pre_saturation: f32, /// Saturation adjustment applied after tonemapping. /// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image /// with luminance defined by ITU-R BT.709 /// Values above 1.0 increase saturation. pub post_saturation: f32, } impl Default for ColorGrading { fn default() -> Self { Self { exposure: 0.0, gamma: 1.0, pre_saturation: 1.0, post_saturation: 1.0, } } } #[derive(Clone, ShaderType)] pub struct ViewUniform { view_proj: Mat4, unjittered_view_proj: Mat4, inverse_view_proj: Mat4, view: Mat4, inverse_view: Mat4, projection: Mat4, inverse_projection: Mat4, world_position: Vec3, // viewport(x_origin, y_origin, width, height) viewport: Vec4, frustum: [Vec4; 6], color_grading: ColorGrading, mip_bias: f32, render_layers: u32, } #[derive(Resource, Default)] pub struct ViewUniforms { pub uniforms: DynamicUniformBuffer, } #[derive(Component)] pub struct ViewUniformOffset { pub offset: u32, } #[derive(Component)] pub struct ViewTarget { main_textures: MainTargetTextures, main_texture_format: TextureFormat, /// 0 represents `main_textures.a`, 1 represents `main_textures.b` /// This is shared across view targets with the same render target main_texture: Arc, out_texture: TextureView, out_texture_format: TextureFormat, } pub struct PostProcessWrite<'a> { pub source: &'a TextureView, pub destination: &'a TextureView, } impl ViewTarget { pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float; /// Retrieve this target's main texture's color attachment. pub fn get_color_attachment(&self) -> RenderPassColorAttachment { if self.main_texture.load(Ordering::SeqCst) == 0 { self.main_textures.a.get_attachment() } else { self.main_textures.b.get_attachment() } } /// Retrieve this target's "unsampled" main texture's color attachment. pub fn get_unsampled_color_attachment(&self) -> RenderPassColorAttachment { if self.main_texture.load(Ordering::SeqCst) == 0 { self.main_textures.a.get_unsampled_attachment() } else { self.main_textures.b.get_unsampled_attachment() } } /// The "main" unsampled texture. pub fn main_texture(&self) -> &Texture { if self.main_texture.load(Ordering::SeqCst) == 0 { &self.main_textures.a.texture.texture } else { &self.main_textures.b.texture.texture } } /// The _other_ "main" unsampled texture. /// In most cases you should use [`Self::main_texture`] instead and never this. /// The textures will naturally be swapped when [`Self::post_process_write`] is called. /// /// A use case for this is to be able to prepare a bind group for all main textures /// ahead of time. pub fn main_texture_other(&self) -> &Texture { if self.main_texture.load(Ordering::SeqCst) == 0 { &self.main_textures.b.texture.texture } else { &self.main_textures.a.texture.texture } } /// The "main" unsampled texture. pub fn main_texture_view(&self) -> &TextureView { if self.main_texture.load(Ordering::SeqCst) == 0 { &self.main_textures.a.texture.default_view } else { &self.main_textures.b.texture.default_view } } /// The _other_ "main" unsampled texture view. /// In most cases you should use [`Self::main_texture_view`] instead and never this. /// The textures will naturally be swapped when [`Self::post_process_write`] is called. /// /// A use case for this is to be able to prepare a bind group for all main textures /// ahead of time. pub fn main_texture_other_view(&self) -> &TextureView { if self.main_texture.load(Ordering::SeqCst) == 0 { &self.main_textures.b.texture.default_view } else { &self.main_textures.a.texture.default_view } } /// The "main" sampled texture. pub fn sampled_main_texture(&self) -> Option<&Texture> { self.main_textures .a .resolve_target .as_ref() .map(|sampled| &sampled.texture) } /// The "main" sampled texture view. pub fn sampled_main_texture_view(&self) -> Option<&TextureView> { self.main_textures .a .resolve_target .as_ref() .map(|sampled| &sampled.default_view) } #[inline] pub fn main_texture_format(&self) -> TextureFormat { self.main_texture_format } /// Returns `true` if and only if the main texture is [`Self::TEXTURE_FORMAT_HDR`] #[inline] pub fn is_hdr(&self) -> bool { self.main_texture_format == ViewTarget::TEXTURE_FORMAT_HDR } /// The final texture this view will render to. #[inline] pub fn out_texture(&self) -> &TextureView { &self.out_texture } /// The format of the final texture this view will render to #[inline] pub fn out_texture_format(&self) -> TextureFormat { self.out_texture_format } /// This will start a new "post process write", which assumes that the caller /// will write the [`PostProcessWrite`]'s `source` to the `destination`. /// /// `source` is the "current" main texture. This will internally flip this /// [`ViewTarget`]'s main texture to the `destination` texture, so the caller /// _must_ ensure `source` is copied to `destination`, with or without modifications. /// Failing to do so will cause the current main texture information to be lost. pub fn post_process_write(&self) -> PostProcessWrite { let old_is_a_main_texture = self.main_texture.fetch_xor(1, Ordering::SeqCst); // if the old main texture is a, then the post processing must write from a to b if old_is_a_main_texture == 0 { self.main_textures.b.mark_as_cleared(); PostProcessWrite { source: &self.main_textures.a.texture.default_view, destination: &self.main_textures.b.texture.default_view, } } else { self.main_textures.a.mark_as_cleared(); PostProcessWrite { source: &self.main_textures.b.texture.default_view, destination: &self.main_textures.a.texture.default_view, } } } } #[derive(Component)] pub struct ViewDepthTexture { pub texture: Texture, attachment: DepthAttachment, } impl ViewDepthTexture { pub fn new(texture: CachedTexture, clear_value: Option) -> Self { Self { texture: texture.texture, attachment: DepthAttachment::new(texture.default_view, clear_value), } } pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment { self.attachment.get_attachment(store) } pub fn view(&self) -> &TextureView { &self.attachment.view } } pub fn prepare_view_uniforms( mut commands: Commands, render_device: Res, render_queue: Res, mut view_uniforms: ResMut, views: Query<( Entity, &ExtractedView, Option<&Frustum>, Option<&TemporalJitter>, Option<&MipBias>, Option<&RenderLayers>, )>, ) { let view_iter = views.iter(); let view_count = view_iter.len(); let Some(mut writer) = view_uniforms .uniforms .get_writer(view_count, &render_device, &render_queue) else { return; }; for (entity, camera, frustum, temporal_jitter, mip_bias, maybe_layers) in &views { let viewport = camera.viewport.as_vec4(); let unjittered_projection = camera.projection; let mut projection = unjittered_projection; if let Some(temporal_jitter) = temporal_jitter { temporal_jitter.jitter_projection(&mut projection, viewport.zw()); } let inverse_projection = projection.inverse(); let view = camera.transform.compute_matrix(); let inverse_view = view.inverse(); let view_proj = if temporal_jitter.is_some() { projection * inverse_view } else { camera .view_projection .unwrap_or_else(|| projection * inverse_view) }; // Map Frustum type to shader array, 6> let frustum = frustum .map(|frustum| frustum.half_spaces.map(|h| h.normal_d())) .unwrap_or([Vec4::ZERO; 6]); let view_uniforms = ViewUniformOffset { offset: writer.write(&ViewUniform { view_proj, unjittered_view_proj: unjittered_projection * inverse_view, inverse_view_proj: view * inverse_projection, view, inverse_view, projection, inverse_projection, world_position: camera.transform.translation(), viewport, frustum, color_grading: camera.color_grading, mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0, render_layers: maybe_layers.copied().unwrap_or_default().bits(), }), }; commands.entity(entity).insert(view_uniforms); } } #[derive(Clone)] struct MainTargetTextures { a: ColorAttachment, b: ColorAttachment, /// 0 represents `main_textures.a`, 1 represents `main_textures.b` /// This is shared across view targets with the same render target main_texture: Arc, } #[allow(clippy::too_many_arguments)] fn prepare_view_targets( mut commands: Commands, windows: Res, images: Res>, msaa: Res, clear_color_global: Res, 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, &manual_texture_views), target.get_texture_format(&windows, &images, &manual_texture_views), ) { let size = Extent3d { width: target_size.x, height: target_size.y, depth_or_array_layers: 1, }; let main_texture_format = if view.hdr { ViewTarget::TEXTURE_FORMAT_HDR } else { TextureFormat::bevy_default() }; let clear_color = match camera.clear_color { ClearColorConfig::Custom(color) => color, _ => clear_color_global.0, }; let (a, b, sampled) = textures .entry((camera.target.clone(), view.hdr)) .or_insert_with(|| { let descriptor = TextureDescriptor { label: None, size, mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, format: main_texture_format, usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_SRC, view_formats: match main_texture_format { TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb], TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb], _ => &[], }, }; let a = texture_cache.get( &render_device, TextureDescriptor { label: Some("main_texture_a"), ..descriptor }, ); let b = texture_cache.get( &render_device, TextureDescriptor { label: Some("main_texture_b"), ..descriptor }, ); let sampled = if msaa.samples() > 1 { let sampled = texture_cache.get( &render_device, TextureDescriptor { label: Some("main_texture_sampled"), size, mip_level_count: 1, sample_count: msaa.samples(), dimension: TextureDimension::D2, format: main_texture_format, usage: TextureUsages::RENDER_ATTACHMENT, view_formats: descriptor.view_formats, }, ); Some(sampled) } else { None }; (a, b, sampled) }); let main_textures = MainTargetTextures { a: ColorAttachment::new(a.clone(), sampled.clone(), clear_color), b: ColorAttachment::new(b.clone(), sampled.clone(), clear_color), main_texture: Arc::new(AtomicUsize::new(0)), }; commands.entity(entity).insert(ViewTarget { main_texture: main_textures.main_texture.clone(), main_textures, main_texture_format, out_texture: out_texture_view.clone(), out_texture_format: out_texture_format.add_srgb_suffix(), }); } } } }