diff --git a/Cargo.toml b/Cargo.toml index 87de1d18f3990..a103c9a13ab7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4769,6 +4769,18 @@ description = "Demonstrates screen space reflections with water ripples" category = "3D Rendering" wasm = false +[[example]] +name = "camera_color_target_graph" +path = "examples/3d/camera_color_target_graph.rs" +# Causes an ICE on docs.rs +doc-scrape-examples = false + +[package.metadata.example.camera_color_target_graph] +name = "Camera color target graph" +description = "Demonstrates connecting color target input and output of multiple cameras" +category = "3D Rendering" +wasm = true + [[example]] name = "camera_sub_view" path = "examples/3d/camera_sub_view.rs" diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs index 29196109ab158..65b4d82d6e7b2 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs @@ -7,7 +7,6 @@ use bevy_core_pipeline::{ FullscreenShader, }; use bevy_ecs::{prelude::*, query::QueryItem}; -use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin}, @@ -17,7 +16,7 @@ use bevy_render::{ *, }, renderer::RenderDevice, - view::{ExtractedView, ViewTarget}, + view::ExtractedView, Render, RenderApp, RenderStartup, RenderSystems, }; @@ -260,11 +259,7 @@ fn prepare_cas_pipelines( &pipeline_cache, CasPipelineKey { denoise: denoise_cas.0, - texture_format: if view.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + texture_format: view.color_target_format, }, )?; diff --git a/crates/bevy_anti_alias/src/dlss/prepare.rs b/crates/bevy_anti_alias/src/dlss/prepare.rs index a8e88f57d012e..b6ab8a14a620c 100644 --- a/crates/bevy_anti_alias/src/dlss/prepare.rs +++ b/crates/bevy_anti_alias/src/dlss/prepare.rs @@ -1,5 +1,5 @@ use super::{Dlss, DlssFeature, DlssSdk}; -use bevy_camera::{Camera3d, CameraMainTextureUsages, MainPassResolutionOverride}; +use bevy_camera::{Camera3d, MainPassResolutionOverride}; use bevy_core_pipeline::prepass::{DepthPrepass, MotionVectorPrepass}; use bevy_diagnostic::FrameCount; use bevy_ecs::{ @@ -32,7 +32,6 @@ pub fn prepare_dlss( &ExtractedView, &Dlss, &mut Camera3d, - &mut CameraMainTextureUsages, &mut TemporalJitter, &mut MipBias, Option<&mut DlssRenderContext>, @@ -50,19 +49,9 @@ pub fn prepare_dlss( frame_count: Res, mut commands: Commands, ) { - for ( - entity, - view, - dlss, - mut camera_3d, - mut camera_main_texture_usages, - mut temporal_jitter, - mut mip_bias, - mut dlss_context, - ) in &mut query + for (entity, view, dlss, mut camera_3d, mut temporal_jitter, mut mip_bias, mut dlss_context) in + &mut query { - camera_main_texture_usages.0 |= TextureUsages::STORAGE_BINDING; - let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages); depth_texture_usages |= TextureUsages::TEXTURE_BINDING; camera_3d.depth_texture_usages = depth_texture_usages.into(); diff --git a/crates/bevy_anti_alias/src/fxaa/mod.rs b/crates/bevy_anti_alias/src/fxaa/mod.rs index b45c7cad5fe95..c321b9590557c 100644 --- a/crates/bevy_anti_alias/src/fxaa/mod.rs +++ b/crates/bevy_anti_alias/src/fxaa/mod.rs @@ -7,7 +7,6 @@ use bevy_core_pipeline::{ FullscreenShader, }; use bevy_ecs::prelude::*; -use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, @@ -17,7 +16,7 @@ use bevy_render::{ *, }, renderer::RenderDevice, - view::{ExtractedView, ViewTarget}, + view::ExtractedView, Render, RenderApp, RenderStartup, RenderSystems, }; use bevy_shader::Shader; @@ -216,11 +215,7 @@ pub fn prepare_fxaa_pipelines( FxaaPipelineKey { edge_threshold: fxaa.edge_threshold, edge_threshold_min: fxaa.edge_threshold_min, - texture_format: if view.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + texture_format: view.color_target_format, }, ); diff --git a/crates/bevy_anti_alias/src/smaa/mod.rs b/crates/bevy_anti_alias/src/smaa/mod.rs index 3e5e7103a2042..a6b08c18c6cf5 100644 --- a/crates/bevy_anti_alias/src/smaa/mod.rs +++ b/crates/bevy_anti_alias/src/smaa/mod.rs @@ -14,8 +14,7 @@ //! To use SMAA, add [`Smaa`] to a [`bevy_camera::Camera`]. In a //! pinch, you can simply use the default settings (via the [`Default`] trait) //! for a high-quality, high-performance appearance. When using SMAA, you will -//! likely want set [`bevy_render::view::Msaa`] to [`bevy_render::view::Msaa::Off`] -//! for every camera using SMAA. +//! likely want to disable MSAA for every camera using SMAA. //! //! Those who have used SMAA in other engines should be aware that Bevy doesn't //! yet support the following more advanced features of SMAA: @@ -48,7 +47,7 @@ use bevy_ecs::{ system::{lifetimeless::Read, Commands, Query, Res, ResMut}, world::World, }; -use bevy_image::{BevyDefault, Image, ToExtents}; +use bevy_image::{Image, ToExtents}; use bevy_math::{vec4, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ @@ -618,11 +617,7 @@ fn prepare_smaa_pipelines( &pipeline_cache, &smaa_pipelines.neighborhood_blending, SmaaNeighborhoodBlendingPipelineKey { - texture_format: if view.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + texture_format: view.color_target_format, preset: smaa.preset, }, ); @@ -675,9 +670,7 @@ fn prepare_smaa_textures( view_targets: Query<(Entity, &ExtractedCamera), (With, With)>, ) { for (entity, camera) in &view_targets { - let Some(texture_size) = camera.physical_target_size else { - continue; - }; + let texture_size = camera.main_color_target_size; // Create the two-channel RG texture for phase 1 (edge detection). let edge_detection_color_texture = texture_cache.get( diff --git a/crates/bevy_anti_alias/src/taa/mod.rs b/crates/bevy_anti_alias/src/taa/mod.rs index 78c566637118a..918be3fb3e17d 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -16,7 +16,7 @@ use bevy_ecs::{ system::{Commands, Query, Res, ResMut}, world::World, }; -use bevy_image::{BevyDefault as _, ToExtents}; +use bevy_image::ToExtents; use bevy_math::vec2; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ @@ -36,7 +36,7 @@ use bevy_render::{ sync_component::SyncComponentPlugin, sync_world::RenderEntity, texture::{CachedTexture, TextureCache}, - view::{ExtractedView, Msaa, ViewTarget}, + view::{ExtractedView, ViewTarget}, ExtractSchedule, MainWorld, Render, RenderApp, RenderStartup, RenderSystems, }; use bevy_utils::default; @@ -106,7 +106,7 @@ impl Plugin for TemporalAntiAliasPlugin { /// /// # Usage Notes /// -/// Any camera with this component must also disable [`Msaa`] by setting it to [`Msaa::Off`]. +/// Any camera with this component must also disable MSAA. /// /// TAA also does not work well with alpha-blended meshes, as it requires depth writing to determine motion. /// @@ -143,24 +143,23 @@ pub struct TemporalAntiAliasNode; impl ViewNode for TemporalAntiAliasNode { type ViewQuery = ( - &'static ExtractedCamera, + &'static ExtractedView, &'static ViewTarget, &'static TemporalAntiAliasHistoryTextures, &'static ViewPrepassTextures, &'static TemporalAntiAliasPipelineId, - &'static Msaa, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (camera, view_target, taa_history_textures, prepass_textures, taa_pipeline_id, msaa): QueryItem< + (view, view_target, taa_history_textures, prepass_textures, taa_pipeline_id): QueryItem< Self::ViewQuery, >, world: &World, ) -> Result<(), NodeRunError> { - if *msaa != Msaa::Off { + if view.msaa_samples != 1 { warn!("Temporal anti-aliasing requires MSAA to be disabled"); return Ok(()); } @@ -222,9 +221,6 @@ impl ViewNode for TemporalAntiAliasNode { taa_pass.set_render_pipeline(taa_pipeline); taa_pass.set_bind_group(0, &taa_bind_group, &[]); - if let Some(viewport) = camera.viewport.as_ref() { - taa_pass.set_camera_viewport(viewport); - } taa_pass.draw(0..3, 0..1); pass_span.end(&mut taa_pass); @@ -310,6 +306,7 @@ struct TaaPipelineSpecializer; #[derive(PartialEq, Eq, Hash, Clone, SpecializerKey)] struct TaaPipelineKey { + texture_format: TextureFormat, hdr: bool, reset: bool, } @@ -323,19 +320,15 @@ impl Specializer for TaaPipelineSpecializer { descriptor: &mut RenderPipelineDescriptor, ) -> Result, BevyError> { let fragment = descriptor.fragment_mut()?; - let format = if key.hdr { + if key.hdr { fragment.shader_defs.push("TONEMAP".into()); - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - + } if key.reset { fragment.shader_defs.push("RESET".into()); } let color_target_state = ColorTargetState { - format, + format: key.texture_format, blend: None, write_mask: ColorWrites::ALL, }; @@ -416,42 +409,36 @@ fn prepare_taa_history_textures( views: Query<(Entity, &ExtractedCamera, &ExtractedView), With>, ) { for (entity, camera, view) in &views { - if let Some(physical_target_size) = camera.physical_target_size { - let mut texture_descriptor = TextureDescriptor { - label: None, - size: physical_target_size.to_extents(), - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: if view.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, - usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT, - view_formats: &[], - }; - - texture_descriptor.label = Some("taa_history_1_texture"); - let history_1_texture = texture_cache.get(&render_device, texture_descriptor.clone()); - - texture_descriptor.label = Some("taa_history_2_texture"); - let history_2_texture = texture_cache.get(&render_device, texture_descriptor); - - let textures = if frame_count.0.is_multiple_of(2) { - TemporalAntiAliasHistoryTextures { - write: history_1_texture, - read: history_2_texture, - } - } else { - TemporalAntiAliasHistoryTextures { - write: history_2_texture, - read: history_1_texture, - } - }; - - commands.entity(entity).insert(textures); - } + let mut texture_descriptor = TextureDescriptor { + label: None, + size: camera.main_color_target_size.to_extents(), + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: view.color_target_format, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }; + + texture_descriptor.label = Some("taa_history_1_texture"); + let history_1_texture = texture_cache.get(&render_device, texture_descriptor.clone()); + + texture_descriptor.label = Some("taa_history_2_texture"); + let history_2_texture = texture_cache.get(&render_device, texture_descriptor); + + let textures = if frame_count.0.is_multiple_of(2) { + TemporalAntiAliasHistoryTextures { + write: history_1_texture, + read: history_2_texture, + } + } else { + TemporalAntiAliasHistoryTextures { + write: history_2_texture, + read: history_1_texture, + } + }; + + commands.entity(entity).insert(textures); } } @@ -467,6 +454,7 @@ fn prepare_taa_pipelines( for (entity, view, taa_settings) in &views { let mut pipeline_key = TaaPipelineKey { hdr: view.hdr, + texture_format: view.color_target_format, reset: taa_settings.reset, }; let pipeline_id = pipeline diff --git a/crates/bevy_camera/src/camera.rs b/crates/bevy_camera/src/camera.rs index 8b2248942e3ef..31c5e14ce6d49 100644 --- a/crates/bevy_camera/src/camera.rs +++ b/crates/bevy_camera/src/camera.rs @@ -1,8 +1,8 @@ -use crate::primitives::Frustum; +use crate::{color_target::MAIN_COLOR_TARGET_DEFAULT_USAGES, primitives::Frustum}; use super::{ visibility::{Visibility, VisibleEntities}, - ClearColorConfig, MsaaWriteback, + ClearColorConfig, }; use bevy_asset::Handle; use bevy_derive::Deref; @@ -15,7 +15,7 @@ use bevy_window::{NormalizedWindowRef, WindowRef}; use core::ops::Range; use derive_more::derive::From; use thiserror::Error; -use wgpu_types::{BlendState, TextureUsages}; +use wgpu_types::{BlendState, TextureFormat, TextureUsages}; /// Render viewport configuration for the [`Camera`] component. /// @@ -80,17 +80,13 @@ impl Viewport { } } - pub fn from_viewport_and_override( - viewport: Option<&Self>, + pub fn from_main_pass_resolution_override( main_pass_resolution_override: Option<&MainPassResolutionOverride>, ) -> Option { - if let Some(override_size) = main_pass_resolution_override { - let mut vp = viewport.map_or_else(Self::default, Self::clone); - vp.physical_size = **override_size; - Some(vp) - } else { - viewport.cloned() - } + main_pass_resolution_override.map(|override_size| Viewport { + physical_size: **override_size, + ..Default::default() + }) } } @@ -338,14 +334,7 @@ pub enum ViewportConversionError { /// [`Camera3d`]: crate::Camera3d #[derive(Component, Debug, Reflect, Clone)] #[reflect(Component, Default, Debug, Clone)] -#[require( - Frustum, - CameraMainTextureUsages, - VisibleEntities, - Transform, - Visibility, - RenderTarget -)] +#[require(Frustum, VisibleEntities, Transform, Visibility, RenderTarget)] pub struct Camera { /// If set, this camera will render to the given [`Viewport`] rectangle within the configured [`RenderTarget`]. pub viewport: Option, @@ -360,9 +349,6 @@ pub struct Camera { // todo: reflect this when #6042 lands /// The [`CameraOutputMode`] for this camera. pub output_mode: CameraOutputMode, - /// Controls when MSAA writeback occurs for this camera. - /// See [`MsaaWriteback`] for available options. - pub msaa_writeback: MsaaWriteback, /// The clear color operation to perform on the render target. pub clear_color: ClearColorConfig, /// Whether to switch culling mode so that materials that request backface @@ -386,7 +372,6 @@ impl Default for Camera { viewport: None, computed: Default::default(), output_mode: Default::default(), - msaa_writeback: MsaaWriteback::default(), clear_color: Default::default(), invert_culling: false, sub_camera_view: None, @@ -818,6 +803,8 @@ pub enum RenderTarget { /// 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), + /// [`MainColorTarget`](crate::color_target::MainColorTarget) to which the camera's view is rendered. + MainColorTarget(Entity), /// The camera won't render to any color target. /// /// This is useful when you want a camera that *only* renders prepasses, for @@ -842,13 +829,23 @@ impl RenderTarget { impl RenderTarget { /// Normalize the render target down to a more concrete value, mostly used for equality comparisons. - pub fn normalize(&self, primary_window: Option) -> Option { + pub fn normalize( + &self, + primary_window: Option, + main_color_target_render_entity: Option, + ) -> Option { match self { RenderTarget::Window(window_ref) => window_ref .normalize(primary_window) .map(NormalizedRenderTarget::Window), RenderTarget::Image(handle) => Some(NormalizedRenderTarget::Image(handle.clone())), RenderTarget::TextureView(id) => Some(NormalizedRenderTarget::TextureView(*id)), + RenderTarget::MainColorTarget(entity) => { + Some(NormalizedRenderTarget::MainColorTarget { + entity: *entity, + render_entity: main_color_target_render_entity, + }) + } RenderTarget::None { size } => Some(NormalizedRenderTarget::None { width: size.x, height: size.y, @@ -870,6 +867,11 @@ pub enum NormalizedRenderTarget { /// 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), + /// [`MainColorTarget`](crate::color_target::MainColorTarget) to which the camera's view is rendered. + MainColorTarget { + entity: Entity, + render_entity: Option, + }, /// The camera won't render to any color target. /// /// This is useful when you want a camera that *only* renders prepasses, for @@ -884,7 +886,7 @@ pub enum NormalizedRenderTarget { /// A unique id that corresponds to a specific `ManualTextureView` in the `ManualTextureViews` collection. /// -/// See `ManualTextureViews` in `bevy_camera` for more details. +/// See `ManualTextureViews` in `bevy_render` for more details. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Component, Reflect)] #[reflect(Component, Default, Debug, PartialEq, Hash, Clone)] pub struct ManualTextureViewHandle(pub u32); @@ -950,25 +952,42 @@ impl Default for RenderTarget { } } -/// This component lets you control the [`TextureUsages`] field of the main texture generated for the camera +/// If there is no [`NoAutoConfiguredMainColorTarget`](crate::color_target::NoAutoConfiguredMainColorTarget), controls the main color targets generated for the camera. #[derive(Component, Clone, Copy, Reflect)] #[reflect(opaque)] #[reflect(Component, Default, Clone)] -pub struct CameraMainTextureUsages(pub TextureUsages); +pub struct CameraMainColorTargetConfig { + pub size: CameraMainColorTargetsSize, + pub sample_count: u32, + pub format: Option, + pub usage: TextureUsages, +} -impl Default for CameraMainTextureUsages { +#[derive(Clone, Copy)] +pub enum CameraMainColorTargetsSize { + Factor(Vec2), + Fixed(UVec2), +} + +impl Default for CameraMainColorTargetConfig { fn default() -> Self { - Self( - TextureUsages::RENDER_ATTACHMENT - | TextureUsages::TEXTURE_BINDING - | TextureUsages::COPY_SRC, - ) + Self { + size: CameraMainColorTargetsSize::Factor(Vec2::ONE), + sample_count: 4, + format: None, + usage: MAIN_COLOR_TARGET_DEFAULT_USAGES, + } } } -impl CameraMainTextureUsages { - pub fn with(mut self, usages: TextureUsages) -> Self { - self.0 |= usages; +impl CameraMainColorTargetConfig { + pub fn with_usage(mut self, usages: TextureUsages) -> Self { + self.usage |= usages; + self + } + + pub fn with_msaa_off(mut self) -> Self { + self.sample_count = 1; self } } diff --git a/crates/bevy_camera/src/clear_color.rs b/crates/bevy_camera/src/clear_color.rs index 979a9503cec56..aeff7b3428a66 100644 --- a/crates/bevy_camera/src/clear_color.rs +++ b/crates/bevy_camera/src/clear_color.rs @@ -22,25 +22,6 @@ pub enum ClearColorConfig { None, } -/// Controls when MSAA writeback occurs for a camera. -/// -/// MSAA writeback copies the previous camera's output into the MSAA sampled texture before -/// rendering, allowing multiple cameras to layer their results when MSAA is enabled. -#[derive(Reflect, Serialize, Deserialize, Copy, Clone, Debug, Default, PartialEq, Eq)] -#[reflect(Serialize, Deserialize, Default, Clone)] -pub enum MsaaWriteback { - /// Never perform MSAA writeback for this camera. - Off, - /// Perform MSAA writeback only when this camera is not the first one rendering to the target. - /// This is the default behavior - the first camera has nothing to write back. - #[default] - Auto, - /// Always perform MSAA writeback, even if this is the first camera rendering to the target. - /// This is useful when content has been written directly to the main texture (e.g., via - /// `write_texture`) and needs to be preserved through the MSAA render pass. - Always, -} - /// A [`Resource`] that stores the default color that cameras use to clear the screen between frames. /// /// This color appears as the "background" color for simple apps, diff --git a/crates/bevy_camera/src/color_target.rs b/crates/bevy_camera/src/color_target.rs new file mode 100644 index 0000000000000..c9bb7bc207931 --- /dev/null +++ b/crates/bevy_camera/src/color_target.rs @@ -0,0 +1,108 @@ +use alloc::sync::Arc; +use bevy_asset::Handle; +use bevy_ecs::reflect::ReflectComponent; +use bevy_ecs::{component::Component, entity::Entity}; +use bevy_image::Image; +use bevy_reflect::Reflect; +use core::sync::atomic::{AtomicUsize, Ordering}; +use wgpu_types::{BlendState, TextureUsages}; + +/// The default texture usages of unsampled main textures required for rendering. +pub const MAIN_COLOR_TARGET_DEFAULT_USAGES: TextureUsages = TextureUsages::from_bits_truncate( + TextureUsages::RENDER_ATTACHMENT.bits() + | TextureUsages::TEXTURE_BINDING.bits() + | TextureUsages::COPY_SRC.bits(), +); + +/// If this component is present in a Camera, the current main texture and multisampled texture +/// will read and be filled with the main color target, during `ColorTargetInput` pass. +#[derive(Component, Debug, Clone)] +#[relationship_target(relationship= MainColorTargetInput)] +pub struct MainColorTargetReadsFrom(Vec); + +/// If present in a [`MainColorTarget`], it will be used as a input to the relationship target. +/// +/// TODO: Allow one input to target multiple cameras once we have many-to-many relationship. +#[derive(Component, Debug, Clone)] +#[relationship(relationship_target= MainColorTargetReadsFrom)] +pub struct MainColorTargetInput(pub Entity); + +/// Add this to a [`MainColorTargetInput`] to configure blend state and order when inputting. +/// +/// By default blend state is `None` and order is `0` if this isn't present. +#[derive(Component, Debug, Clone, Copy)] +pub struct MainColorTargetInputConfig { + pub blend_state: Option, + pub order: isize, +} + +/// The main color target used by camera in most render passes. +/// +/// 1. In main passes, objects are rendered to `main_a` (or `main_b`, depends on `main_target_flag`). If `multisampled` texture is provided, then MSAA will be enabled and resolved to `main_a`. +/// 2. In post process, `main_b` should be provided as `main_a` need to be written to `main_a` and then swapped during `post_process_write`. +/// 3. Finally, in upscaling pass, the current main color target is written to [`RenderTarget`](crate::RenderTarget). +#[derive(Component, Debug, Clone)] +pub struct MainColorTarget { + pub main_a: Handle, + pub main_b: Option>, + pub multisampled: Option>, + pub main_target_flag: Option>, +} + +impl MainColorTarget { + pub fn new( + main_a: Handle, + main_b: Option>, + multisampled: Option>, + ) -> Self { + let main_target = main_b.as_ref().map(|_| Arc::new(AtomicUsize::new(0))); + Self { + main_a, + main_b, + multisampled, + main_target_flag: main_target, + } + } + + pub fn current_target(&self) -> &Handle { + if let Some(main_target) = &self.main_target_flag + && main_target.load(Ordering::SeqCst) == 1 + { + self.main_b.as_ref().unwrap() + } else { + &self.main_a + } + } + + pub fn other_target(&self) -> Option<&Handle> { + let Some(main_target) = &self.main_target_flag else { + return None; + }; + Some(if main_target.load(Ordering::SeqCst) == 1 { + &self.main_a + } else { + self.main_b.as_ref().unwrap() + }) + } +} + +/// Add this component to camera to opt-out auto configuring [`WithMainColorTarget`]. +/// +/// Specifically, opt-out spawning separate [`MainColorTarget`] for each camera and syncing it with [`CameraMainColorTargetConfig`], otherwise [`CameraMainColorTargetConfig`] has no effect. +/// +/// [`CameraMainColorTargetConfig`]: crate::camera::CameraMainColorTargetConfig +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +pub struct NoAutoConfiguredMainColorTarget; + +/// Link this camera to a [`MainColorTarget`] entity. +#[derive(Component, Reflect, Debug)] +#[relationship(relationship_target = MainColorTargetCameras, allow_self_referential)] +#[reflect(Component)] +pub struct WithMainColorTarget(pub Entity); + +/// The cameras that are using this [`MainColorTarget`]. +#[derive(Component, Reflect, Debug)] +#[relationship_target(relationship = WithMainColorTarget, linked_spawn)] +#[reflect(Component)] +pub struct MainColorTargetCameras(Vec); diff --git a/crates/bevy_camera/src/lib.rs b/crates/bevy_camera/src/lib.rs index b8450dcada258..80c26bd7bcbd4 100644 --- a/crates/bevy_camera/src/lib.rs +++ b/crates/bevy_camera/src/lib.rs @@ -1,6 +1,11 @@ #![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] +extern crate alloc; +extern crate core; + mod camera; + mod clear_color; +pub mod color_target; mod components; pub mod primitives; mod projection; @@ -34,8 +39,8 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ visibility::{InheritedVisibility, ViewVisibility, Visibility}, - Camera, Camera2d, Camera3d, ClearColor, ClearColorConfig, MsaaWriteback, - OrthographicProjection, PerspectiveProjection, Projection, + Camera, Camera2d, Camera3d, ClearColor, ClearColorConfig, OrthographicProjection, + PerspectiveProjection, Projection, }; } diff --git a/crates/bevy_core_pipeline/src/blit/mod.rs b/crates/bevy_core_pipeline/src/blit/mod.rs index 0bf0ca7aae91f..1347c90c342a8 100644 --- a/crates/bevy_core_pipeline/src/blit/mod.rs +++ b/crates/bevy_core_pipeline/src/blit/mod.rs @@ -35,6 +35,8 @@ impl Plugin for BlitPlugin { pub struct BlitPipeline { pub layout: BindGroupLayoutDescriptor, pub sampler: Sampler, + pub layout_filtering: BindGroupLayoutDescriptor, + pub sampler_filtering: Sampler, pub fullscreen_shader: FullscreenShader, pub fragment_shader: Handle, } @@ -58,9 +60,28 @@ pub fn init_blit_pipeline( let sampler = render_device.create_sampler(&SamplerDescriptor::default()); + let layout_filtering = BindGroupLayoutDescriptor::new( + "blit_bind_group_layout_filtering", + &BindGroupLayoutEntries::sequential( + ShaderStages::FRAGMENT, + ( + texture_2d(TextureSampleType::Float { filterable: true }), + binding_types::sampler(SamplerBindingType::Filtering), + ), + ), + ); + + let sampler_filtering = render_device.create_sampler(&SamplerDescriptor { + mag_filter: FilterMode::Linear, + min_filter: FilterMode::Linear, + ..Default::default() + }); + commands.insert_resource(BlitPipeline { layout, sampler, + layout_filtering, + sampler_filtering, fullscreen_shader: fullscreen_shader.clone(), fragment_shader: load_embedded_asset!(asset_server.as_ref(), "blit.wgsl"), }); @@ -72,12 +93,21 @@ impl BlitPipeline { render_device: &RenderDevice, src_texture: &TextureView, pipeline_cache: &PipelineCache, + filtering: bool, ) -> BindGroup { - render_device.create_bind_group( - None, - &pipeline_cache.get_bind_group_layout(&self.layout), - &BindGroupEntries::sequential((src_texture, &self.sampler)), - ) + if filtering { + render_device.create_bind_group( + None, + &pipeline_cache.get_bind_group_layout(&self.layout_filtering), + &BindGroupEntries::sequential((src_texture, &self.sampler_filtering)), + ) + } else { + render_device.create_bind_group( + None, + &pipeline_cache.get_bind_group_layout(&self.layout), + &BindGroupEntries::sequential((src_texture, &self.sampler)), + ) + } } } @@ -86,6 +116,7 @@ pub struct BlitPipelineKey { pub texture_format: TextureFormat, pub blend_state: Option, pub samples: u32, + pub filtering: bool, } impl SpecializedRenderPipeline for BlitPipeline { @@ -94,7 +125,11 @@ impl SpecializedRenderPipeline for BlitPipeline { fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { RenderPipelineDescriptor { label: Some("blit pipeline".into()), - layout: vec![self.layout.clone()], + layout: vec![if key.filtering { + self.layout_filtering.clone() + } else { + self.layout.clone() + }], vertex: self.fullscreen_shader.to_vertex_state(), fragment: Some(FragmentState { shader: self.fragment_shader.clone(), diff --git a/crates/bevy_core_pipeline/src/color_target_input.rs b/crates/bevy_core_pipeline/src/color_target_input.rs new file mode 100644 index 0000000000000..3603c80730927 --- /dev/null +++ b/crates/bevy_core_pipeline/src/color_target_input.rs @@ -0,0 +1,173 @@ +use crate::{ + blit::{BlitPipeline, BlitPipelineKey}, + core_2d::graph::{Core2d, Node2d}, + core_3d::graph::{Core3d, Node3d}, +}; +use bevy_app::{App, Plugin}; +use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_platform::collections::HashMap; +use bevy_render::{ + camera::ExtractedMainColorTargetReadsFrom, + diagnostic::RecordDiagnostics, + render_asset::RenderAssets, + render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, + render_resource::*, + renderer::RenderContext, + texture::GpuImage, + view::{ExtractedView, ViewTarget}, + Render, RenderApp, RenderSystems, +}; + +/// This enables [`MainColorTargetReadsFrom`](bevy_camera::color_target::MainColorTargetReadsFrom) support for the `core_2d` and `core_3d` pipelines. +#[derive(Default)] +pub struct ColorTargetInputPlugin; + +impl Plugin for ColorTargetInputPlugin { + fn build(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + render_app.add_systems( + Render, + prepare_color_target_input_pipelines.in_set(RenderSystems::Prepare), + ); + { + render_app + .add_render_graph_node::>( + Core2d, + Node2d::ColorTargetInput, + ) + .add_render_graph_edge(Core2d, Node2d::ColorTargetInput, Node2d::StartMainPass); + } + { + render_app + .add_render_graph_node::>( + Core3d, + Node3d::ColorTargetInput, + ) + .add_render_graph_edge(Core3d, Node3d::ColorTargetInput, Node3d::StartMainPass); + } + } +} + +#[derive(Default)] +pub struct ColorTargetInputNode; + +impl ViewNode for ColorTargetInputNode { + type ViewQuery = ( + &'static ExtractedView, + &'static ViewTarget, + &'static ColorTargetInputBlitPipeline, + &'static ExtractedMainColorTargetReadsFrom, + ); + + fn run<'w>( + &self, + _graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + (view, view_target, blit_pipeline_id, reads_from): QueryItem<'w, '_, Self::ViewQuery>, + world: &'w World, + ) -> Result<(), NodeRunError> { + let blit_pipeline = world.resource::(); + let pipeline_cache = world.resource::(); + let images = world.resource::>(); + + // Blend all inputs. + for (input, input_config) in &reads_from.0 { + let Some(source) = images.get(*input) else { + continue; + }; + let Some(pipeline) = blit_pipeline_id + .0 + .get(&input_config.blend_state) + .and_then(|id| pipeline_cache.get_render_pipeline(*id)) + else { + continue; + }; + + let diagnostics = render_context.diagnostic_recorder(); + + let pass_descriptor = RenderPassDescriptor { + label: Some("color_target_input"), + color_attachments: &[Some(if view.msaa_samples > 1 { + // Write to both multisampled texture and main texture. + RenderPassColorAttachment { + // If MSAA is enabled, then the sampled texture will always exist + view: view_target.sampled_main_texture_view().unwrap(), + depth_slice: None, + resolve_target: Some(view_target.main_texture_other_view()), + ops: Operations { + load: LoadOp::Load, + store: StoreOp::Store, + }, + } + } else { + // Just write to main texture. + RenderPassColorAttachment { + view: view_target.main_texture_view(), + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Load, + store: StoreOp::Store, + }, + } + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: None, + }; + + let bind_group = blit_pipeline.create_bind_group( + render_context.render_device(), + &source.texture_view, + pipeline_cache, + true, + ); + + let mut render_pass = render_context + .command_encoder() + .begin_render_pass(&pass_descriptor); + let pass_span = diagnostics.pass_span(&mut render_pass, "color_target_input"); + + render_pass.set_pipeline(pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.draw(0..3, 0..1); + + pass_span.end(&mut render_pass); + } + Ok(()) + } +} + +#[derive(Component)] +pub struct ColorTargetInputBlitPipeline(HashMap, CachedRenderPipelineId>); + +fn prepare_color_target_input_pipelines( + mut commands: Commands, + pipeline_cache: Res, + mut pipelines: ResMut>, + blit_pipeline: Res, + view_targets: Query<(Entity, &ExtractedView, &ExtractedMainColorTargetReadsFrom)>, +) { + for (entity, view, reads_from) in view_targets.iter() { + // Collect all blend state pipelines. + let mut map = HashMap::new(); + for (_, input_config) in &reads_from.0 { + map.entry(input_config.blend_state).or_insert_with(|| { + let key = BlitPipelineKey { + texture_format: view.color_target_format, + samples: view.msaa_samples, + blend_state: input_config.blend_state, + filtering: true, + }; + + pipelines.specialize(&pipeline_cache, &blit_pipeline, key) + }); + } + commands + .entity(entity) + .insert(ColorTargetInputBlitPipeline(map)); + } +} diff --git a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs index d7a70acce73d0..20e385e767656 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs @@ -1,7 +1,6 @@ use crate::core_2d::Opaque2d; use bevy_ecs::{prelude::World, query::QueryItem}; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, @@ -21,7 +20,6 @@ use super::AlphaMask2d; pub struct MainOpaquePass2dNode; impl ViewNode for MainOpaquePass2dNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, @@ -31,7 +29,7 @@ impl ViewNode for MainOpaquePass2dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, + (view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let (Some(opaque_phases), Some(alpha_mask_phases)) = ( @@ -75,10 +73,6 @@ impl ViewNode for MainOpaquePass2dNode { let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_2d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } - // Opaque draws if !opaque_phase.is_empty() { #[cfg(feature = "trace")] diff --git a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs index 4afe34c038c44..2e0adc254fbb2 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs @@ -1,7 +1,6 @@ use crate::core_2d::Transparent2d; use bevy_ecs::prelude::*; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::{TrackedRenderPass, ViewSortedRenderPhases}, @@ -18,7 +17,6 @@ pub struct MainTransparentPass2dNode {} impl ViewNode for MainTransparentPass2dNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, @@ -28,7 +26,7 @@ impl ViewNode for MainTransparentPass2dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): bevy_ecs::query::QueryItem<'w, '_, Self::ViewQuery>, + (view, target, depth): bevy_ecs::query::QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let Some(transparent_phases) = @@ -77,10 +75,6 @@ impl ViewNode for MainTransparentPass2dNode { let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_2d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } - if !transparent_phase.items.is_empty() { #[cfg(feature = "trace")] let _transparent_main_pass_2d_span = @@ -96,24 +90,6 @@ impl ViewNode for MainTransparentPass2dNode { pass_span.end(&mut render_pass); } - // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't - // reset for the next render pass so add an empty render pass without a custom viewport - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - if camera.viewport.is_some() { - #[cfg(feature = "trace")] - let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered(); - let pass_descriptor = RenderPassDescriptor { - label: Some("reset_viewport_pass_2d"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - multiview_mask: None, - }; - - command_encoder.begin_render_pass(&pass_descriptor); - } - command_encoder.finish() }); diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index dee095185d44b..41cc1e8121af2 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -13,7 +13,7 @@ pub mod graph { #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] pub enum Node2d { - MsaaWriteback, + ColorTargetInput, StartMainPass, MainOpaquePass, MainTransparentPass, @@ -69,7 +69,7 @@ use bevy_render::{ renderer::RenderDevice, sync_world::MainEntity, texture::TextureCache, - view::{Msaa, ViewDepthTexture}, + view::ViewDepthTexture, Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; @@ -468,29 +468,29 @@ pub fn prepare_core_2d_depth_textures( render_device: Res, transparent_2d_phases: Res>, opaque_2d_phases: Res>, - views_2d: Query<(Entity, &ExtractedCamera, &ExtractedView, &Msaa), (With,)>, + views_2d: Query<(Entity, &ExtractedCamera, &ExtractedView), (With,)>, ) { let mut textures = >::default(); - for (view, camera, extracted_view, msaa) in &views_2d { + for (view, camera, extracted_view) in &views_2d { if !opaque_2d_phases.contains_key(&extracted_view.retained_view_entity) || !transparent_2d_phases.contains_key(&extracted_view.retained_view_entity) { continue; }; - let Some(physical_target_size) = camera.physical_target_size else { - continue; - }; - let cached_texture = textures - .entry(camera.target.clone()) + .entry(( + camera.output_color_target.clone(), + camera.main_color_target_size, + extracted_view.msaa_samples, + )) .or_insert_with(|| { let descriptor = TextureDescriptor { label: Some("view_depth_texture"), // The size of the depth texture - size: physical_target_size.to_extents(), + size: camera.main_color_target_size.to_extents(), mip_level_count: 1, - sample_count: msaa.samples(), + sample_count: extracted_view.msaa_samples, dimension: TextureDimension::D2, format: CORE_2D_DEPTH_FORMAT, usage: TextureUsages::RENDER_ATTACHMENT, diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index c0acf148c3e3c..fc05ab378471e 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -5,7 +5,6 @@ use crate::{ use bevy_camera::{MainPassResolutionOverride, Viewport}; use bevy_ecs::{prelude::World, query::QueryItem}; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, @@ -25,7 +24,6 @@ use super::AlphaMask3d; pub struct MainOpaquePass3dNode; impl ViewNode for MainOpaquePass3dNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, @@ -40,7 +38,6 @@ impl ViewNode for MainOpaquePass3dNode { graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, ( - camera, extracted_view, target, depth, @@ -94,7 +91,7 @@ impl ViewNode for MainOpaquePass3dNode { let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_3d"); if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index 27dd5f7730bcd..e8b31a95a0016 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -56,8 +56,6 @@ impl ViewNode for MainTransmissivePass3dNode { let diagnostics = render_context.diagnostic_recorder(); - let physical_target_size = camera.physical_target_size.unwrap(); - let render_pass_descriptor = RenderPassDescriptor { label: Some("main_transmissive_pass_3d"), color_attachments: &[Some(target.get_color_attachment())], @@ -94,7 +92,7 @@ impl ViewNode for MainTransmissivePass3dNode { render_context.command_encoder().copy_texture_to_texture( target.main_texture().as_image_copy(), transmission.texture.as_image_copy(), - physical_target_size.to_extents(), + camera.main_color_target_size.to_extents(), ); let mut render_pass = @@ -102,10 +100,6 @@ impl ViewNode for MainTransmissivePass3dNode { let pass_span = diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } - // render items in range if let Err(err) = transmissive_phase.render_range(&mut render_pass, world, view_entity, range) @@ -121,10 +115,9 @@ impl ViewNode for MainTransmissivePass3dNode { let pass_span = diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); - if let Some(viewport) = Viewport::from_viewport_and_override( - camera.viewport.as_ref(), - resolution_override, - ) { + if let Some(viewport) = + Viewport::from_main_pass_resolution_override(resolution_override) + { render_pass.set_camera_viewport(&viewport); } diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs index b18f0ab191fa6..ff73d00edd14e 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs @@ -2,7 +2,6 @@ use crate::core_3d::Transparent3d; use bevy_camera::{MainPassResolutionOverride, Viewport}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::ViewSortedRenderPhases, @@ -21,7 +20,6 @@ pub struct MainTransparentPass3dNode; impl ViewNode for MainTransparentPass3dNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, @@ -31,7 +29,7 @@ impl ViewNode for MainTransparentPass3dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (camera, view, target, depth, resolution_override): QueryItem, + (view, target, depth, resolution_override): QueryItem, world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.view_entity(); @@ -72,7 +70,7 @@ impl ViewNode for MainTransparentPass3dNode { let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_3d"); if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } @@ -84,26 +82,6 @@ impl ViewNode for MainTransparentPass3dNode { pass_span.end(&mut render_pass); } - // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't - // reset for the next render pass so add an empty render pass without a custom viewport - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - if camera.viewport.is_some() { - #[cfg(feature = "trace")] - let _reset_viewport_pass_3d = info_span!("reset_viewport_pass_3d").entered(); - let pass_descriptor = RenderPassDescriptor { - label: Some("reset_viewport_pass_3d"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - multiview_mask: None, - }; - - render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - } - Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index feeef1701487e..aa7a58e83a781 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -14,7 +14,7 @@ pub mod graph { #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] pub enum Node3d { - MsaaWriteback, + ColorTargetInput, EarlyPrepass, EarlyDownsampleDepth, LatePrepass, @@ -90,13 +90,12 @@ use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::UntypedAssetId; use bevy_color::LinearRgba; use bevy_ecs::prelude::*; -use bevy_image::{BevyDefault, ToExtents}; +use bevy_image::ToExtents; use bevy_math::FloatOrd; use bevy_platform::collections::{HashMap, HashSet}; use bevy_render::{ camera::ExtractedCamera, extract_component::ExtractComponentPlugin, - prelude::Msaa, render_graph::{EmptyNode, RenderGraphExt, ViewNodeRunner}, render_phase::{ sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, @@ -110,11 +109,10 @@ use bevy_render::{ renderer::RenderDevice, sync_world::{MainEntity, RenderEntity}, texture::{ColorAttachment, TextureCache}, - view::{ExtractedView, ViewDepthTexture, ViewTarget}, + view::{ExtractedView, ViewDepthTexture}, Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use nonmax::NonMaxU32; -use tracing::warn; use crate::{ core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode, @@ -802,11 +800,10 @@ pub fn prepare_core_3d_depth_textures( &ExtractedView, Option<&DepthPrepass>, &Camera3d, - &Msaa, )>, ) { let mut render_target_usage = >::default(); - for (_, camera, extracted_view, depth_prepass, camera_3d, _msaa) in &views_3d { + for (_, camera, extracted_view, depth_prepass, camera_3d) in &views_3d { if !opaque_3d_phases.contains_key(&extracted_view.retained_view_entity) || !alpha_mask_3d_phases.contains_key(&extracted_view.retained_view_entity) || !transmissive_3d_phases.contains_key(&extracted_view.retained_view_entity) @@ -822,30 +819,30 @@ pub fn prepare_core_3d_depth_textures( usage |= TextureUsages::COPY_SRC; } render_target_usage - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .and_modify(|u| *u |= usage) .or_insert_with(|| usage); } let mut textures = >::default(); - for (entity, camera, _, _, camera_3d, msaa) in &views_3d { - let Some(physical_target_size) = camera.physical_target_size else { - continue; - }; - + for (entity, camera, view, _, camera_3d) in &views_3d { + let usage = *render_target_usage + .get(&camera.output_color_target.clone()) + .expect("The depth texture usage should already exist for this target"); let cached_texture = textures - .entry((camera.target.clone(), msaa)) + .entry(( + camera.output_color_target.clone(), + camera.main_color_target_size, + usage, + view.msaa_samples, + )) .or_insert_with(|| { - let usage = *render_target_usage - .get(&camera.target.clone()) - .expect("The depth texture usage should already exist for this target"); - let descriptor = TextureDescriptor { label: Some("view_depth_texture"), // The size of the depth texture - size: physical_target_size.to_extents(), + size: camera.main_color_target_size.to_extents(), mip_level_count: 1, - sample_count: msaa.samples(), + sample_count: view.msaa_samples, dimension: TextureDimension::D2, format: CORE_3D_DEPTH_FORMAT, usage, @@ -897,10 +894,6 @@ pub fn prepare_core_3d_transmission_textures( continue; }; - let Some(physical_target_size) = camera.physical_target_size else { - continue; - }; - // Don't prepare a transmission texture if the number of steps is set to 0 if camera_3d.screen_space_specular_transmission_steps == 0 { continue; @@ -912,24 +905,18 @@ pub fn prepare_core_3d_transmission_textures( } let cached_texture = textures - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .or_insert_with(|| { let usage = TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST; - let format = if view.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - let descriptor = TextureDescriptor { label: Some("view_transmission_texture"), // The size of the transmission texture - size: physical_target_size.to_extents(), + size: camera.main_color_target_size.to_extents(), mip_level_count: 1, sample_count: 1, // No need for MSAA, as we'll only copy the main texture here dimension: TextureDimension::D2, - format, + format: view.color_target_format, usage, view_formats: &[], }; @@ -974,15 +961,13 @@ fn configure_occlusion_culling_view_targets( } } -// Disable MSAA and warn if using deferred rendering -pub fn check_msaa(mut deferred_views: Query<&mut Msaa, (With, With)>) { - for mut msaa in deferred_views.iter_mut() { - match *msaa { - Msaa::Off => (), - _ => { - warn!("MSAA is incompatible with deferred rendering and has been disabled."); - *msaa = Msaa::Off; - } +// Check MSAA and panic if using deferred rendering +pub fn check_msaa( + mut deferred_views: Query<&ExtractedView, (With, With)>, +) { + for view in deferred_views.iter_mut() { + if view.msaa_samples > 1 { + panic!("MSAA is incompatible with deferred rendering."); }; } } @@ -1001,7 +986,6 @@ pub fn prepare_prepass_textures( Entity, &ExtractedCamera, &ExtractedView, - &Msaa, Has, Has, Has, @@ -1021,7 +1005,6 @@ pub fn prepare_prepass_textures( entity, camera, view, - msaa, depth_prepass, normal_prepass, motion_vector_prepass, @@ -1039,21 +1022,17 @@ pub fn prepare_prepass_textures( continue; }; - let Some(physical_target_size) = camera.physical_target_size else { - continue; - }; - - let size = physical_target_size.to_extents(); + let size = camera.main_color_target_size.to_extents(); let cached_depth_texture1 = depth_prepass.then(|| { depth_textures1 - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .or_insert_with(|| { let descriptor = TextureDescriptor { label: Some("prepass_depth_texture_1"), size, mip_level_count: 1, - sample_count: msaa.samples(), + sample_count: view.msaa_samples, dimension: TextureDimension::D2, format: CORE_3D_DEPTH_FORMAT, usage: TextureUsages::COPY_DST @@ -1068,13 +1047,13 @@ pub fn prepare_prepass_textures( let cached_depth_texture2 = depth_prepass_double_buffer.then(|| { depth_textures2 - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .or_insert_with(|| { let descriptor = TextureDescriptor { label: Some("prepass_depth_texture_2"), size, mip_level_count: 1, - sample_count: msaa.samples(), + sample_count: view.msaa_samples, dimension: TextureDimension::D2, format: CORE_3D_DEPTH_FORMAT, usage: TextureUsages::COPY_DST @@ -1089,7 +1068,7 @@ pub fn prepare_prepass_textures( let cached_normals_texture = normal_prepass.then(|| { normal_textures - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .or_insert_with(|| { texture_cache.get( &render_device, @@ -1097,7 +1076,7 @@ pub fn prepare_prepass_textures( label: Some("prepass_normal_texture"), size, mip_level_count: 1, - sample_count: msaa.samples(), + sample_count: view.msaa_samples, dimension: TextureDimension::D2, format: NORMAL_PREPASS_FORMAT, usage: TextureUsages::RENDER_ATTACHMENT @@ -1111,7 +1090,7 @@ pub fn prepare_prepass_textures( let cached_motion_vectors_texture = motion_vector_prepass.then(|| { motion_vectors_textures - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .or_insert_with(|| { texture_cache.get( &render_device, @@ -1119,7 +1098,7 @@ pub fn prepare_prepass_textures( label: Some("prepass_motion_vectors_textures"), size, mip_level_count: 1, - sample_count: msaa.samples(), + sample_count: view.msaa_samples, dimension: TextureDimension::D2, format: MOTION_VECTOR_PREPASS_FORMAT, usage: TextureUsages::RENDER_ATTACHMENT @@ -1133,7 +1112,7 @@ pub fn prepare_prepass_textures( let cached_deferred_texture1 = deferred_prepass.then(|| { deferred_textures1 - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .or_insert_with(|| { texture_cache.get( &render_device, @@ -1155,7 +1134,7 @@ pub fn prepare_prepass_textures( let cached_deferred_texture2 = deferred_prepass_double_buffer.then(|| { deferred_textures2 - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .or_insert_with(|| { texture_cache.get( &render_device, @@ -1177,7 +1156,7 @@ pub fn prepare_prepass_textures( let cached_deferred_lighting_pass_id_texture = deferred_prepass.then(|| { deferred_lighting_id_textures - .entry(camera.target.clone()) + .entry(camera.output_color_target.clone()) .or_insert_with(|| { texture_cache.get( &render_device, diff --git a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs index f917fafa99bb7..188ea759cd4b9 100644 --- a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs +++ b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs @@ -173,21 +173,19 @@ fn prepare_deferred_lighting_id_textures( views: Query<(Entity, &ExtractedCamera), With>, ) { for (entity, camera) in &views { - if let Some(physical_target_size) = camera.physical_target_size { - let texture_descriptor = TextureDescriptor { - label: Some("deferred_lighting_id_depth_texture_a"), - size: physical_target_size.to_extents(), - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, - usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC, - view_formats: &[], - }; - let texture = texture_cache.get(&render_device, texture_descriptor); - commands - .entity(entity) - .insert(DeferredLightingIdDepthTexture { texture }); - } + let texture_descriptor = TextureDescriptor { + label: Some("deferred_lighting_id_depth_texture_a"), + size: camera.main_color_target_size.to_extents(), + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC, + view_formats: &[], + }; + let texture = texture_cache.get(&render_device, texture_descriptor); + commands + .entity(entity) + .insert(DeferredLightingIdDepthTexture { texture }); } } diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 4ec7e8c795a36..44ad0c6879447 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -5,7 +5,6 @@ use bevy_render::render_graph::ViewNode; use bevy_render::view::{ExtractedView, NoIndirectDrawing}; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext}, render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, @@ -64,7 +63,6 @@ pub struct LateDeferredGBufferPrepassNode; impl ViewNode for LateDeferredGBufferPrepassNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewDepthTexture, &'static ViewPrepassTextures, @@ -108,7 +106,7 @@ impl ViewNode for LateDeferredGBufferPrepassNode { fn run_deferred_prepass<'w>( graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, extracted_view, view_depth_texture, view_prepass_textures, resolution_override, _, _): QueryItem< + ( extracted_view, view_depth_texture, view_prepass_textures, resolution_override, _, _): QueryItem< 'w, '_, ::ViewQuery, @@ -227,9 +225,8 @@ fn run_deferred_prepass<'w>( }); let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); let pass_span = diagnostic.pass_span(&mut render_pass, label); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { + + if let Some(viewport) = Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } diff --git a/crates/bevy_core_pipeline/src/fullscreen_material.rs b/crates/bevy_core_pipeline/src/fullscreen_material.rs index 48b16b3055d3a..6750a45fded6d 100644 --- a/crates/bevy_core_pipeline/src/fullscreen_material.rs +++ b/crates/bevy_core_pipeline/src/fullscreen_material.rs @@ -39,7 +39,7 @@ use bevy_render::{ TextureSampleType, }, renderer::{RenderContext, RenderDevice}, - view::ViewTarget, + view::{ExtractedView, ViewTarget}, ExtractSchedule, MainWorld, RenderApp, RenderStartup, }; use bevy_shader::ShaderRef; @@ -251,7 +251,7 @@ fn init_pipeline( desc.fragment.as_mut().unwrap().targets[0] .as_mut() .unwrap() - .format = ViewTarget::TEXTURE_FORMAT_HDR; + .format = TextureFormat::Rgba16Float; let pipeline_id_hdr = pipeline_cache.queue_render_pipeline(desc); commands.insert_resource(FullscreenMaterialPipeline { layout, @@ -268,19 +268,23 @@ struct FullscreenMaterialNode { impl ViewNode for FullscreenMaterialNode { // TODO we should expose the depth buffer and the gbuffer if using deferred - type ViewQuery = (&'static ViewTarget, &'static DynamicUniformIndex); + type ViewQuery = ( + &'static ExtractedView, + &'static ViewTarget, + &'static DynamicUniformIndex, + ); fn run<'w>( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (view_target, settings_index): QueryItem, + (view, view_target, settings_index): QueryItem, world: &World, ) -> Result<(), NodeRunError> { let fullscreen_pipeline = world.resource::(); let pipeline_cache = world.resource::(); - let pipeline_id = if view_target.is_hdr() { + let pipeline_id = if view.hdr { fullscreen_pipeline.pipeline_id_hdr } else { fullscreen_pipeline.pipeline_id diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 15ac93c775345..22dd200517b53 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -7,6 +7,7 @@ )] pub mod blit; +pub mod color_target_input; pub mod core_2d; pub mod core_3d; pub mod deferred; @@ -24,9 +25,10 @@ mod fullscreen_vertex_shader; mod skybox; use crate::{ - blit::BlitPlugin, core_2d::Core2dPlugin, core_3d::Core3dPlugin, - deferred::copy_lighting_id::CopyDeferredLightingIdPlugin, mip_generation::MipGenerationPlugin, - tonemapping::TonemappingPlugin, upscaling::UpscalingPlugin, + blit::BlitPlugin, color_target_input::ColorTargetInputPlugin, core_2d::Core2dPlugin, + core_3d::Core3dPlugin, deferred::copy_lighting_id::CopyDeferredLightingIdPlugin, + mip_generation::MipGenerationPlugin, tonemapping::TonemappingPlugin, + upscaling::UpscalingPlugin, }; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; @@ -45,6 +47,7 @@ impl Plugin for CorePipelinePlugin { BlitPlugin, TonemappingPlugin, UpscalingPlugin, + ColorTargetInputPlugin, OrderIndependentTransparencyPlugin, MipGenerationPlugin, )); diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 51c07dff717c6..07e5f145986df 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -1,23 +1,21 @@ //! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details. use bevy_app::prelude::*; -use bevy_camera::{Camera3d, RenderTarget}; +use bevy_camera::Camera3d; use bevy_ecs::{component::*, lifecycle::ComponentHook, prelude::*}; use bevy_math::UVec2; -use bevy_platform::collections::HashSet; use bevy_platform::time::Instant; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::view::ExtractedView; use bevy_render::{ camera::ExtractedCamera, extract_component::{ExtractComponent, ExtractComponentPlugin}, render_graph::{RenderGraphExt, ViewNodeRunner}, render_resource::{BufferUsages, BufferVec, DynamicUniformBuffer, ShaderType, TextureUsages}, renderer::{RenderDevice, RenderQueue}, - view::Msaa, Render, RenderApp, RenderStartup, RenderSystems, }; use bevy_shader::load_shader_library; -use bevy_window::PrimaryWindow; use resolve::{ node::{OitResolveNode, OitResolvePass}, OitResolvePlugin, @@ -104,7 +102,7 @@ impl Plugin for OrderIndependentTransparencyPlugin { OitResolvePlugin, )) .add_systems(Update, check_msaa) - .add_systems(Last, configure_depth_texture_usages); + .add_systems(Last, configure_camera_depth_usages); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -130,40 +128,23 @@ impl Plugin for OrderIndependentTransparencyPlugin { } } -// WARN This should only happen for cameras with the [`OrderIndependentTransparencySettings`] component -// but when multiple cameras are present on the same window -// bevy reuses the same depth texture so we need to set this on all cameras with the same render target. -fn configure_depth_texture_usages( - p: Query>, - cameras: Query<(&RenderTarget, Has)>, - mut new_cameras: Query<(&mut Camera3d, &RenderTarget), Added>, +fn configure_camera_depth_usages( + mut cameras: Query< + &mut Camera3d, + ( + Changed, + With, + ), + >, ) { - if new_cameras.is_empty() { - return; - } - - // Find all the render target that potentially uses OIT - let primary_window = p.single().ok(); - let mut render_target_has_oit = >::default(); - for (render_target, has_oit) in &cameras { - if has_oit { - render_target_has_oit.insert(render_target.normalize(primary_window)); - } - } - - // Update the depth texture usage for cameras with a render target that has OIT - for (mut camera_3d, render_target) in &mut new_cameras { - if render_target_has_oit.contains(&render_target.normalize(primary_window)) { - let mut usages = TextureUsages::from(camera_3d.depth_texture_usages); - usages |= TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING; - camera_3d.depth_texture_usages = usages.into(); - } + for mut camera in &mut cameras { + camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits(); } } -fn check_msaa(cameras: Query<&Msaa, With>) { - for msaa in &cameras { - if msaa.samples() > 1 { +fn check_msaa(cameras: Query<&ExtractedView, With>) { + for view in &cameras { + if view.msaa_samples > 1 { panic!("MSAA is not supported when using OrderIndependentTransparency"); } } @@ -236,9 +217,7 @@ pub fn prepare_oit_buffers( let mut max_layer_ids_size = usize::MIN; let mut max_layers_size = usize::MIN; for (camera, settings) in &cameras { - let Some(size) = camera.physical_target_size else { - continue; - }; + let size = camera.main_color_target_size; let layer_count = settings.layer_count as usize; let size = (size.x * size.y) as usize; diff --git a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs index 41dd16390d420..051dec8b2bbe6 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs @@ -7,7 +7,6 @@ use bevy_ecs::{ entity::{EntityHashMap, EntityHashSet}, prelude::*, }; -use bevy_image::BevyDefault as _; use bevy_render::{ render_resource::{ binding_types::{storage_buffer_sized, texture_depth_2d, uniform_buffer}, @@ -17,7 +16,7 @@ use bevy_render::{ TextureFormat, }, renderer::{RenderAdapter, RenderDevice}, - view::{ExtractedView, ViewTarget, ViewUniform, ViewUniforms}, + view::{ExtractedView, ViewUniform, ViewUniforms}, Render, RenderApp, RenderSystems, }; use bevy_shader::ShaderDefVal; @@ -136,7 +135,7 @@ pub struct OitResolvePipelineId(pub CachedRenderPipelineId); /// This key is used to cache the pipeline id and to specialize the render pipeline descriptor. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct OitResolvePipelineKey { - hdr: bool, + texture_format: TextureFormat, layer_count: i32, } @@ -162,7 +161,7 @@ pub fn queue_oit_resolve_pipeline( for (e, view, oit_settings) in &views { current_view_entities.insert(e); let key = OitResolvePipelineKey { - hdr: view.hdr, + texture_format: view.color_target_format, layer_count: oit_settings.layer_count, }; @@ -199,12 +198,6 @@ fn specialize_oit_resolve_pipeline( fullscreen_shader: &FullscreenShader, asset_server: &AssetServer, ) -> RenderPipelineDescriptor { - let format = if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - RenderPipelineDescriptor { label: Some("oit_resolve_pipeline".into()), layout: vec![ @@ -218,7 +211,7 @@ fn specialize_oit_resolve_pipeline( key.layer_count as u32, )], targets: vec![Some(ColorTargetState { - format, + format: key.texture_format, blend: Some(BlendState { color: BlendComponent::OVER, alpha: BlendComponent::OVER, diff --git a/crates/bevy_core_pipeline/src/oit/resolve/node.rs b/crates/bevy_core_pipeline/src/oit/resolve/node.rs index 9ff1fb25c7592..565deae15032d 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/node.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/node.rs @@ -1,7 +1,6 @@ use bevy_camera::{MainPassResolutionOverride, Viewport}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode}, render_resource::{BindGroupEntries, PipelineCache, RenderPassDescriptor}, @@ -20,7 +19,6 @@ pub struct OitResolvePass; pub struct OitResolveNode; impl ViewNode for OitResolveNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ViewTarget, &'static ViewUniformOffset, &'static OitResolvePipelineId, @@ -32,7 +30,7 @@ impl ViewNode for OitResolveNode { &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (camera, view_target, view_uniform, oit_resolve_pipeline_id, depth, resolution_override): QueryItem< + (view_target, view_uniform, oit_resolve_pipeline_id, depth, resolution_override): QueryItem< Self::ViewQuery, >, world: &World, @@ -71,7 +69,7 @@ impl ViewNode for OitResolveNode { let pass_span = diagnostics.pass_span(&mut render_pass, "oit_resolve"); if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index 3f605ff11d9f2..2441f1a04a676 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -1,7 +1,6 @@ use bevy_camera::{MainPassResolutionOverride, Viewport}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, occlusion_culling::OcclusionCulling, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, @@ -57,7 +56,6 @@ pub struct LatePrepassNode; impl ViewNode for LatePrepassNode { type ViewQuery = ( ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewDepthTexture, &'static ViewPrepassTextures, @@ -108,7 +106,7 @@ fn run_prepass<'w>( graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, ( - (camera, extracted_view, view_depth_texture, view_prepass_textures, view_uniform_offset), + (extracted_view, view_depth_texture, view_prepass_textures, view_uniform_offset), ( deferred_prepass, skybox_prepass_pipeline, @@ -188,9 +186,7 @@ fn run_prepass<'w>( let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); let pass_span = diagnostics.pass_span(&mut render_pass, label); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { + if let Some(viewport) = Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index 20cf97726b082..8fd8d790b4486 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -9,7 +9,7 @@ use bevy_ecs::{ schedule::IntoScheduleConfigs, system::{Commands, Query, Res, ResMut}, }; -use bevy_image::{BevyDefault, Image}; +use bevy_image::Image; use bevy_math::{Mat4, Quat}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ @@ -24,7 +24,7 @@ use bevy_render::{ }, renderer::RenderDevice, texture::GpuImage, - view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniforms}, + view::{ExtractedView, ViewUniform, ViewUniforms}, Render, RenderApp, RenderStartup, RenderSystems, }; use bevy_shader::Shader; @@ -181,7 +181,7 @@ fn init_skybox_pipeline(mut commands: Commands, asset_server: Res) #[derive(PartialEq, Eq, Hash, Clone, Copy)] struct SkyboxPipelineKey { - hdr: bool, + texture_format: TextureFormat, samples: u32, depth_format: TextureFormat, } @@ -221,11 +221,7 @@ impl SpecializedRenderPipeline for SkyboxPipeline { fragment: Some(FragmentState { shader: self.shader.clone(), targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.texture_format, // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. blend: None, write_mask: ColorWrites::ALL, @@ -245,15 +241,15 @@ fn prepare_skybox_pipelines( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<(Entity, &ExtractedView, &Msaa), With>, + views: Query<(Entity, &ExtractedView), With>, ) { - for (entity, view, msaa) in &views { + for (entity, view) in &views { let pipeline_id = pipelines.specialize( &pipeline_cache, &pipeline, SkyboxPipelineKey { - hdr: view.hdr, - samples: msaa.samples(), + texture_format: view.color_target_format, + samples: view.msaa_samples, depth_format: CORE_3D_DEPTH_FORMAT, }, ); diff --git a/crates/bevy_core_pipeline/src/skybox/prepass.rs b/crates/bevy_core_pipeline/src/skybox/prepass.rs index 8eb51c10e2067..08ece16f2fbcc 100644 --- a/crates/bevy_core_pipeline/src/skybox/prepass.rs +++ b/crates/bevy_core_pipeline/src/skybox/prepass.rs @@ -16,7 +16,7 @@ use bevy_render::{ SpecializedRenderPipeline, SpecializedRenderPipelines, }, renderer::RenderDevice, - view::{Msaa, ViewUniform, ViewUniforms}, + view::{ExtractedView, ViewUniform, ViewUniforms}, }; use bevy_shader::Shader; use bevy_utils::prelude::default; @@ -116,11 +116,14 @@ pub fn prepare_skybox_prepass_pipelines( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<(Entity, Has, &Msaa), (With, With)>, + views: Query< + (Entity, Has, &ExtractedView), + (With, With), + >, ) { - for (entity, normal_prepass, msaa) in &views { + for (entity, normal_prepass, view) in &views { let pipeline_key = SkyboxPrepassPipelineKey { - samples: msaa.samples(), + samples: view.msaa_samples, normal_prepass, }; diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 93681a5c199f4..e448d0d083b3f 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -186,6 +186,7 @@ bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct TonemappingPipelineKey { + texture_format: TextureFormat, deband_dither: DebandDither, tonemapping: Tonemapping, flags: TonemappingPipelineKeyFlags, @@ -273,7 +274,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline { shader: self.fragment_shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: ViewTarget::TEXTURE_FORMAT_HDR, + format: key.texture_format, blend: None, write_mask: ColorWrites::ALL, })], @@ -357,6 +358,7 @@ pub fn prepare_view_tonemapping_pipelines( deband_dither: *dither.unwrap_or(&DebandDither::Disabled), tonemapping: *tonemapping.unwrap_or(&Tonemapping::None), flags, + texture_format: view.color_target_format, }; let pipeline = pipelines.specialize(&pipeline_cache, &upscaling_pipeline, key); diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index 146ea46d421c8..7a15f5897c7ce 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -13,7 +13,7 @@ use bevy_render::{ }, renderer::RenderContext, texture::{FallbackImage, GpuImage}, - view::{ViewTarget, ViewUniformOffset, ViewUniforms}, + view::{ExtractedView, ViewTarget, ViewUniformOffset, ViewUniforms}, }; use super::{get_lut_bindings, Tonemapping}; @@ -28,6 +28,7 @@ impl ViewNode for TonemappingNode { type ViewQuery = ( &'static ViewUniformOffset, &'static ViewTarget, + &'static ExtractedView, &'static ViewTonemappingPipeline, &'static Tonemapping, ); @@ -36,7 +37,7 @@ impl ViewNode for TonemappingNode { &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (view_uniform_offset, target, view_tonemapping_pipeline, tonemapping): QueryItem< + (view_uniform_offset, target, view, view_tonemapping_pipeline, tonemapping): QueryItem< Self::ViewQuery, >, world: &World, @@ -53,7 +54,7 @@ impl ViewNode for TonemappingNode { return Ok(()); } - if !target.is_hdr() { + if !view.hdr { return Ok(()); } diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 053ce5007a8a7..3d9a3903ad169 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -75,6 +75,7 @@ fn prepare_view_upscaling_pipelines( texture_format: view_target.out_texture_view_format(), blend_state, samples: 1, + filtering: true, }; let pipeline = pipelines.specialize(&pipeline_cache, &blit_pipeline, key); diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index 7005d3870f3a5..bdc8d3732adf4 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -57,10 +57,12 @@ impl ViewNode for UpscalingNode { let bind_group = match &mut *cached_bind_group { Some((id, bind_group)) if main_texture_view.id() == *id => bind_group, cached_bind_group => { + // Use linear filtering for better quality when upsampling. let bind_group = blit_pipeline.create_bind_group( render_context.render_device(), main_texture_view, pipeline_cache, + true, ); let (_, bind_group) = @@ -94,7 +96,14 @@ impl ViewNode for UpscalingNode { { let size = viewport.physical_size; let position = viewport.physical_position; - render_pass.set_scissor_rect(position.x, position.y, size.x, size.y); + render_pass.set_viewport( + position.x as f32, + position.y as f32, + size.x as f32, + size.y as f32, + viewport.depth.start, + viewport.depth.end, + ); } render_pass.set_pipeline(pipeline); diff --git a/crates/bevy_dev_tools/src/picking_debug.rs b/crates/bevy_dev_tools/src/picking_debug.rs index 515dbfc222946..8aa85a81869d2 100644 --- a/crates/bevy_dev_tools/src/picking_debug.rs +++ b/crates/bevy_dev_tools/src/picking_debug.rs @@ -256,7 +256,7 @@ pub fn debug_draw( for (camera, _, _) in camera_query.iter().filter(|(_, _, render_target)| { render_target - .normalize(primary_window.single().ok()) + .normalize(primary_window.single().ok(), None) .is_some_and(|target| target == pointer_location.target) }) { let mut pointer_pos = pointer_location.position; diff --git a/crates/bevy_dev_tools/src/render_debug.rs b/crates/bevy_dev_tools/src/render_debug.rs index ff0983bf6a4a4..5bcdf603eb08b 100644 --- a/crates/bevy_dev_tools/src/render_debug.rs +++ b/crates/bevy_dev_tools/src/render_debug.rs @@ -38,7 +38,7 @@ use bevy_render::{ }, renderer::{RenderContext, RenderDevice, RenderQueue}, texture::{FallbackImage, GpuImage}, - view::{Msaa, ViewTarget, ViewUniformOffset}, + view::{ExtractedView, ViewTarget, ViewUniformOffset}, Render, RenderApp, RenderSystems, }; use bevy_shader::Shader; @@ -506,20 +506,19 @@ fn prepare_debug_overlay_pipelines( blue_noise: Res, views: Query<( Entity, - &ViewTarget, + &ExtractedView, &RenderDebugOverlay, - &Msaa, Option<&bevy_core_pipeline::prepass::ViewPrepassTextures>, Has, Has, )>, ) { - for (entity, target, config, msaa, prepass_textures, has_oit, has_atmosphere) in &views { + for (entity, view, config, prepass_textures, has_oit, has_atmosphere) in &views { if !config.enabled { continue; } - let mut view_layout_key = MeshPipelineViewLayoutKey::from(*msaa) + let mut view_layout_key = MeshPipelineViewLayoutKey::from_msaa_samples(view.msaa_samples) | MeshPipelineViewLayoutKey::from(prepass_textures); if has_oit { @@ -541,7 +540,7 @@ fn prepare_debug_overlay_pipelines( RenderDebugOverlayPipelineKey { mode: config.mode, view_layout_key, - texture_format: target.main_texture_format(), + texture_format: view.color_target_format, }, ); diff --git a/crates/bevy_gizmos_render/src/pipeline_2d.rs b/crates/bevy_gizmos_render/src/pipeline_2d.rs index 2a29d18de24cf..015b17d3c0b24 100644 --- a/crates/bevy_gizmos_render/src/pipeline_2d.rs +++ b/crates/bevy_gizmos_render/src/pipeline_2d.rs @@ -15,7 +15,6 @@ use bevy_ecs::{ schedule::IntoScheduleConfigs, system::{Commands, Query, Res, ResMut}, }; -use bevy_image::BevyDefault as _; use bevy_math::FloatOrd; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, @@ -24,7 +23,7 @@ use bevy_render::{ ViewSortedRenderPhases, }, render_resource::*, - view::{ExtractedView, Msaa, ViewTarget}, + view::ExtractedView, Render, RenderApp, RenderSystems, }; use bevy_render::{sync_world::MainEntity, RenderStartup}; @@ -111,12 +110,6 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { type Key = LineGizmoPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - let shader_defs = vec![ #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] "SIXTEEN_BYTE_ALIGNMENT".into(), @@ -146,7 +139,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { shader_defs, entry_point: Some(fragment_entry_point.into()), targets: vec![Some(ColorTargetState { - format, + format: key.mesh_key.color_target_format(), blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -196,12 +189,6 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline { type Key = LineJointGizmoPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - let shader_defs = vec![ #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] "SIXTEEN_BYTE_ALIGNMENT".into(), @@ -233,7 +220,7 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format, + format: key.mesh_key.color_target_format(), blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -297,7 +284,7 @@ fn queue_line_and_joint_gizmos_2d( line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, - mut views: Query<(&ExtractedView, &Msaa, Option<&RenderLayers>)>, + mut views: Query<(&ExtractedView, Option<&RenderLayers>)>, ) { let draw_function = draw_functions.read().get_id::().unwrap(); let draw_line_function_strip = draw_functions @@ -309,14 +296,14 @@ fn queue_line_and_joint_gizmos_2d( .get_id::() .unwrap(); - for (view, msaa, render_layers) in &mut views { + for (view, render_layers) in &mut views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) else { continue; }; - let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) - | Mesh2dPipelineKey::from_hdr(view.hdr); + let mesh_key = Mesh2dPipelineKey::from_msaa_samples(view.msaa_samples) + | Mesh2dPipelineKey::from_color_target_format(view.color_target_format); let render_layers = render_layers.unwrap_or_default(); for (entity, main_entity, config) in &line_gizmos { diff --git a/crates/bevy_gizmos_render/src/pipeline_3d.rs b/crates/bevy_gizmos_render/src/pipeline_3d.rs index 88ce897f79f9d..f91fc11d3d604 100644 --- a/crates/bevy_gizmos_render/src/pipeline_3d.rs +++ b/crates/bevy_gizmos_render/src/pipeline_3d.rs @@ -21,7 +21,6 @@ use bevy_ecs::{ schedule::IntoScheduleConfigs, system::{Commands, Query, Res, ResMut}, }; -use bevy_image::BevyDefault as _; use bevy_pbr::{ExtractedAtmosphere, MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, @@ -30,7 +29,7 @@ use bevy_render::{ ViewSortedRenderPhases, }, render_resource::*, - view::{ExtractedView, Msaa, ViewTarget}, + view::ExtractedView, Render, RenderApp, RenderSystems, }; use bevy_render::{sync_world::MainEntity, RenderStartup}; @@ -156,12 +155,6 @@ impl Specializer for LineGizmoPipelineSpecializer { fragment.shader_defs.push("PERSPECTIVE".into()); } - let format = if key.view_key.contains(MeshPipelineKey::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - let fragment_entry_point = match key.line_style { GizmoLineStyle::Solid => "fragment_solid", GizmoLineStyle::Dotted => "fragment_dotted", @@ -174,7 +167,7 @@ impl Specializer for LineGizmoPipelineSpecializer { fragment.set_target( 0, ColorTargetState { - format, + format: key.view_key.color_target_format(), blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, }, @@ -211,12 +204,6 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline { shader_defs.push("PERSPECTIVE".into()); } - let format = if key.view_key.contains(MeshPipelineKey::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - let view_layout = self .mesh_pipeline .get_view_layout(key.view_key.into()) @@ -244,7 +231,7 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format, + format: key.view_key.color_target_format(), blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -297,7 +284,6 @@ fn queue_line_gizmos_3d( mut transparent_render_phases: ResMut>, views: Query<( &ExtractedView, - &Msaa, Option<&RenderLayers>, ( Has, @@ -317,7 +303,6 @@ fn queue_line_gizmos_3d( for ( view, - msaa, render_layers, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass, oit, atmosphere), ) in &views @@ -329,8 +314,8 @@ fn queue_line_gizmos_3d( let render_layers = render_layers.unwrap_or_default(); - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); + let mut view_key = MeshPipelineKey::from_msaa_samples(view.msaa_samples) + | MeshPipelineKey::from_color_target_format(view.color_target_format); if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; @@ -422,7 +407,6 @@ fn queue_line_joint_gizmos_3d( mut transparent_render_phases: ResMut>, views: Query<( &ExtractedView, - &Msaa, Option<&RenderLayers>, ( Has, @@ -439,7 +423,6 @@ fn queue_line_joint_gizmos_3d( for ( view, - msaa, render_layers, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), ) in &views @@ -451,8 +434,8 @@ fn queue_line_joint_gizmos_3d( let render_layers = render_layers.unwrap_or_default(); - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); + let mut view_key = MeshPipelineKey::from_msaa_samples(view.msaa_samples) + | MeshPipelineKey::from_color_target_format(view.color_target_format); if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; diff --git a/crates/bevy_pbr/src/atmosphere/node.rs b/crates/bevy_pbr/src/atmosphere/node.rs index 32696f61651a3..dafc9986aceed 100644 --- a/crates/bevy_pbr/src/atmosphere/node.rs +++ b/crates/bevy_pbr/src/atmosphere/node.rs @@ -2,7 +2,6 @@ use bevy_camera::{MainPassResolutionOverride, Viewport}; use bevy_ecs::{query::QueryItem, system::lifetimeless::Read, world::World}; use bevy_math::{UVec2, Vec3Swizzles}; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, extract_component::DynamicUniformIndex, render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode}, @@ -167,7 +166,6 @@ pub(super) struct RenderSkyNode; impl ViewNode for RenderSkyNode { type ViewQuery = ( - Read, Read, Read, Read>, @@ -184,7 +182,6 @@ impl ViewNode for RenderSkyNode { _graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, ( - camera, atmosphere_bind_groups, view_target, atmosphere_uniforms_offset, @@ -216,9 +213,7 @@ impl ViewNode for RenderSkyNode { }); let pass_span = diagnostics.pass_span(&mut render_sky_pass, "render_sky"); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { + if let Some(viewport) = Viewport::from_main_pass_resolution_override(resolution_override) { render_sky_pass.set_camera_viewport(&viewport); } diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index f71c5e152651a..c2404f6b02459 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -23,7 +23,7 @@ use bevy_render::{ render_resource::{binding_types::*, *}, renderer::{RenderDevice, RenderQueue}, texture::{CachedTexture, TextureCache}, - view::{ExtractedView, Msaa, ViewDepthTexture, ViewUniform, ViewUniforms}, + view::{ExtractedView, ViewDepthTexture, ViewUniform, ViewUniforms}, }; use bevy_shader::Shader; use bevy_utils::default; @@ -364,19 +364,19 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { } pub(super) fn queue_render_sky_pipelines( - views: Query<(Entity, &Msaa), (With, With)>, + views: Query<(Entity, &ExtractedView), (With, With)>, pipeline_cache: Res, layouts: Res, mut specializer: ResMut>, render_device: Res, mut commands: Commands, ) { - for (entity, msaa) in &views { + for (entity, view) in &views { let id = specializer.specialize( &pipeline_cache, &layouts, RenderSkyPipelineKey { - msaa_samples: msaa.samples(), + msaa_samples: view.msaa_samples, dual_source_blending: render_device .features() .contains(WgpuFeatures::DUAL_SOURCE_BLENDING), @@ -595,7 +595,7 @@ pub(super) fn prepare_atmosphere_bind_groups( &ExtractedAtmosphere, &AtmosphereTextures, &ViewDepthTexture, - &Msaa, + &ExtractedView, ), (With, With), >, @@ -640,7 +640,7 @@ pub(super) fn prepare_atmosphere_bind_groups( .binding() .ok_or(AtmosphereBindGroupError::LightUniforms)?; - for (entity, atmosphere, textures, view_depth_texture, msaa) in &views { + for (entity, atmosphere, textures, view_depth_texture, view) in &views { let gpu_medium = gpu_media .get(atmosphere.medium) .ok_or(ScatteringMediumMissingError(atmosphere.medium))?; @@ -727,7 +727,7 @@ pub(super) fn prepare_atmosphere_bind_groups( let render_sky = render_device.create_bind_group( "render_sky_bind_group", - &pipeline_cache.get_bind_group_layout(if *msaa == Msaa::Off { + &pipeline_cache.get_bind_group_layout(if view.msaa_samples == 1 { &render_sky_layouts.render_sky } else { &render_sky_layouts.render_sky_msaa diff --git a/crates/bevy_pbr/src/decal/forward.rs b/crates/bevy_pbr/src/decal/forward.rs index 71a3e4f38a074..e313a56dcad2d 100644 --- a/crates/bevy_pbr/src/decal/forward.rs +++ b/crates/bevy_pbr/src/decal/forward.rs @@ -58,7 +58,7 @@ impl Plugin for ForwardDecalPlugin { /// * Any camera rendering a forward decal must have the [`bevy_core_pipeline::prepass::DepthPrepass`] component. /// * Looking at forward decals at a steep angle can cause distortion. This can be mitigated by padding your decal's /// texture with extra transparent pixels on the edges. -/// * On Wasm, requires using WebGPU and disabling `Msaa` on your camera. +/// * On Wasm, requires using WebGPU and disabling MSAA on your camera. #[derive(Component, Reflect)] #[require(Mesh3d)] #[component(on_add=forward_decal_set_mesh)] diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 699561a9dca98..ad095603b4481 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -20,7 +20,6 @@ use bevy_core_pipeline::{ tonemapping::{DebandDither, Tonemapping}, }; use bevy_ecs::{prelude::*, query::QueryItem}; -use bevy_image::BevyDefault as _; use bevy_light::{EnvironmentMapLight, IrradianceVolume, ShadowFilteringMethod}; use bevy_render::RenderStartup; use bevy_render::{ @@ -374,11 +373,7 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { shader: self.deferred_lighting_shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.contains(MeshPipelineKey::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.color_target_format(), blend: None, write_mask: ColorWrites::ALL, })], @@ -488,7 +483,7 @@ pub fn prepare_deferred_lighting_pipelines( continue; } - let mut view_key = MeshPipelineKey::from_hdr(view.hdr); + let mut view_key = MeshPipelineKey::from_color_target_format(view.color_target_format); if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 5c61c59a9a0f6..2645516886edd 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -55,7 +55,7 @@ use bevy_render::{ render_resource::*, renderer::RenderDevice, sync_world::MainEntity, - view::{ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity}, + view::{ExtractedView, RenderVisibilityRanges, RetainedViewEntity}, Extract, }; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; @@ -618,7 +618,7 @@ pub struct MaterialExtractionSystems; #[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] pub struct MaterialExtractEntitiesNeedingSpecializationSystems; -pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey { +pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa_samples: u32) -> MeshPipelineKey { match alpha_mode { // Premultiplied and Add share the same pipeline key // They're made distinct in the PBR shader, via `premultiply_alpha()` @@ -626,8 +626,8 @@ pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> Mesh AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA, AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY, AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD, - AlphaMode::AlphaToCoverage => match *msaa { - Msaa::Off => MeshPipelineKey::MAY_DISCARD, + AlphaMode::AlphaToCoverage => match msaa_samples { + 1 => MeshPipelineKey::MAY_DISCARD, _ => MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE, }, _ => MeshPipelineKey::NONE, @@ -1114,7 +1114,7 @@ pub(crate) fn specialize_material_meshes( material.properties.mesh_pipeline_key_bits.downcast(); mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key( material.properties.alpha_mode, - &Msaa::from_samples(view_key.msaa_samples()), + view_key.msaa_samples(), )); let mut mesh_key = *view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()) diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index 65cc14d7fc4be..32b31c42ba211 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -78,8 +78,8 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass( has_irradiance_volumes, ) in &mut views { - let mut view_key = - MeshPipelineKey::from_msaa_samples(1) | MeshPipelineKey::from_hdr(view.hdr); + let mut view_key = MeshPipelineKey::from_msaa_samples(1) + | MeshPipelineKey::from_color_target_format(view.color_target_format); if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; @@ -291,8 +291,8 @@ pub fn prepare_material_meshlet_meshes_prepass( (normal_prepass, motion_vector_prepass, deferred_prepass), ) in &mut views { - let mut view_key = - MeshPipelineKey::from_msaa_samples(1) | MeshPipelineKey::from_hdr(view.hdr); + let mut view_key = MeshPipelineKey::from_msaa_samples(1) + | MeshPipelineKey::from_color_target_format(view.color_target_format); if normal_prepass.is_some() { view_key |= MeshPipelineKey::NORMAL_PREPASS; diff --git a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs index 0e327bd007d81..32fb70e14b6d2 100644 --- a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs +++ b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs @@ -21,7 +21,6 @@ use bevy_ecs::{ world::World, }; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ @@ -37,7 +36,6 @@ use bevy_render::{ pub struct MeshletMainOpaquePass3dNode; impl ViewNode for MeshletMainOpaquePass3dNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ViewTarget, &'static MeshViewBindGroup, &'static ViewUniformOffset, @@ -58,7 +56,6 @@ impl ViewNode for MeshletMainOpaquePass3dNode { _graph: &mut RenderGraphContext, render_context: &mut RenderContext, ( - camera, target, mesh_view_bind_group, view_uniform_offset, @@ -112,9 +109,8 @@ impl ViewNode for MeshletMainOpaquePass3dNode { multiview_mask: None, }); let pass_span = diagnostics.pass_span(&mut render_pass, "meshlet_material_opaque_3d_pass"); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { + + if let Some(viewport) = Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } @@ -160,7 +156,6 @@ impl ViewNode for MeshletMainOpaquePass3dNode { pub struct MeshletPrepassNode; impl ViewNode for MeshletPrepassNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ViewPrepassTextures, &'static ViewUniformOffset, &'static PreviousViewUniformOffset, @@ -176,7 +171,6 @@ impl ViewNode for MeshletPrepassNode { _graph: &mut RenderGraphContext, render_context: &mut RenderContext, ( - camera, view_prepass_textures, view_uniform_offset, previous_view_uniform_offset, @@ -241,9 +235,8 @@ impl ViewNode for MeshletPrepassNode { multiview_mask: None, }); let pass_span = diagnostics.pass_span(&mut render_pass, "meshlet_material_prepass"); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { + + if let Some(viewport) = Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } @@ -293,7 +286,6 @@ impl ViewNode for MeshletPrepassNode { pub struct MeshletDeferredGBufferPrepassNode; impl ViewNode for MeshletDeferredGBufferPrepassNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ViewPrepassTextures, &'static ViewUniformOffset, &'static PreviousViewUniformOffset, @@ -309,7 +301,6 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode { _graph: &mut RenderGraphContext, render_context: &mut RenderContext, ( - camera, view_prepass_textures, view_uniform_offset, previous_view_uniform_offset, @@ -380,9 +371,8 @@ impl ViewNode for MeshletDeferredGBufferPrepassNode { }); let pass_span = diagnostics.pass_span(&mut render_pass, "meshlet_material_deferred_prepass"); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { + + if let Some(viewport) = Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index bbb546bf7e891..82debd445c315 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -81,7 +81,7 @@ use bevy_render::{ render_graph::{RenderGraphExt, ViewNodeRunner}, renderer::RenderDevice, settings::WgpuFeatures, - view::{prepare_view_targets, Msaa}, + view::{prepare_view_targets, ExtractedView}, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, }; use bevy_shader::load_shader_library; @@ -109,8 +109,8 @@ use tracing::error; /// /// This plugin currently works only on the Vulkan and Metal backends. /// -/// This plugin is not compatible with [`Msaa`]. Any camera rendering a [`MeshletMesh`] must have -/// [`Msaa`] set to [`Msaa::Off`]. +/// This plugin is not compatible with MSAA. Any camera rendering a [`MeshletMesh`] must have +/// main color target not be multisampled. /// /// Mixing forward+prepass and deferred rendering for opaque materials is not currently supported when using this plugin. /// You must use one or the other by setting [`crate::DefaultOpaqueRendererMethod`]. @@ -278,16 +278,16 @@ impl From<&MeshletMesh3d> for AssetId { fn configure_meshlet_views( mut views_3d: Query<( Entity, - &Msaa, + &ExtractedView, Has, Has, Has, )>, mut commands: Commands, ) { - for (entity, msaa, normal_prepass, motion_vector_prepass, deferred_prepass) in &mut views_3d { - if *msaa != Msaa::Off { - error!("MeshletPlugin can't be used with MSAA. Add Msaa::Off to your camera to use this plugin."); + for (entity, view, normal_prepass, motion_vector_prepass, deferred_prepass) in &mut views_3d { + if view.msaa_samples != 1 { + error!("MeshletPlugin can't be used with MSAA. Make sure your camera main color target isn't multisampled to use this plugin."); std::process::exit(1); } diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index cf9fa73e527fa..a6dcf4f348f25 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -13,7 +13,6 @@ use bevy_ecs::{ }; use bevy_math::UVec2; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::*, @@ -24,7 +23,6 @@ use bevy_render::{ /// Rasterize meshlets into a depth buffer, and optional visibility buffer + material depth buffer for shading passes. pub struct MeshletVisibilityBufferRasterPassNode { main_view_query: QueryState<( - &'static ExtractedCamera, &'static ViewDepthTexture, &'static ViewUniformOffset, &'static PreviousViewUniformOffset, @@ -65,7 +63,6 @@ impl Node for MeshletVisibilityBufferRasterPassNode { world: &World, ) -> Result<(), NodeRunError> { let Ok(( - camera, view_depth, view_offset, previous_view_offset, @@ -154,7 +151,6 @@ impl Node for MeshletVisibilityBufferRasterPassNode { visibility_buffer_software_raster_pipeline, visibility_buffer_hardware_raster_pipeline, fill_counts_pipeline, - Some(camera), meshlet_view_resources.rightmost_slot, ); render_context.command_encoder().pop_debug_group(); @@ -193,7 +189,6 @@ impl Node for MeshletVisibilityBufferRasterPassNode { visibility_buffer_software_raster_pipeline, visibility_buffer_hardware_raster_pipeline, fill_counts_pipeline, - Some(camera), meshlet_view_resources.rightmost_slot, ); render_context.command_encoder().pop_debug_group(); @@ -203,14 +198,12 @@ impl Node for MeshletVisibilityBufferRasterPassNode { view_depth.get_attachment(StoreOp::Store), meshlet_view_bind_groups, resolve_depth_pipeline, - camera, ); resolve_material_depth( render_context, meshlet_view_resources, meshlet_view_bind_groups, resolve_material_depth_pipeline, - camera, ); meshlet_view_resources.depth_pyramid.downsample_depth( "downsample_depth", @@ -282,7 +275,6 @@ impl Node for MeshletVisibilityBufferRasterPassNode { visibility_buffer_software_raster_shadow_view_pipeline, shadow_visibility_buffer_hardware_raster_pipeline, fill_counts_pipeline, - None, meshlet_view_resources.rightmost_slot, ); render_context.command_encoder().pop_debug_group(); @@ -321,7 +313,6 @@ impl Node for MeshletVisibilityBufferRasterPassNode { visibility_buffer_software_raster_shadow_view_pipeline, shadow_visibility_buffer_hardware_raster_pipeline, fill_counts_pipeline, - None, meshlet_view_resources.rightmost_slot, ); render_context.command_encoder().pop_debug_group(); @@ -331,7 +322,6 @@ impl Node for MeshletVisibilityBufferRasterPassNode { shadow_view.depth_attachment.get_attachment(StoreOp::Store), meshlet_view_bind_groups, resolve_depth_shadow_view_pipeline, - camera, ); meshlet_view_resources.depth_pyramid.downsample_depth( "downsample_depth", @@ -578,7 +568,6 @@ fn raster_pass( visibility_buffer_software_raster_pipeline: &ComputePipeline, visibility_buffer_hardware_raster_pipeline: &RenderPipeline, fill_counts_pipeline: &ComputePipeline, - camera: Option<&ExtractedCamera>, raster_cluster_rightmost_slot: u32, ) { let mut software_pass = @@ -621,9 +610,7 @@ fn raster_pass( occlusion_query_set: None, multiview_mask: None, }); - if let Some(viewport) = camera.and_then(|camera| camera.viewport.as_ref()) { - hardware_pass.set_camera_viewport(viewport); - } + hardware_pass.set_render_pipeline(visibility_buffer_hardware_raster_pipeline); hardware_pass.set_immediates(0, &raster_cluster_rightmost_slot.to_le_bytes()); hardware_pass.set_bind_group( @@ -651,7 +638,6 @@ fn resolve_depth( depth_stencil_attachment: RenderPassDepthStencilAttachment, meshlet_view_bind_groups: &MeshletViewBindGroups, resolve_depth_pipeline: &RenderPipeline, - camera: &ExtractedCamera, ) { let mut resolve_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("resolve_depth"), @@ -661,9 +647,7 @@ fn resolve_depth( occlusion_query_set: None, multiview_mask: None, }); - if let Some(viewport) = &camera.viewport { - resolve_pass.set_camera_viewport(viewport); - } + resolve_pass.set_render_pipeline(resolve_depth_pipeline); resolve_pass.set_bind_group(0, &meshlet_view_bind_groups.resolve_depth, &[]); resolve_pass.draw(0..3, 0..1); @@ -674,7 +658,6 @@ fn resolve_material_depth( meshlet_view_resources: &MeshletViewResources, meshlet_view_bind_groups: &MeshletViewBindGroups, resolve_material_depth_pipeline: &RenderPipeline, - camera: &ExtractedCamera, ) { if let (Some(material_depth), Some(resolve_material_depth_bind_group)) = ( meshlet_view_resources.material_depth.as_ref(), @@ -695,9 +678,7 @@ fn resolve_material_depth( occlusion_query_set: None, multiview_mask: None, }); - if let Some(viewport) = &camera.viewport { - resolve_pass.set_camera_viewport(viewport); - } + resolve_pass.set_render_pipeline(resolve_material_depth_pipeline); resolve_pass.set_bind_group(0, resolve_material_depth_bind_group, &[]); resolve_pass.draw(0..3, 0..1); diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 810b2cf1710e6..598615e24919d 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -37,8 +37,8 @@ use bevy_render::{ renderer::{RenderAdapter, RenderDevice, RenderQueue}, sync_world::RenderEntity, view::{ - ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity, ViewUniform, - ViewUniformOffset, ViewUniforms, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT, + ExtractedView, RenderVisibilityRanges, RetainedViewEntity, ViewUniform, ViewUniformOffset, + ViewUniforms, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT, }, Extract, ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, }; @@ -782,15 +782,14 @@ pub fn check_prepass_views_need_specialization( mut view_specialization_ticks: ResMut, mut views: Query<( &ExtractedView, - &Msaa, Option<&DepthPrepass>, Option<&NormalPrepass>, Option<&MotionVectorPrepass>, )>, ticks: SystemChangeTick, ) { - for (view, msaa, depth_prepass, normal_prepass, motion_vector_prepass) in views.iter_mut() { - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); + for (view, depth_prepass, normal_prepass, motion_vector_prepass) in views.iter_mut() { + let mut view_key = MeshPipelineKey::from_msaa_samples(view.msaa_samples); if depth_prepass.is_some() { view_key |= MeshPipelineKey::DEPTH_PREPASS; } @@ -837,7 +836,6 @@ pub(crate) struct SpecializePrepassSystemParam<'w, 's> { ( &'static ExtractedView, &'static RenderVisibleEntities, - &'static Msaa, Option<&'static MotionVectorPrepass>, Option<&'static DeferredPrepass>, ), @@ -887,9 +885,7 @@ pub(crate) fn specialize_prepass_material_meshes( this_run = system_change_tick.this_run(); - for (extracted_view, visible_entities, msaa, motion_vector_prepass, deferred_prepass) in - &views - { + for (extracted_view, visible_entities, motion_vector_prepass, deferred_prepass) in &views { if !opaque_deferred_render_phases.contains_key(&extracted_view.retained_view_entity) && !alpha_mask_deferred_render_phases .contains_key(&extracted_view.retained_view_entity) @@ -955,7 +951,8 @@ pub(crate) fn specialize_prepass_material_meshes( let alpha_mode = material.properties.alpha_mode; match alpha_mode { AlphaMode::Opaque | AlphaMode::AlphaToCoverage | AlphaMode::Mask(_) => { - mesh_key |= alpha_mode_pipeline_key(alpha_mode, msaa); + mesh_key |= + alpha_mode_pipeline_key(alpha_mode, extracted_view.msaa_samples); } AlphaMode::Blend | AlphaMode::Premultiplied diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 9040d219a0b05..2c6348cf337cb 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -20,6 +20,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::Read, SystemParam, SystemState}, }; +use bevy_image::BevyDefault; use bevy_light::cascade::Cascade; use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType}; use bevy_light::cluster::GlobalVisibleClusterableObjects; @@ -1373,6 +1374,8 @@ pub fn prepare_lights( hdr: false, color_grading: Default::default(), invert_culling: false, + msaa_samples: 1, + color_target_format: TextureFormat::bevy_default(), }, *frustum, LightEntity::Point { @@ -1476,6 +1479,8 @@ pub fn prepare_lights( hdr: false, color_grading: Default::default(), invert_culling: false, + msaa_samples: 1, + color_target_format: TextureFormat::bevy_default(), }, *spot_light_frustum.unwrap(), LightEntity::Spot { light_entity }, @@ -1625,6 +1630,8 @@ pub fn prepare_lights( hdr: false, color_grading: Default::default(), invert_culling: false, + msaa_samples: 1, + color_target_format: TextureFormat::bevy_default(), }, frustum, LightEntity::Directional { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 96ddaa7123b1e..c2672c3c24fb8 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -25,7 +25,7 @@ use bevy_ecs::{ relationship::RelationshipSourceCollection, system::{lifetimeless::*, SystemParamItem, SystemState}, }; -use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo}; +use bevy_image::{ImageSampler, TextureFormatPixelInfo}; use bevy_light::{ EnvironmentMapLight, IrradianceVolume, NotShadowCaster, NotShadowReceiver, ShadowFilteringMethod, TransmittedShadowReceiver, @@ -56,8 +56,7 @@ use bevy_render::{ sync_world::MainEntityHashSet, texture::{DefaultImageSampler, GpuImage}, view::{ - self, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, ViewTarget, - ViewUniformOffset, + self, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, ViewUniformOffset, }, Extract, }; @@ -86,7 +85,6 @@ use bevy_core_pipeline::tonemapping::{DebandDither, Tonemapping}; use bevy_ecs::change_detection::Tick; use bevy_ecs::system::SystemChangeTick; use bevy_render::camera::TemporalJitter; -use bevy_render::prelude::Msaa; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::ExtractedView; use bevy_render::RenderSystems::PrepareAssets; @@ -314,7 +312,6 @@ pub fn check_views_need_specialization( mut view_specialization_ticks: ResMut, mut views: Query<( &ExtractedView, - &Msaa, Option<&Tonemapping>, Option<&DebandDither>, Option<&ShadowFilteringMethod>, @@ -341,7 +338,6 @@ pub fn check_views_need_specialization( ) { for ( view, - msaa, tonemapping, dither, shadow_filter_method, @@ -357,8 +353,8 @@ pub fn check_views_need_specialization( has_ssr, ) in views.iter_mut() { - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); + let mut view_key = MeshPipelineKey::from_msaa_samples(view.msaa_samples) + | MeshPipelineKey::from_color_target_format(view.color_target_format); if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; @@ -2334,6 +2330,19 @@ bitflags::bitflags! { Self::SHADOW_FILTER_METHOD_RESERVED_BITS.bits() | Self::VIEW_PROJECTION_RESERVED_BITS.bits() | Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS.bits(); + + const COLOR_TARGET_FORMAT_RESERVED_BITS = Self::COLOR_TARGET_FORMAT_MASK_BITS << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_R8UNORM = 0 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RG8UNORM = 1 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA8UNORM = 2 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA8UNORMSRGB = 3 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_BGRA8UNORM = 4 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_BGRA8UNORMSRGB = 5 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_R16FLOAT = 6 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RG16FLOAT = 7 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA16FLOAT = 8 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RB11B10FLOAT = 9 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGB10A2UNORM = 10 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; } } @@ -2361,20 +2370,17 @@ impl MeshPipelineKey { const SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS: u64 = Self::VIEW_PROJECTION_MASK_BITS.count_ones() as u64 + Self::VIEW_PROJECTION_SHIFT_BITS; + const COLOR_TARGET_FORMAT_MASK_BITS: u64 = 0b1111; + const COLOR_TARGET_FORMAT_SHIFT_BITS: u64 = Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS + .count_ones() as u64 + + Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; + pub fn from_msaa_samples(msaa_samples: u32) -> Self { let msaa_bits = (msaa_samples.trailing_zeros() as u64 & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; Self::from_bits_retain(msaa_bits) } - pub fn from_hdr(hdr: bool) -> Self { - if hdr { - MeshPipelineKey::HDR - } else { - MeshPipelineKey::NONE - } - } - pub fn msaa_samples(&self) -> u32 { 1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } @@ -2399,6 +2405,57 @@ impl MeshPipelineKey { _ => PrimitiveTopology::default(), } } + + /// Create a pipeline key from view target format. + #[inline] + pub fn from_color_target_format(format: TextureFormat) -> Self { + match format { + TextureFormat::R8Unorm => Self::COLOR_TARGET_FORMAT_R8UNORM, + TextureFormat::Rg8Unorm => Self::COLOR_TARGET_FORMAT_RG8UNORM, + TextureFormat::Rgba8Unorm => Self::COLOR_TARGET_FORMAT_RGBA8UNORM, + TextureFormat::Rgba8UnormSrgb => Self::COLOR_TARGET_FORMAT_RGBA8UNORMSRGB, + TextureFormat::Bgra8Unorm => Self::COLOR_TARGET_FORMAT_BGRA8UNORM, + TextureFormat::Bgra8UnormSrgb => Self::COLOR_TARGET_FORMAT_BGRA8UNORMSRGB, + TextureFormat::R16Float => Self::COLOR_TARGET_FORMAT_R16FLOAT, + TextureFormat::Rg16Float => Self::COLOR_TARGET_FORMAT_RG16FLOAT, + TextureFormat::Rgba16Float => Self::COLOR_TARGET_FORMAT_RGBA16FLOAT, + TextureFormat::Rg11b10Ufloat => Self::COLOR_TARGET_FORMAT_RB11B10FLOAT, + TextureFormat::Rgb10a2Unorm => Self::COLOR_TARGET_FORMAT_RGB10A2UNORM, + _ => unreachable!("Unsupported view target format"), + } + } + + /// Get the view target format of this pipeline key. + #[inline] + pub fn color_target_format(&self) -> TextureFormat { + let target_format = *self & Self::COLOR_TARGET_FORMAT_RESERVED_BITS; + + if target_format == Self::COLOR_TARGET_FORMAT_R8UNORM { + TextureFormat::R8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RG8UNORM { + TextureFormat::Rg8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA8UNORM { + TextureFormat::Rgba8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA8UNORMSRGB { + TextureFormat::Rgba8UnormSrgb + } else if target_format == Self::COLOR_TARGET_FORMAT_BGRA8UNORM { + TextureFormat::Bgra8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_BGRA8UNORMSRGB { + TextureFormat::Bgra8UnormSrgb + } else if target_format == Self::COLOR_TARGET_FORMAT_R16FLOAT { + TextureFormat::R16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RG16FLOAT { + TextureFormat::Rg16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA16FLOAT { + TextureFormat::Rgba16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RB11B10FLOAT { + TextureFormat::Rg11b10Ufloat + } else if target_format == Self::COLOR_TARGET_FORMAT_RGB10A2UNORM { + TextureFormat::Rgb10a2Unorm + } else { + unreachable!("Unsupported view target format") + } + } } impl From for MeshPipelineKey { @@ -2799,12 +2856,6 @@ impl SpecializedMeshPipeline for MeshPipeline { } } - let format = if key.contains(MeshPipelineKey::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }; - // This is defined here so that custom shaders that use something other than // the mesh binding from bevy_pbr::mesh_bindings can easily make use of this // in their own shaders. @@ -2826,7 +2877,7 @@ impl SpecializedMeshPipeline for MeshPipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format, + format: key.color_target_format(), blend, write_mask: ColorWrites::ALL, })], diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 77fdc3d45525d..14364b1b18f6b 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -26,7 +26,7 @@ use bevy_render::{ renderer::{RenderAdapter, RenderDevice}, texture::{FallbackImage, FallbackImageMsaa, FallbackImageZero, GpuImage}, view::{ - Msaa, RenderVisibilityRanges, ViewUniform, ViewUniforms, + ExtractedView, RenderVisibilityRanges, ViewUniform, ViewUniforms, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT, }, }; @@ -144,6 +144,16 @@ impl MeshPipelineViewLayoutKey { }, ) } + + pub fn from_msaa_samples(value: u32) -> Self { + let mut result = MeshPipelineViewLayoutKey::empty(); + + if value > 1 { + result |= MeshPipelineViewLayoutKey::MULTISAMPLED; + } + + result + } } impl From for MeshPipelineViewLayoutKey { @@ -180,18 +190,6 @@ impl From for MeshPipelineViewLayoutKey { } } -impl From for MeshPipelineViewLayoutKey { - fn from(value: Msaa) -> Self { - let mut result = MeshPipelineViewLayoutKey::empty(); - - if value.samples() > 1 { - result |= MeshPipelineViewLayoutKey::MULTISAMPLED; - } - - result - } -} - impl From> for MeshPipelineViewLayoutKey { fn from(value: Option<&ViewPrepassTextures>) -> Self { let mut result = MeshPipelineViewLayoutKey::empty(); @@ -603,7 +601,7 @@ pub fn prepare_mesh_view_bind_groups( Entity, &ViewShadowBindings, &ViewClusterBindings, - &Msaa, + &ExtractedView, Option<&ScreenSpaceAmbientOcclusionResources>, Option<&ViewPrepassTextures>, Option<&ViewTransmissionTexture>, @@ -667,7 +665,7 @@ pub fn prepare_mesh_view_bind_groups( entity, shadow_bindings, cluster_bindings, - msaa, + view, ssao_resources, prepass_textures, transmission_texture, @@ -688,7 +686,7 @@ pub fn prepare_mesh_view_bind_groups( .map(|t| &t.screen_space_ambient_occlusion_texture.default_view) .unwrap_or(&fallback_ssao); - let mut layout_key = MeshPipelineViewLayoutKey::from(*msaa) + let mut layout_key = MeshPipelineViewLayoutKey::from_msaa_samples(view.msaa_samples) | MeshPipelineViewLayoutKey::from(prepass_textures); if has_oit { layout_key |= MeshPipelineViewLayoutKey::OIT_ENABLED; @@ -738,7 +736,8 @@ pub fn prepare_mesh_view_bind_groups( // When using WebGL, we can't have a depth texture with multisampling let prepass_bindings; - if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32"))) || msaa.samples() == 1 + if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32"))) + || view.msaa_samples == 1 { prepass_bindings = prepass::get_bindings(prepass_textures); for (binding, index) in prepass_bindings diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 59f4a1255b920..e064a026e2fb2 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -33,7 +33,7 @@ use bevy_render::{ sync_component::SyncComponentPlugin, sync_world::RenderEntity, texture::{CachedTexture, TextureCache}, - view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms}, + view::{ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_shader::{load_shader_library, Shader, ShaderDefVal}; @@ -191,18 +191,11 @@ impl ViewNode for SsaoNode { ) -> Result<(), NodeRunError> { let pipelines = world.resource::(); let pipeline_cache = world.resource::(); - let ( - Some(camera_size), - Some(preprocess_depth_pipeline), - Some(spatial_denoise_pipeline), - Some(ssao_pipeline), - ) = ( - camera.physical_viewport_size, + let (Some(preprocess_depth_pipeline), Some(spatial_denoise_pipeline), Some(ssao_pipeline)) = ( pipeline_cache.get_compute_pipeline(pipelines.preprocess_depth_pipeline), pipeline_cache.get_compute_pipeline(pipelines.spatial_denoise_pipeline), pipeline_cache.get_compute_pipeline(pipeline_id.0), - ) - else { + ) else { return Ok(()); }; @@ -226,8 +219,8 @@ impl ViewNode for SsaoNode { &[view_uniform_offset.offset], ); preprocess_depth_pass.dispatch_workgroups( - camera_size.x.div_ceil(16), - camera_size.y.div_ceil(16), + camera.main_color_target_size.x.div_ceil(16), + camera.main_color_target_size.y.div_ceil(16), 1, ); } @@ -244,7 +237,11 @@ impl ViewNode for SsaoNode { &bind_groups.common_bind_group, &[view_uniform_offset.offset], ); - ssao_pass.dispatch_workgroups(camera_size.x.div_ceil(8), camera_size.y.div_ceil(8), 1); + ssao_pass.dispatch_workgroups( + camera.main_color_target_size.x.div_ceil(8), + camera.main_color_target_size.y.div_ceil(8), + 1, + ); } { @@ -261,8 +258,8 @@ impl ViewNode for SsaoNode { &[view_uniform_offset.offset], ); spatial_denoise_pass.dispatch_workgroups( - camera_size.x.div_ceil(8), - camera_size.y.div_ceil(8), + camera.main_color_target_size.x.div_ceil(8), + camera.main_color_target_size.y.div_ceil(8), 1, ); } @@ -496,16 +493,21 @@ fn extract_ssao_settings( mut commands: Commands, cameras: Extract< Query< - (RenderEntity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa), + ( + RenderEntity, + &Camera, + &ScreenSpaceAmbientOcclusion, + &ExtractedView, + ), (With, With, With), >, >, ) { - for (entity, camera, ssao_settings, msaa) in &cameras { - if *msaa != Msaa::Off { + for (entity, camera, ssao_settings, view) in &cameras { + if view.msaa_samples != 1 { error!( - "SSAO is being used which requires Msaa::Off, but Msaa is currently set to Msaa::{:?}", - *msaa + "SSAO is being used which requires MSAA off, but the msaa samples is currently set to {:?}", + view.msaa_samples ); return; } @@ -537,10 +539,7 @@ fn prepare_ssao_textures( views: Query<(Entity, &ExtractedCamera, &ScreenSpaceAmbientOcclusion)>, ) { for (entity, camera, ssao_settings) in &views { - let Some(physical_viewport_size) = camera.physical_viewport_size else { - continue; - }; - let size = physical_viewport_size.to_extents(); + let size = camera.main_color_target_size.to_extents(); let preprocessed_depth_texture = texture_cache.get( &render_device, diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index c0a5dc0aa496f..724f3996ceb5d 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -23,7 +23,6 @@ use bevy_ecs::{ system::{lifetimeless::Read, Commands, Query, Res, ResMut}, world::World, }; -use bevy_image::BevyDefault as _; use bevy_light::EnvironmentMapLight; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ @@ -44,7 +43,7 @@ use bevy_render::{ }, renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue}, texture::GpuImage, - view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset}, + view::{ExtractedView, ViewTarget, ViewUniformOffset}, Render, RenderApp, RenderStartup, RenderSystems, }; use bevy_shader::{load_shader_library, Shader}; @@ -200,7 +199,7 @@ pub struct ViewScreenSpaceReflectionsUniformOffset(u32); #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct ScreenSpaceReflectionsPipelineKey { mesh_pipeline_view_key: MeshPipelineViewLayoutKey, - is_hdr: bool, + texture_format: TextureFormat, has_environment_maps: bool, has_atmosphere: bool, } @@ -485,7 +484,7 @@ pub fn prepare_ssr_pipelines( { // SSR is only supported in the deferred pipeline, which has no MSAA // support. Thus we can assume MSAA is off. - let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(Msaa::Off) + let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from_msaa_samples(1) | MeshPipelineViewLayoutKey::DEPTH_PREPASS | MeshPipelineViewLayoutKey::DEFERRED_PREPASS; mesh_pipeline_view_key.set( @@ -507,7 +506,7 @@ pub fn prepare_ssr_pipelines( &ssr_pipeline, ScreenSpaceReflectionsPipelineKey { mesh_pipeline_view_key, - is_hdr: extracted_view.hdr, + texture_format: extracted_view.color_target_format, has_environment_maps, has_atmosphere, }, @@ -612,11 +611,7 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline { shader: self.fragment_shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.is_hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.texture_format, blend: None, write_mask: ColorWrites::ALL, })], diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index bc50a314a07ef..9359a93e63c2f 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -17,7 +17,7 @@ use bevy_ecs::{ system::{lifetimeless::Read, Commands, Local, Query, Res, ResMut}, world::World, }; -use bevy_image::{BevyDefault, Image}; +use bevy_image::Image; use bevy_light::{FogVolume, VolumetricFog, VolumetricLight}; use bevy_math::{vec4, Affine3A, Mat4, Vec3, Vec3A, Vec4}; use bevy_mesh::{Mesh, MeshVertexBufferLayoutRef}; @@ -41,7 +41,7 @@ use bevy_render::{ renderer::{RenderContext, RenderDevice, RenderQueue}, sync_world::RenderEntity, texture::GpuImage, - view::{ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniformOffset}, + view::{ExtractedView, ViewDepthTexture, ViewTarget, ViewUniformOffset}, Extract, }; use bevy_shader::Shader; @@ -143,6 +143,7 @@ pub struct VolumetricFogPipelineKey { /// Flags that specify features on the pipeline key. flags: VolumetricFogPipelineKeyFlags, + texture_format: TextureFormat, } /// The same as [`VolumetricFog`] and [`FogVolume`], but formatted for @@ -305,6 +306,7 @@ pub fn extract_volumetric_fog( impl ViewNode for VolumetricFogNode { type ViewQuery = ( + Read, Read, Read, Read, @@ -316,7 +318,6 @@ impl ViewNode for VolumetricFogNode { Read, Read, Read, - Read, Read, ); @@ -325,6 +326,7 @@ impl ViewNode for VolumetricFogNode { _: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, ( + view, view_target, view_depth_texture, view_volumetric_lighting_pipelines, @@ -336,7 +338,6 @@ impl ViewNode for VolumetricFogNode { view_bind_group, view_ssr_offset, view_contact_shadows_offset, - msaa, view_environment_map_offset, ): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, @@ -411,7 +412,7 @@ impl ViewNode for VolumetricFogNode { let mut bind_group_layout_key = VolumetricFogBindGroupLayoutKey::empty(); bind_group_layout_key.set( VolumetricFogBindGroupLayoutKey::MULTISAMPLED, - !matches!(*msaa, Msaa::Off), + view.msaa_samples > 1, ); // Create the bind group entries. The ones relating to the density @@ -592,11 +593,7 @@ impl SpecializedRenderPipeline for VolumetricFogPipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.flags.contains(VolumetricFogPipelineKeyFlags::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.texture_format, // Blend on top of what's already in the framebuffer. Doing // the alpha blending with the hardware blender allows us to // avoid having to use intermediate render targets. @@ -632,7 +629,6 @@ pub fn prepare_volumetric_fog_pipelines( ( Entity, &ExtractedView, - &Msaa, Has, Has, Has, @@ -651,7 +647,6 @@ pub fn prepare_volumetric_fog_pipelines( for ( entity, view, - msaa, normal_prepass, depth_prepass, motion_vector_prepass, @@ -660,7 +655,8 @@ pub fn prepare_volumetric_fog_pipelines( ) in view_targets.iter() { // Create a mesh pipeline view layout key corresponding to the view. - let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(*msaa); + let mut mesh_pipeline_view_key = + MeshPipelineViewLayoutKey::from_msaa_samples(view.msaa_samples); mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::NORMAL_PREPASS, normal_prepass); mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::DEPTH_PREPASS, depth_prepass); mesh_pipeline_view_key.set( @@ -684,6 +680,7 @@ pub fn prepare_volumetric_fog_pipelines( mesh_pipeline_view_key, vertex_buffer_layout: plane_mesh.layout.clone(), flags: textureless_flags, + texture_format: view.color_target_format, }; let textureless_pipeline_id = pipelines.specialize( &pipeline_cache, diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 5bb66de1ec43e..ab4b98bea3856 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -26,7 +26,7 @@ use bevy_platform::{ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, - camera::{extract_cameras, ExtractedCamera}, + camera::extract_cameras, diagnostic::RecordDiagnostics, extract_resource::ExtractResource, mesh::{ @@ -364,7 +364,6 @@ impl SpecializedMeshPipeline for Wireframe3dPipeline { struct Wireframe3dNode; impl ViewNode for Wireframe3dNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, @@ -374,7 +373,7 @@ impl ViewNode for Wireframe3dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, + (view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let Some(wireframe_phase) = world.get_resource::>() @@ -398,10 +397,6 @@ impl ViewNode for Wireframe3dNode { }); let pass_span = diagnostics.pass_span(&mut render_pass, "wireframe_3d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } - if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) { error!("Error encountered while rendering the stencil phase {err:?}"); return Err(NodeRunError::DrawError(err)); diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs index efa634e05a40c..7cf846b5161cb 100644 --- a/crates/bevy_picking/src/input.rs +++ b/crates/bevy_picking/src/input.rs @@ -133,7 +133,7 @@ pub fn mouse_pick_events( WindowEvent::CursorMoved(event) => { let location = Location { target: match RenderTarget::Window(WindowRef::Entity(event.window)) - .normalize(primary_window.single().ok()) + .normalize(primary_window.single().ok(), None) { Some(target) => target, None => continue, @@ -153,7 +153,7 @@ pub fn mouse_pick_events( WindowEvent::MouseButtonInput(input) => { let location = Location { target: match RenderTarget::Window(WindowRef::Entity(input.window)) - .normalize(primary_window.single().ok()) + .normalize(primary_window.single().ok(), None) { Some(target) => target, None => continue, @@ -177,7 +177,7 @@ pub fn mouse_pick_events( let location = Location { target: match RenderTarget::Window(WindowRef::Entity(window)) - .normalize(primary_window.single().ok()) + .normalize(primary_window.single().ok(), None) { Some(target) => target, None => continue, @@ -210,7 +210,7 @@ pub fn touch_pick_events( let pointer = PointerId::Touch(touch.id); let location = Location { target: match RenderTarget::Window(WindowRef::Entity(touch.window)) - .normalize(primary_window.single().ok()) + .normalize(primary_window.single().ok(), None) { Some(target) => target, None => continue, diff --git a/crates/bevy_picking/src/pointer.rs b/crates/bevy_picking/src/pointer.rs index 1ef36a323346c..be5da67975c0d 100644 --- a/crates/bevy_picking/src/pointer.rs +++ b/crates/bevy_picking/src/pointer.rs @@ -227,10 +227,13 @@ impl Location { primary_window: &Query>, ) -> bool { if render_target - .normalize(Some(match primary_window.single() { - Ok(w) => w, - Err(_) => return false, - })) + .normalize( + Some(match primary_window.single() { + Ok(w) => w, + Err(_) => return false, + }), + None, + ) .as_ref() != Some(&self.target) { diff --git a/crates/bevy_post_process/src/bloom/bloom.wgsl b/crates/bevy_post_process/src/bloom/bloom.wgsl index 8a7c3833c820e..d1d8830a4334e 100644 --- a/crates/bevy_post_process/src/bloom/bloom.wgsl +++ b/crates/bevy_post_process/src/bloom/bloom.wgsl @@ -8,9 +8,7 @@ struct BloomUniforms { threshold_precomputations: vec4, - viewport: vec4, scale: vec2, - aspect: f32, }; @group(0) @binding(0) var input_texture: texture_2d; @@ -156,8 +154,7 @@ fn sample_input_3x3_tent(uv: vec2) -> vec3 { #ifdef FIRST_DOWNSAMPLE @fragment fn downsample_first(@location(0) output_uv: vec2) -> @location(0) vec4 { - let sample_uv = uniforms.viewport.xy + output_uv * uniforms.viewport.zw; - var sample = sample_input_13_tap(sample_uv); + var sample = sample_input_13_tap(output_uv); // Lower bound of 0.0001 is to avoid propagating multiplying by 0.0 through the // downscaling and upscaling which would result in black boxes. // The upper bound is to prevent NaNs. diff --git a/crates/bevy_post_process/src/bloom/downsampling_pipeline.rs b/crates/bevy_post_process/src/bloom/downsampling_pipeline.rs index 4e94743d1f01f..104ca65d20d3a 100644 --- a/crates/bevy_post_process/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_post_process/src/bloom/downsampling_pipeline.rs @@ -48,9 +48,7 @@ pub struct BloomDownsamplingPipelineKeys { pub struct BloomUniforms { // Precomputed values used when thresholding, see https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4 pub threshold_precomputations: Vec4, - pub viewport: Vec4, pub scale: Vec2, - pub aspect: f32, } pub fn init_bloom_downsampling_pipeline( diff --git a/crates/bevy_post_process/src/bloom/mod.rs b/crates/bevy_post_process/src/bloom/mod.rs index cd68f9d5d8b9a..55ed0ba93bed5 100644 --- a/crates/bevy_post_process/src/bloom/mod.rs +++ b/crates/bevy_post_process/src/bloom/mod.rs @@ -104,7 +104,6 @@ impl Plugin for BloomPlugin { struct BloomNode; impl ViewNode for BloomNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ViewTarget, &'static BloomTexture, &'static BloomBindGroups, @@ -122,7 +121,6 @@ impl ViewNode for BloomNode { _graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, ( - camera, view_target, bloom_texture, bind_groups, @@ -292,16 +290,7 @@ impl ViewNode for BloomNode { &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize], &[uniform_index.index()], ); - if let Some(viewport) = camera.viewport.as_ref() { - upsampling_final_pass.set_viewport( - viewport.physical_position.x as f32, - viewport.physical_position.y as f32, - viewport.physical_size.x as f32, - viewport.physical_size.y as f32, - viewport.depth.start, - viewport.depth.end, - ); - } + let blend = compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32); upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend).into()); @@ -364,58 +353,58 @@ fn prepare_bloom_textures( views: Query<(Entity, &ExtractedCamera, &Bloom)>, ) { for (entity, camera, bloom) in &views { - if let Some(viewport) = camera.physical_viewport_size { - // How many times we can halve the resolution minus one so we don't go unnecessarily low - let mip_count = bloom.max_mip_dimension.ilog2().max(2) - 1; - let mip_height_ratio = if viewport.y != 0 { - bloom.max_mip_dimension as f32 / viewport.y as f32 - } else { - 0. - }; - - let texture_descriptor = TextureDescriptor { - label: Some("bloom_texture"), - size: (viewport.as_vec2() * mip_height_ratio) - .round() - .as_uvec2() - .max(UVec2::ONE) - .to_extents(), - mip_level_count: mip_count, - sample_count: 1, - dimension: TextureDimension::D2, - format: BLOOM_TEXTURE_FORMAT, - usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }; - - #[cfg(any( - not(feature = "webgl"), - not(target_arch = "wasm32"), - feature = "webgpu" - ))] - let texture = texture_cache.get(&render_device, texture_descriptor); - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - let texture: Vec = (0..mip_count) - .map(|mip| { - texture_cache.get( - &render_device, - TextureDescriptor { - size: Extent3d { - width: (texture_descriptor.size.width >> mip).max(1), - height: (texture_descriptor.size.height >> mip).max(1), - depth_or_array_layers: 1, - }, - mip_level_count: 1, - ..texture_descriptor.clone() + let viewport = camera.main_color_target_size; + let mip_height_ratio = if viewport.y != 0 { + bloom.max_mip_dimension as f32 / viewport.y as f32 + } else { + 0. + } + .min(1.0); + let size = (viewport.as_vec2() * mip_height_ratio) + .round() + .as_uvec2() + .max(UVec2::ONE); + // How many times we can halve the resolution minus one so we don't go unnecessarily low + let mip_count = size.min_element().ilog2().max(2) - 1; + + let texture_descriptor = TextureDescriptor { + label: Some("bloom_texture"), + size: size.to_extents(), + mip_level_count: mip_count, + sample_count: 1, + dimension: TextureDimension::D2, + format: BLOOM_TEXTURE_FORMAT, + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }; + + #[cfg(any( + not(feature = "webgl"), + not(target_arch = "wasm32"), + feature = "webgpu" + ))] + let texture = texture_cache.get(&render_device, texture_descriptor); + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + let texture: Vec = (0..mip_count) + .map(|mip| { + texture_cache.get( + &render_device, + TextureDescriptor { + size: Extent3d { + width: (texture_descriptor.size.width >> mip).max(1), + height: (texture_descriptor.size.height >> mip).max(1), + depth_or_array_layers: 1, }, - ) - }) - .collect(); + mip_level_count: 1, + ..texture_descriptor.clone() + }, + ) + }) + .collect(); - commands - .entity(entity) - .insert(BloomTexture { texture, mip_count }); - } + commands + .entity(entity) + .insert(BloomTexture { texture, mip_count }); } } diff --git a/crates/bevy_post_process/src/bloom/settings.rs b/crates/bevy_post_process/src/bloom/settings.rs index 39457dac8efd2..3f9ab2647345b 100644 --- a/crates/bevy_post_process/src/bloom/settings.rs +++ b/crates/bevy_post_process/src/bloom/settings.rs @@ -1,11 +1,6 @@ use super::downsampling_pipeline::BloomUniforms; -use bevy_camera::Camera; -use bevy_ecs::{ - prelude::Component, - query::{QueryItem, With}, - reflect::ReflectComponent, -}; -use bevy_math::{AspectRatio, URect, UVec4, Vec2, Vec4}; +use bevy_ecs::{prelude::Component, query::QueryItem, reflect::ReflectComponent}; +use bevy_math::{Vec2, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{extract_component::ExtractComponent, view::Hdr}; @@ -223,44 +218,26 @@ pub enum BloomCompositeMode { } impl ExtractComponent for Bloom { - type QueryData = (&'static Self, &'static Camera); + type QueryData = &'static Self; - type QueryFilter = With; + type QueryFilter = (); type Out = (Self, BloomUniforms); - fn extract_component((bloom, camera): QueryItem<'_, '_, Self::QueryData>) -> Option { - match ( - camera.physical_viewport_rect(), - camera.physical_viewport_size(), - camera.physical_target_size(), - camera.is_active, - ) { - (Some(URect { min: origin, .. }), Some(size), Some(target_size), true) - if size.x != 0 && size.y != 0 => - { - let threshold = bloom.prefilter.threshold; - let threshold_softness = bloom.prefilter.threshold_softness; - let knee = threshold * threshold_softness.clamp(0.0, 1.0); - - let uniform = BloomUniforms { - threshold_precomputations: Vec4::new( - threshold, - threshold - knee, - 2.0 * knee, - 0.25 / (knee + 0.00001), - ), - viewport: UVec4::new(origin.x, origin.y, size.x, size.y).as_vec4() - / UVec4::new(target_size.x, target_size.y, target_size.x, target_size.y) - .as_vec4(), - aspect: AspectRatio::try_from_pixels(size.x, size.y) - .expect("Valid screen size values for Bloom settings") - .ratio(), - scale: bloom.scale, - }; - - Some((bloom.clone(), uniform)) - } - _ => None, - } + fn extract_component(bloom: QueryItem<'_, '_, Self::QueryData>) -> Option { + let threshold = bloom.prefilter.threshold; + let threshold_softness = bloom.prefilter.threshold_softness; + let knee = threshold * threshold_softness.clamp(0.0, 1.0); + + let uniform = BloomUniforms { + threshold_precomputations: Vec4::new( + threshold, + threshold - knee, + 2.0 * knee, + 0.25 / (knee + 0.00001), + ), + scale: bloom.scale, + }; + + Some((bloom.clone(), uniform)) } } diff --git a/crates/bevy_post_process/src/bloom/upsampling_pipeline.rs b/crates/bevy_post_process/src/bloom/upsampling_pipeline.rs index 96f47dac09a48..1107c74a2dda3 100644 --- a/crates/bevy_post_process/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_post_process/src/bloom/upsampling_pipeline.rs @@ -14,7 +14,7 @@ use bevy_render::{ binding_types::{sampler, texture_2d, uniform_buffer}, *, }, - view::ViewTarget, + view::ExtractedView, }; use bevy_shader::Shader; use bevy_utils::default; @@ -37,7 +37,7 @@ pub struct BloomUpsamplingPipeline { #[derive(PartialEq, Eq, Hash, Clone)] pub struct BloomUpsamplingPipelineKeys { composite_mode: BloomCompositeMode, - final_pipeline: bool, + texture_format: TextureFormat, } pub fn init_bloom_upscaling_pipeline( @@ -71,12 +71,6 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline { type Key = BloomUpsamplingPipelineKeys; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let texture_format = if key.final_pipeline { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - BLOOM_TEXTURE_FORMAT - }; - let color_blend = match key.composite_mode { BloomCompositeMode::EnergyConserving => { // At the time of developing this we decided to blend our @@ -117,7 +111,7 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline { shader: self.fragment_shader.clone(), entry_point: Some("upsample".into()), targets: vec![Some(ColorTargetState { - format: texture_format, + format: key.texture_format, blend: Some(BlendState { color: color_blend, alpha: BlendComponent { @@ -140,15 +134,15 @@ pub fn prepare_upsampling_pipeline( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<(Entity, &Bloom)>, + views: Query<(Entity, &ExtractedView, &Bloom)>, ) { - for (entity, bloom) in &views { + for (entity, view, bloom) in &views { let pipeline_id = pipelines.specialize( &pipeline_cache, &pipeline, BloomUpsamplingPipelineKeys { composite_mode: bloom.composite_mode, - final_pipeline: false, + texture_format: BLOOM_TEXTURE_FORMAT, }, ); @@ -157,7 +151,7 @@ pub fn prepare_upsampling_pipeline( &pipeline, BloomUpsamplingPipelineKeys { composite_mode: bloom.composite_mode, - final_pipeline: true, + texture_format: view.color_target_format, }, ); diff --git a/crates/bevy_post_process/src/dof/mod.rs b/crates/bevy_post_process/src/dof/mod.rs index 4cff455fe7fde..fd79c01d597df 100644 --- a/crates/bevy_post_process/src/dof/mod.rs +++ b/crates/bevy_post_process/src/dof/mod.rs @@ -28,7 +28,6 @@ use bevy_ecs::{ system::{lifetimeless::Read, Commands, Query, Res, ResMut}, world::World, }; -use bevy_image::BevyDefault as _; use bevy_math::ops; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::{ @@ -53,7 +52,7 @@ use bevy_render::{ sync_world::RenderEntity, texture::{CachedTexture, TextureCache}, view::{ - prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform, + prepare_view_targets, ExtractedView, ViewDepthTexture, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, }, Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, @@ -186,8 +185,7 @@ pub struct DepthOfFieldUniform { pub struct DepthOfFieldPipelineKey { /// Whether we're doing Gaussian or bokeh blur. pass: DofPass, - /// Whether we're using HDR. - hdr: bool, + texture_format: TextureFormat, /// Whether the render target is multisampled. multisample: bool, } @@ -541,9 +539,9 @@ pub fn init_dof_global_bind_group_layout(mut commands: Commands, render_device: /// specific to each view. pub fn prepare_depth_of_field_view_bind_group_layouts( mut commands: Commands, - view_targets: Query<(Entity, &DepthOfField, &Msaa)>, + view_targets: Query<(Entity, &DepthOfField, &ExtractedView)>, ) { - for (view, depth_of_field, msaa) in view_targets.iter() { + for (entity, depth_of_field, view) in view_targets.iter() { // Create the bind group layout for the passes that take one input. let single_input = BindGroupLayoutDescriptor::new( "depth of field bind group layout (single input)", @@ -551,7 +549,7 @@ pub fn prepare_depth_of_field_view_bind_group_layouts( ShaderStages::FRAGMENT, ( uniform_buffer::(true), - if *msaa != Msaa::Off { + if view.msaa_samples > 1 { texture_depth_2d_multisampled() } else { texture_depth_2d() @@ -571,7 +569,7 @@ pub fn prepare_depth_of_field_view_bind_group_layouts( ShaderStages::FRAGMENT, ( uniform_buffer::(true), - if *msaa != Msaa::Off { + if view.msaa_samples > 1 { texture_depth_2d_multisampled() } else { texture_depth_2d() @@ -584,7 +582,7 @@ pub fn prepare_depth_of_field_view_bind_group_layouts( }; commands - .entity(view) + .entity(entity) .insert(ViewDepthOfFieldBindGroupLayouts { single_input, dual_input, @@ -638,9 +636,9 @@ pub fn prepare_auxiliary_depth_of_field_textures( mut commands: Commands, render_device: Res, mut texture_cache: ResMut, - mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>, + mut view_targets: Query<(Entity, &ExtractedView, &ViewTarget, &DepthOfField)>, ) { - for (entity, view_target, depth_of_field) in view_targets.iter_mut() { + for (entity, view, view_target, depth_of_field) in view_targets.iter_mut() { // An auxiliary texture is only needed for bokeh. if depth_of_field.mode != DepthOfFieldMode::Bokeh { continue; @@ -651,9 +649,9 @@ pub fn prepare_auxiliary_depth_of_field_textures( label: Some("depth of field auxiliary texture"), size: view_target.main_texture().size(), mip_level_count: 1, - sample_count: view_target.main_texture().sample_count(), + sample_count: 1, dimension: TextureDimension::D2, - format: view_target.main_texture_format(), + format: view.color_target_format, usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, view_formats: &[], }; @@ -677,12 +675,11 @@ pub fn prepare_depth_of_field_pipelines( &ExtractedView, &DepthOfField, &ViewDepthOfFieldBindGroupLayouts, - &Msaa, )>, fullscreen_shader: Res, asset_server: Res, ) { - for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() { + for (entity, view, depth_of_field, view_bind_group_layouts) in view_targets.iter() { let dof_pipeline = DepthOfFieldPipeline { view_bind_group_layouts: view_bind_group_layouts.clone(), global_bind_group_layout: global_bind_group_layout.layout.clone(), @@ -690,8 +687,7 @@ pub fn prepare_depth_of_field_pipelines( fragment_shader: load_embedded_asset!(asset_server.as_ref(), "dof.wgsl"), }; - // We'll need these two flags to create the `DepthOfFieldPipelineKey`s. - let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off); + let multisample = view.msaa_samples > 1; // Go ahead and specialize the pipelines. match depth_of_field.mode { @@ -703,7 +699,7 @@ pub fn prepare_depth_of_field_pipelines( &pipeline_cache, &dof_pipeline, DepthOfFieldPipelineKey { - hdr, + texture_format: view.color_target_format, multisample, pass: DofPass::GaussianHorizontal, }, @@ -712,7 +708,7 @@ pub fn prepare_depth_of_field_pipelines( &pipeline_cache, &dof_pipeline, DepthOfFieldPipelineKey { - hdr, + texture_format: view.color_target_format, multisample, pass: DofPass::GaussianVertical, }, @@ -728,7 +724,7 @@ pub fn prepare_depth_of_field_pipelines( &pipeline_cache, &dof_pipeline, DepthOfFieldPipelineKey { - hdr, + texture_format: view.color_target_format, multisample, pass: DofPass::BokehPass0, }, @@ -737,7 +733,7 @@ pub fn prepare_depth_of_field_pipelines( &pipeline_cache, &dof_pipeline, DepthOfFieldPipelineKey { - hdr, + texture_format: view.color_target_format, multisample, pass: DofPass::BokehPass1, }, @@ -755,11 +751,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline { // Build up our pipeline layout. let (mut layout, mut shader_defs) = (vec![], vec![]); let mut targets = vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.texture_format, blend: None, write_mask: ColorWrites::ALL, })]; diff --git a/crates/bevy_post_process/src/effect_stack/mod.rs b/crates/bevy_post_process/src/effect_stack/mod.rs index edb2a50e6731b..8e1a8f51726d8 100644 --- a/crates/bevy_post_process/src/effect_stack/mod.rs +++ b/crates/bevy_post_process/src/effect_stack/mod.rs @@ -30,7 +30,7 @@ use bevy_ecs::{ system::{lifetimeless::Read, Commands, Query, Res, ResMut}, world::World, }; -use bevy_image::{BevyDefault, Image}; +use bevy_image::Image; use bevy_render::{ diagnostic::RecordDiagnostics, extract_component::ExtractComponentPlugin, @@ -390,11 +390,7 @@ pub fn prepare_post_processing_pipelines( &pipeline_cache, &post_processing_pipeline, PostProcessingPipelineKey { - texture_format: if view.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + texture_format: view.color_target_format, }, ); diff --git a/crates/bevy_post_process/src/lib.rs b/crates/bevy_post_process/src/lib.rs index 848d93d36cb99..3fcb80bffc1af 100644 --- a/crates/bevy_post_process/src/lib.rs +++ b/crates/bevy_post_process/src/lib.rs @@ -11,11 +11,10 @@ pub mod bloom; pub mod dof; pub mod effect_stack; pub mod motion_blur; -pub mod msaa_writeback; use crate::{ bloom::BloomPlugin, dof::DepthOfFieldPlugin, effect_stack::EffectStackPlugin, - motion_blur::MotionBlurPlugin, msaa_writeback::MsaaWritebackPlugin, + motion_blur::MotionBlurPlugin, }; use bevy_app::{App, Plugin}; use bevy_shader::load_shader_library; @@ -29,7 +28,6 @@ impl Plugin for PostProcessPlugin { load_shader_library!(app, "gaussian_blur.wgsl"); app.add_plugins(( - MsaaWritebackPlugin, BloomPlugin, MotionBlurPlugin, DepthOfFieldPlugin, diff --git a/crates/bevy_post_process/src/motion_blur/node.rs b/crates/bevy_post_process/src/motion_blur/node.rs index 7bd16ed6428f4..6ae898ddc2d67 100644 --- a/crates/bevy_post_process/src/motion_blur/node.rs +++ b/crates/bevy_post_process/src/motion_blur/node.rs @@ -9,7 +9,7 @@ use bevy_render::{ RenderPassDescriptor, }, renderer::RenderContext, - view::{Msaa, ViewTarget}, + view::{ExtractedView, ViewTarget}, }; use bevy_core_pipeline::prepass::ViewPrepassTextures; @@ -24,17 +24,17 @@ pub struct MotionBlurNode; impl ViewNode for MotionBlurNode { type ViewQuery = ( + &'static ExtractedView, &'static ViewTarget, &'static MotionBlurPipelineId, &'static ViewPrepassTextures, &'static MotionBlurUniform, - &'static Msaa, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (view_target, pipeline_id, prepass_textures, motion_blur, msaa): QueryItem, + (view, view_target, pipeline_id, prepass_textures, motion_blur): QueryItem, world: &World, ) -> Result<(), NodeRunError> { if motion_blur.samples == 0 || motion_blur.shutter_angle <= 0.0 { @@ -64,7 +64,7 @@ impl ViewNode for MotionBlurNode { let post_process = view_target.post_process_write(); - let layout = if msaa.samples() == 1 { + let layout = if view.msaa_samples == 1 { &motion_blur_pipeline.layout } else { &motion_blur_pipeline.layout_msaa diff --git a/crates/bevy_post_process/src/motion_blur/pipeline.rs b/crates/bevy_post_process/src/motion_blur/pipeline.rs index de266ad470837..9306aec85ca56 100644 --- a/crates/bevy_post_process/src/motion_blur/pipeline.rs +++ b/crates/bevy_post_process/src/motion_blur/pipeline.rs @@ -7,7 +7,6 @@ use bevy_ecs::{ resource::Resource, system::{Commands, Query, Res, ResMut}, }; -use bevy_image::BevyDefault as _; use bevy_render::{ globals::GlobalsUniform, render_resource::{ @@ -21,7 +20,7 @@ use bevy_render::{ SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat, TextureSampleType, }, renderer::RenderDevice, - view::{ExtractedView, Msaa, ViewTarget}, + view::ExtractedView, }; use bevy_shader::{Shader, ShaderDefVal}; use bevy_utils::default; @@ -110,7 +109,7 @@ pub fn init_motion_blur_pipeline( #[derive(PartialEq, Eq, Hash, Clone, Copy)] pub struct MotionBlurPipelineKey { - hdr: bool, + texture_format: TextureFormat, samples: u32, } @@ -143,11 +142,7 @@ impl SpecializedRenderPipeline for MotionBlurPipeline { shader: self.fragment_shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.texture_format, blend: None, write_mask: ColorWrites::ALL, })], @@ -166,15 +161,15 @@ pub(crate) fn prepare_motion_blur_pipelines( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<(Entity, &ExtractedView, &Msaa), With>, + views: Query<(Entity, &ExtractedView), With>, ) { - for (entity, view, msaa) in &views { + for (entity, view) in &views { let pipeline_id = pipelines.specialize( &pipeline_cache, &pipeline, MotionBlurPipelineKey { - hdr: view.hdr, - samples: msaa.samples(), + texture_format: view.color_target_format, + samples: view.msaa_samples, }, ); diff --git a/crates/bevy_post_process/src/msaa_writeback.rs b/crates/bevy_post_process/src/msaa_writeback.rs deleted file mode 100644 index 23ba7050116c6..0000000000000 --- a/crates/bevy_post_process/src/msaa_writeback.rs +++ /dev/null @@ -1,166 +0,0 @@ -use bevy_app::{App, Plugin}; -use bevy_camera::MsaaWriteback; -use bevy_color::LinearRgba; -use bevy_core_pipeline::{ - blit::{BlitPipeline, BlitPipelineKey}, - core_2d::graph::{Core2d, Node2d}, - core_3d::graph::{Core3d, Node3d}, -}; -use bevy_ecs::{prelude::*, query::QueryItem}; -use bevy_render::{ - camera::ExtractedCamera, - diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, - render_resource::*, - renderer::RenderContext, - view::{Msaa, ViewTarget}, - Render, RenderApp, RenderSystems, -}; - -/// This enables "msaa writeback" support for the `core_2d` and `core_3d` pipelines, which can be enabled on cameras -/// using [`bevy_camera::Camera::msaa_writeback`]. See the docs on that field for more information. -#[derive(Default)] -pub struct MsaaWritebackPlugin; - -impl Plugin for MsaaWritebackPlugin { - fn build(&self, app: &mut App) { - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - render_app.add_systems( - Render, - prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare), - ); - { - render_app - .add_render_graph_node::>( - Core2d, - Node2d::MsaaWriteback, - ) - .add_render_graph_edge(Core2d, Node2d::MsaaWriteback, Node2d::StartMainPass); - } - { - render_app - .add_render_graph_node::>( - Core3d, - Node3d::MsaaWriteback, - ) - .add_render_graph_edge(Core3d, Node3d::MsaaWriteback, Node3d::StartMainPass); - } - } -} - -#[derive(Default)] -pub struct MsaaWritebackNode; - -impl ViewNode for MsaaWritebackNode { - type ViewQuery = ( - &'static ViewTarget, - &'static MsaaWritebackBlitPipeline, - &'static Msaa, - ); - - fn run<'w>( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (target, blit_pipeline_id, msaa): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - if *msaa == Msaa::Off { - return Ok(()); - } - - let blit_pipeline = world.resource::(); - let pipeline_cache = world.resource::(); - let Some(pipeline) = pipeline_cache.get_render_pipeline(blit_pipeline_id.0) else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - // The current "main texture" needs to be bound as an input resource, and we need the "other" - // unused target to be the "resolve target" for the MSAA write. Therefore this is the same - // as a post process write! - let post_process = target.post_process_write(); - - let pass_descriptor = RenderPassDescriptor { - label: Some("msaa_writeback"), - // The target's "resolve target" is the "destination" in post_process. - // We will indirectly write the results to the "destination" using - // the MSAA resolve step. - color_attachments: &[Some(RenderPassColorAttachment { - // If MSAA is enabled, then the sampled texture will always exist - view: target.sampled_main_texture_view().unwrap(), - depth_slice: None, - resolve_target: Some(post_process.destination), - ops: Operations { - load: LoadOp::Clear(LinearRgba::BLACK.into()), - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - multiview_mask: None, - }; - - let bind_group = blit_pipeline.create_bind_group( - render_context.render_device(), - post_process.source, - pipeline_cache, - ); - - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - let pass_span = diagnostics.pass_span(&mut render_pass, "msaa_writeback"); - - render_pass.set_pipeline(pipeline); - render_pass.set_bind_group(0, &bind_group, &[]); - render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - - Ok(()) - } -} - -#[derive(Component)] -pub struct MsaaWritebackBlitPipeline(CachedRenderPipelineId); - -fn prepare_msaa_writeback_pipelines( - mut commands: Commands, - pipeline_cache: Res, - mut pipelines: ResMut>, - blit_pipeline: Res, - view_targets: Query<(Entity, &ViewTarget, &ExtractedCamera, &Msaa)>, -) { - for (entity, view_target, camera, msaa) in view_targets.iter() { - // Determine if we should do MSAA writeback based on the camera's setting - let should_writeback = match camera.msaa_writeback { - MsaaWriteback::Off => false, - MsaaWriteback::Auto => camera.sorted_camera_index_for_target > 0, - MsaaWriteback::Always => true, - }; - - if msaa.samples() > 1 && should_writeback { - let key = BlitPipelineKey { - texture_format: view_target.main_texture_format(), - samples: msaa.samples(), - blend_state: None, - }; - - let pipeline = pipelines.specialize(&pipeline_cache, &blit_pipeline, key); - commands - .entity(entity) - .insert(MsaaWritebackBlitPipeline(pipeline)); - } else { - // This isn't strictly necessary now, but if we move to retained render entity state I don't - // want this to silently break - commands - .entity(entity) - .remove::(); - } - } -} diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 91a9a81d11409..791378f93ea70 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -227,7 +227,6 @@ //! "bevy_camera::Camera": { //! "clear_color": "Default", //! "is_active": true, -//! "msaa_writeback": true, //! "order": 0, //! "sub_camera_view": null, //! "target": { @@ -246,7 +245,6 @@ //! "bevy_camera::primitives::Frustum": {}, //! "bevy_render::sync_world::RenderEntity": 4294967291, //! "bevy_render::sync_world::SyncToRenderWorld": {}, -//! "bevy_render::view::Msaa": "Sample4", //! "bevy_camera::visibility::InheritedVisibility": true, //! "bevy_camera::visibility::ViewVisibility": false, //! "bevy_camera::visibility::Visibility": "Inherited", diff --git a/crates/bevy_render/src/camera/color_target.rs b/crates/bevy_render/src/camera/color_target.rs new file mode 100644 index 0000000000000..f53c6ece002a8 --- /dev/null +++ b/crates/bevy_render/src/camera/color_target.rs @@ -0,0 +1,312 @@ +use alloc::sync::Arc; +use bevy_asset::{AssetId, Assets, Handle}; +use bevy_camera::{ + color_target::{ + MainColorTarget, MainColorTargetInputConfig, MainColorTargetReadsFrom, + NoAutoConfiguredMainColorTarget, WithMainColorTarget, + }, + Camera, CameraMainColorTargetConfig, CameraMainColorTargetsSize, +}; +use bevy_ecs::{ + component::Component, + entity::Entity, + query::{Has, QueryItem, With, Without}, + relationship::RelationshipTarget, + system::{Commands, Local, Query, ResMut, SystemState}, +}; +use bevy_image::{BevyDefault, Image, ToExtents}; +use core::sync::atomic::AtomicUsize; +use core::sync::atomic::Ordering; +use wgpu::{TextureFormat, TextureUsages}; + +use crate::{ + extract_component::ExtractComponent, sync_world::RenderEntity, view::Hdr, Extract, MainWorld, +}; + +pub(super) fn insert_camera_required_components_if_auto_configured( + mut commands: Commands, + query: Query< + (Entity, Has), + (With, Without), + >, +) { + for (entity, has_config) in query.iter() { + let mut entity_commands = commands.entity(entity); + if !has_config { + entity_commands.insert(CameraMainColorTargetConfig::default()); + } + } +} + +pub(super) fn configure_camera_color_target( + mut commands: Commands, + mut image_assets: ResMut>, + query: Query< + ( + Entity, + &Camera, + &CameraMainColorTargetConfig, + Has, + Option<&WithMainColorTarget>, + ), + Without, + >, + mut main_color_targets: Query<&mut MainColorTarget>, +) { + for (entity, camera, config, hdr, with_main_color_target) in query.iter() { + let Some(physical_size) = camera.physical_target_size() else { + continue; + }; + let size = match config.size { + CameraMainColorTargetsSize::Factor(vec2) => { + (physical_size.as_vec2() * vec2).round().as_uvec2() + } + CameraMainColorTargetsSize::Fixed(uvec2) => uvec2, + } + .to_extents(); + let format = if let Some(format) = config.format { + format + } else if hdr { + TextureFormat::Rgba16Float + } else { + TextureFormat::bevy_default() + }; + let mut image_desc = wgpu::TextureDescriptor { + label: Some("main_texture_a"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: config.usage, + view_formats: &[], + }; + if let Some(with_main_color_target) = with_main_color_target { + let Ok(mut main_color_target) = main_color_targets.get_mut(with_main_color_target.0) + else { + continue; + }; + if config.sample_count > 1 { + // If we already configured this camera but msaa is removed in `sync_camera_color_target_config`. + // We need to re-add it. + if main_color_target.multisampled.is_none() { + image_desc.label = Some("main_texture_multisampled"); + image_desc.sample_count = config.sample_count; + image_desc.usage = TextureUsages::RENDER_ATTACHMENT; + let image_texture_multisampled = Image { + texture_descriptor: image_desc, + copy_on_resize: false, + data: None, + ..Default::default() + }; + main_color_target.multisampled = + Some(image_assets.add(image_texture_multisampled)); + } + } else { + main_color_target.multisampled = None; + } + } else { + let image_texture_a = Image { + texture_descriptor: image_desc.clone(), + copy_on_resize: false, + data: None, + ..Default::default() + }; + image_desc.label = Some("main_texture_b"); + let image_texture_b = Image { + texture_descriptor: image_desc.clone(), + copy_on_resize: false, + data: None, + ..Default::default() + }; + + let msaa_texture = if config.sample_count > 1 { + image_desc.label = Some("main_texture_multisampled"); + image_desc.sample_count = config.sample_count; + image_desc.usage = TextureUsages::RENDER_ATTACHMENT; + let image_texture_multisampled = Image { + texture_descriptor: image_desc, + copy_on_resize: false, + data: None, + ..Default::default() + }; + Some(image_assets.add(image_texture_multisampled)) + } else { + None + }; + + commands.entity(entity).insert(( + MainColorTarget::new( + image_assets.add(image_texture_a), + Some(image_assets.add(image_texture_b)), + msaa_texture, + ), + WithMainColorTarget(entity), + )); + } + } +} + +pub(super) fn sync_camera_color_target_config( + mut main_world: ResMut, + mut system_state: Local< + Option< + SystemState<( + Commands, + Query< + ( + Entity, + &Camera, + &CameraMainColorTargetConfig, + Has, + &WithMainColorTarget, + ), + Without, + >, + Query<&mut MainColorTarget>, + ResMut>, + )>, + >, + >, +) { + if system_state.is_none() { + *system_state = Some(SystemState::new(&mut main_world)); + } + let system_state = system_state.as_mut().unwrap(); + + let (mut commands, query, mut query_main_color_targets, mut image_assets) = + system_state.get_mut(&mut main_world); + + for (entity, camera, config, hdr, with_color_target) in query.iter() { + let Some(physical_size) = camera.physical_target_size() else { + continue; + }; + let size = match config.size { + CameraMainColorTargetsSize::Factor(vec2) => { + (physical_size.as_vec2() * vec2).round().as_uvec2() + } + CameraMainColorTargetsSize::Fixed(uvec2) => uvec2, + } + .to_extents(); + let Ok(mut main_textures) = query_main_color_targets.get_mut(with_color_target.0) else { + continue; + }; + let main_texture_a = main_textures.current_target(); + let main_texture_b = main_textures.other_target().unwrap(); + let format = if let Some(format) = config.format { + format + } else if hdr { + TextureFormat::Rgba16Float + } else { + TextureFormat::bevy_default() + }; + let Some(main_texture_a) = image_assets.get_mut(main_texture_a) else { + continue; + }; + main_texture_a.resize(size); + main_texture_a.texture_descriptor.usage = config.usage; + main_texture_a.texture_descriptor.format = format; + let Some(main_texture_b) = image_assets.get_mut(main_texture_b) else { + continue; + }; + main_texture_b.resize(size); + main_texture_b.texture_descriptor.usage = config.usage; + main_texture_b.texture_descriptor.format = format; + + if config.sample_count > 1 { + let Some(msaa_texture) = main_textures.multisampled.as_ref() else { + // Msaa is re-enabled after disabled. Reconfigure it. + commands.entity(entity).remove::(); + continue; + }; + let Some(msaa_texture) = image_assets.get_mut(msaa_texture) else { + continue; + }; + msaa_texture.resize(size); + msaa_texture.texture_descriptor.format = format; + msaa_texture.texture_descriptor.sample_count = config.sample_count; + } else { + main_textures.multisampled = None; + } + } + + system_state.apply(&mut main_world); +} + +#[derive(Component)] +pub struct ExtractedMainColorTarget { + pub main_a: AssetId, + pub main_b: Option>, + pub multisampled: Option>, + pub main_target_flag: Option>, +} + +impl ExtractedMainColorTarget { + pub fn current_target(&self) -> AssetId { + if let Some(main_target) = &self.main_target_flag + && main_target.load(Ordering::SeqCst) == 1 + { + self.main_b.unwrap() + } else { + self.main_a + } + } + + pub fn other_target(&self) -> Option> { + let Some(main_target) = &self.main_target_flag else { + return None; + }; + Some(if main_target.load(Ordering::SeqCst) == 1 { + self.main_a + } else { + self.main_b.unwrap() + }) + } +} + +impl ExtractComponent for MainColorTarget { + type QueryData = &'static Self; + type QueryFilter = (); + type Out = ExtractedMainColorTarget; + + fn extract_component(item: QueryItem) -> Option { + Some(Self::Out { + main_a: item.main_a.id(), + main_b: item.main_b.as_ref().map(Handle::id), + multisampled: item.multisampled.as_ref().map(Handle::id), + main_target_flag: item.main_target_flag.clone(), + }) + } +} + +#[derive(Component, Debug)] +pub struct ExtractedMainColorTargetReadsFrom(pub Vec<(AssetId, MainColorTargetInputConfig)>); + +pub(super) fn extract_main_color_target_reads_from( + mut commands: Commands, + query: Extract>>, + query_main_color_targets: Extract< + Query<(&MainColorTarget, Option<&MainColorTargetInputConfig>)>, + >, +) { + for (entity, reads_from) in query.iter() { + let mut images = reads_from + .iter() + .map(|entity| { + let (t, input_config) = query_main_color_targets.get(entity).unwrap(); + ( + t.current_target().id(), + input_config.cloned().unwrap_or(MainColorTargetInputConfig { + blend_state: None, + order: 0, + }), + ) + }) + .collect::>(); + images.sort_by(|a, b| a.1.order.cmp(&b.1.order)); + + commands + .entity(entity) + .insert(ExtractedMainColorTargetReadsFrom(images)); + } +} diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera/mod.rs similarity index 70% rename from crates/bevy_render/src/camera.rs rename to crates/bevy_render/src/camera/mod.rs index 797d489023be1..63049058225ff 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -8,7 +8,7 @@ use crate::{ sync_world::{RenderEntity, SyncToRenderWorld}, texture::{GpuImage, ManualTextureViews}, view::{ - ColorGrading, ExtractedView, ExtractedWindows, Hdr, Msaa, NoIndirectDrawing, + ColorGrading, ExtractedView, ExtractedWindows, Hdr, NoIndirectDrawing, RenderVisibleEntities, RetainedViewEntity, ViewUniformOffset, }, Extract, ExtractSchedule, Render, RenderApp, RenderSystems, @@ -17,11 +17,12 @@ use crate::{ use bevy_app::{App, Plugin, PostStartup, PostUpdate}; use bevy_asset::{AssetEvent, AssetEventSystems, AssetId, Assets}; use bevy_camera::{ + color_target::{MainColorTarget, WithMainColorTarget}, primitives::Frustum, visibility::{self, RenderLayers, VisibleEntities}, - Camera, Camera2d, Camera3d, CameraMainTextureUsages, CameraOutputMode, CameraUpdateSystems, - ClearColor, ClearColorConfig, Exposure, ManualTextureViewHandle, MsaaWriteback, - NormalizedRenderTarget, Projection, RenderTarget, RenderTargetInfo, Viewport, + Camera, Camera2d, Camera3d, CameraOutputMode, CameraUpdateSystems, ClearColor, + ClearColorConfig, Exposure, ManualTextureViewHandle, NormalizedRenderTarget, Projection, + RenderTarget, RenderTargetInfo, Viewport, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -32,7 +33,7 @@ use bevy_ecs::{ lifecycle::HookContext, message::MessageReader, prelude::With, - query::{Has, QueryItem}, + query::{Has, QueryEntityError, QueryItem}, reflect::ReflectComponent, resource::Resource, schedule::IntoScheduleConfigs, @@ -41,33 +42,57 @@ use bevy_ecs::{ }; use bevy_image::Image; use bevy_log::warn; -use bevy_math::{uvec2, vec2, Mat4, URect, UVec2, UVec4, Vec2}; -use bevy_platform::collections::{HashMap, HashSet}; +use bevy_math::{uvec2, vec2, Mat4, UVec2, UVec4, Vec2}; +use bevy_platform::collections::HashSet; use bevy_reflect::prelude::*; use bevy_transform::components::GlobalTransform; use bevy_window::{PrimaryWindow, Window, WindowCreated, WindowResized, WindowScaleFactorChanged}; use wgpu::TextureFormat; +mod color_target; +pub use color_target::*; + #[derive(Default)] pub struct CameraPlugin; impl Plugin for CameraPlugin { fn build(&self, app: &mut App) { - app.register_required_components::() - .register_required_components::() + app.register_required_components::() .register_required_components::() .register_required_components::() .add_plugins(( ExtractResourcePlugin::::default(), - ExtractComponentPlugin::::default(), + ExtractComponentPlugin::::default(), )) - .add_systems(PostStartup, camera_system.in_set(CameraUpdateSystems)) + .add_systems( + PostStartup, + ( + ( + configure_camera_color_target, + camera_system, + configure_camera_color_target, + ) + .chain() + .in_set(CameraUpdateSystems), + insert_camera_required_components_if_auto_configured + .in_set(CameraUpdateSystems), + ), + ) .add_systems( PostUpdate, - camera_system - .in_set(CameraUpdateSystems) - .before(AssetEventSystems) - .before(visibility::update_frusta), + ( + ( + configure_camera_color_target, + camera_system, + configure_camera_color_target, + ) + .chain() + .in_set(CameraUpdateSystems) + .before(AssetEventSystems) + .before(visibility::update_frusta), + insert_camera_required_components_if_auto_configured + .in_set(CameraUpdateSystems), + ), ); app.world_mut() .register_component_hooks::() @@ -76,7 +101,13 @@ impl Plugin for CameraPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .add_systems(ExtractSchedule, extract_cameras) + .add_systems( + ExtractSchedule, + ( + (sync_camera_color_target_config, extract_cameras).chain(), + extract_main_color_target_reads_from, + ), + ) .add_systems(Render, sort_cameras.in_set(RenderSystems::ManageViews)); let camera_driver_node = CameraDriverNode::new(render_app.world_mut()); let mut render_graph = render_app.world_mut().resource_mut::(); @@ -99,15 +130,6 @@ impl ExtractResource for ClearColor { source.clone() } } -impl ExtractComponent for CameraMainTextureUsages { - type QueryData = &'static Self; - type QueryFilter = (); - type Out = Self; - - fn extract_component(item: QueryItem) -> Option { - Some(*item) - } -} impl ExtractComponent for Camera2d { type QueryData = &'static Self; type QueryFilter = With; @@ -153,14 +175,16 @@ pub trait NormalizedRenderTargetExt { windows: &'a ExtractedWindows, images: &'a RenderAssets, manual_texture_views: &'a ManualTextureViews, + query_main_color_targets: &'a Query<&ExtractedMainColorTarget>, ) -> Option<&'a TextureView>; /// Retrieves the [`TextureFormat`] of this render target, if it exists. - fn get_texture_view_format<'a>( + fn get_texture_view_format( &self, - windows: &'a ExtractedWindows, - images: &'a RenderAssets, - manual_texture_views: &'a ManualTextureViews, + windows: &ExtractedWindows, + images: &RenderAssets, + manual_texture_views: &ManualTextureViews, + query_main_color_targets: &Query<&ExtractedMainColorTarget>, ) -> Option; fn get_render_target_info<'a>( @@ -168,6 +192,7 @@ pub trait NormalizedRenderTargetExt { resolutions: impl IntoIterator, images: &Assets, manual_texture_views: &ManualTextureViews, + query_main_color_targets: &Query<&MainColorTarget>, ) -> Result; // Check if this render target is contained in the given changed windows or images. @@ -175,6 +200,7 @@ pub trait NormalizedRenderTargetExt { &self, changed_window_ids: &HashSet, changed_image_handles: &HashSet<&AssetId>, + query_main_color_targets: &Query<&MainColorTarget>, ) -> bool; } @@ -184,6 +210,7 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { windows: &'a ExtractedWindows, images: &'a RenderAssets, manual_texture_views: &'a ManualTextureViews, + query_main_color_targets: &'a Query<&ExtractedMainColorTarget>, ) -> Option<&'a TextureView> { match self { NormalizedRenderTarget::Window(window_ref) => windows @@ -195,16 +222,25 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { NormalizedRenderTarget::TextureView(id) => { manual_texture_views.get(id).map(|tex| &tex.texture_view) } + NormalizedRenderTarget::MainColorTarget { render_entity, .. } => { + if let Ok(t) = query_main_color_targets.get(render_entity.unwrap()) + && let Some(image) = images.get(t.main_a) + { + return Some(&image.texture_view); + } + None + } NormalizedRenderTarget::None { .. } => None, } } /// Retrieves the texture view's [`TextureFormat`] of this render target, if it exists. - fn get_texture_view_format<'a>( + fn get_texture_view_format( &self, - windows: &'a ExtractedWindows, - images: &'a RenderAssets, - manual_texture_views: &'a ManualTextureViews, + windows: &ExtractedWindows, + images: &RenderAssets, + manual_texture_views: &ManualTextureViews, + query_main_color_targets: &Query<&ExtractedMainColorTarget>, ) -> Option { match self { NormalizedRenderTarget::Window(window_ref) => windows @@ -216,6 +252,14 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { NormalizedRenderTarget::TextureView(id) => { manual_texture_views.get(id).map(|tex| tex.view_format) } + NormalizedRenderTarget::MainColorTarget { render_entity, .. } => { + if let Ok(t) = query_main_color_targets.get(render_entity.unwrap()) + && let Some(image) = images.get(t.main_a) + { + return Some(image.view_format()); + } + None + } NormalizedRenderTarget::None { .. } => None, } } @@ -225,6 +269,7 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { resolutions: impl IntoIterator, images: &Assets, manual_texture_views: &ManualTextureViews, + query_main_color_targets: &Query<&MainColorTarget>, ) -> Result { match self { NormalizedRenderTarget::Window(window_ref) => resolutions @@ -253,6 +298,27 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { scale_factor: 1.0, }) .ok_or(MissingRenderTargetInfoError::TextureView { texture_view: *id }), + NormalizedRenderTarget::MainColorTarget { entity, .. } => { + match query_main_color_targets.get(*entity) { + Ok(t) => { + if let Some(image) = images.get(&t.main_a) { + Ok(RenderTargetInfo { + physical_size: image.size(), + scale_factor: 1.0, + }) + } else { + Err(MissingRenderTargetInfoError::MainColorTarget { + image: Some(t.main_a.id()), + query_error: None, + }) + } + } + Err(err) => Err(MissingRenderTargetInfoError::MainColorTarget { + image: None, + query_error: Some(err), + }), + } + } NormalizedRenderTarget::None { width, height } => Ok(RenderTargetInfo { physical_size: uvec2(*width, *height), scale_factor: 1.0, @@ -265,6 +331,7 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { &self, changed_window_ids: &HashSet, changed_image_handles: &HashSet<&AssetId>, + query_main_color_targets: &Query<&MainColorTarget>, ) -> bool { match self { NormalizedRenderTarget::Window(window_ref) => { @@ -274,6 +341,22 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { changed_image_handles.contains(&image_target.handle.id()) } NormalizedRenderTarget::TextureView(_) => true, + NormalizedRenderTarget::MainColorTarget { entity, .. } => { + let mut handles = smallvec::SmallVec::<[AssetId; 3]>::new(); + if let Ok(t) = query_main_color_targets.get(*entity) { + handles.push(t.main_a.id()); + if let Some(b) = &t.main_b { + handles.push(b.id()); + } + if let Some(multisampled) = &t.multisampled { + handles.push(multisampled.id()); + } + } + handles + .iter() + .any(|handle| changed_image_handles.contains(handle)) + } + NormalizedRenderTarget::None { .. } => false, } } @@ -285,6 +368,11 @@ pub enum MissingRenderTargetInfoError { Window { window: Entity }, #[error("RenderTarget::Image missing ({image:?}): Make sure the Image's usages include RenderAssetUsages::MAIN_WORLD.")] Image { image: AssetId }, + #[error("RenderTarget::MainColorTarget failed to get target info, query error: {query_error:?}, image: {image:?}")] + MainColorTarget { + query_error: Option, + image: Option>, + }, #[error("RenderTarget::TextureView missing ({texture_view:?}): make sure the texture view handle was not removed.")] TextureView { texture_view: ManualTextureViewHandle, @@ -313,6 +401,7 @@ pub fn camera_system( images: Res>, manual_texture_views: Res, mut cameras: Query<(&mut Camera, &RenderTarget, &mut Projection)>, + query_main_color_targets: Query<&MainColorTarget>, ) -> Result<(), BevyError> { let primary_window = primary_window.iter().next(); @@ -339,28 +428,53 @@ pub fn camera_system( .as_ref() .map(|viewport| viewport.physical_size); - if let Some(normalized_target) = render_target.normalize(primary_window) - && (normalized_target.is_changed(&changed_window_ids, &changed_image_handles) - || camera.is_added() + if let Some(normalized_target) = render_target.normalize(primary_window, None) + && (normalized_target.is_changed( + &changed_window_ids, + &changed_image_handles, + &query_main_color_targets, + ) || camera.is_added() || camera_projection.is_changed() || camera.computed.old_viewport_size != viewport_size || camera.computed.old_sub_camera_view != camera.sub_camera_view) { - let new_computed_target_info = normalized_target.get_render_target_info( + let new_computed_target_info = match normalized_target.get_render_target_info( windows, &images, &manual_texture_views, - )?; + &query_main_color_targets, + ) { + Ok(info) => info, + Err(err) => { + // If render target is `MainColorTarget` and query failed, we ignore this error and continue. + // Because the entity is not yet spawned by `configure_camera_color_target`, + // which runs after and depends on `camera_system` to compute physical target size first. + // TODO: Deal with this better. + if matches!( + err, + MissingRenderTargetInfoError::MainColorTarget { + query_error: Some(QueryEntityError::QueryDoesNotMatch(..)), + image: None + } + ) { + continue; + } + return Err(err.into()); + } + }; // Check for the scale factor changing, and resize the viewport if needed. // This can happen when the window is moved between monitors with different DPIs. // Without this, the viewport will take a smaller portion of the window moved to // a higher DPI monitor. - if normalized_target.is_changed(&scale_factor_changed_window_ids, &HashSet::default()) - && let Some(old_scale_factor) = camera - .computed - .target_info - .as_ref() - .map(|info| info.scale_factor) + if normalized_target.is_changed( + &scale_factor_changed_window_ids, + &HashSet::default(), + &query_main_color_targets, + ) && let Some(old_scale_factor) = camera + .computed + .target_info + .as_ref() + .map(|info| info.scale_factor) { let resize_factor = new_computed_target_info.scale_factor / old_scale_factor; if let Some(ref mut viewport) = camera.viewport { @@ -402,16 +516,14 @@ pub fn camera_system( #[derive(Component, Debug)] pub struct ExtractedCamera { - pub target: Option, - pub physical_viewport_size: Option, - pub physical_target_size: Option, + pub output_color_target: Option, + pub main_color_target: Entity, + pub main_color_target_size: UVec2, pub viewport: Option, pub render_graph: InternedRenderSubGraph, pub order: isize, pub output_mode: CameraOutputMode, - pub msaa_writeback: MsaaWriteback, pub clear_color: ClearColorConfig, - pub sorted_camera_index_for_target: usize, pub exposure: f32, pub hdr: bool, } @@ -438,8 +550,11 @@ pub fn extract_cameras( Option<&Projection>, Has, ), + &WithMainColorTarget, )>, >, + query_main_color_targets: Extract>, + images: Extract>>, primary_window: Extract>>, gpu_preprocessing_support: Res, mapper: Extract>, @@ -475,6 +590,7 @@ pub fn extract_cameras( projection, no_indirect_drawing, ), + with_main_color_target, ) in query.iter() { if !camera.is_active { @@ -484,20 +600,33 @@ pub fn extract_cameras( continue; } + let Ok((main_color_target_render_entity, main_color_target)) = + query_main_color_targets.get(with_main_color_target.0) + else { + continue; + }; + + let Some(main_texture_a) = images.get(&main_color_target.main_a) else { + continue; + }; + let color_target_format = main_texture_a + .texture_view_descriptor + .as_ref() + .and_then(|v| v.format) + .unwrap_or(main_texture_a.texture_descriptor.format); + let main_color_target_size = main_texture_a.size(); + let msaa_samples = if let Some(multisampled) = &main_color_target.multisampled { + let Some(tex) = images.get(multisampled) else { + continue; + }; + tex.texture_descriptor.sample_count + } else { + 1 + }; + let color_grading = color_grading.unwrap_or(&ColorGrading::default()).clone(); - if let ( - Some(URect { - min: viewport_origin, - .. - }), - Some(viewport_size), - Some(target_size), - ) = ( - camera.physical_viewport_rect(), - camera.physical_viewport_size(), - camera.physical_target_size(), - ) { + if let Some(target_size) = camera.physical_target_size() { if target_size.x == 0 || target_size.y == 0 { commands .entity(render_entity) @@ -526,20 +655,30 @@ pub fn extract_cameras( .collect(), }; + let render_target_color_target_render_entity = + if let RenderTarget::MainColorTarget(entity) = render_target { + query_main_color_targets + .get(*entity) + .ok() + .map(|(render_entity, _)| render_entity) + } else { + None + }; + let output_color_target = + render_target.normalize(primary_window, render_target_color_target_render_entity); + let mut commands = commands.entity(render_entity); + commands.insert(( ExtractedCamera { - target: render_target.normalize(primary_window), + output_color_target, + main_color_target: main_color_target_render_entity, + main_color_target_size, viewport: camera.viewport.clone(), - physical_viewport_size: Some(viewport_size), - physical_target_size: Some(target_size), render_graph: camera_render_graph.0, order: camera.order, output_mode: camera.output_mode, - msaa_writeback: camera.msaa_writeback, clear_color: camera.clear_color, - // this will be set in sort_cameras - sorted_camera_index_for_target: 0, exposure: exposure .map(Exposure::exposure) .unwrap_or_else(|| Exposure::default().exposure()), @@ -550,15 +689,12 @@ pub fn extract_cameras( clip_from_view: camera.clip_from_view(), world_from_view: *transform, clip_from_world: None, - hdr, - viewport: UVec4::new( - viewport_origin.x, - viewport_origin.y, - viewport_size.x, - viewport_size.y, - ), + viewport: UVec4::new(0, 0, main_color_target_size.x, main_color_target_size.y), color_grading, invert_culling: camera.invert_culling, + hdr, + color_target_format, + msaa_samples, }, render_visible_entities, *frustum, @@ -609,46 +745,31 @@ pub struct SortedCameras(pub Vec); pub struct SortedCamera { pub entity: Entity, pub order: isize, - pub target: Option, - pub hdr: bool, } pub fn sort_cameras( mut sorted_cameras: ResMut, - mut cameras: Query<(Entity, &mut ExtractedCamera)>, + cameras: Query<(Entity, &ExtractedCamera)>, ) { sorted_cameras.0.clear(); for (entity, camera) in cameras.iter() { sorted_cameras.0.push(SortedCamera { entity, order: camera.order, - target: camera.target.clone(), - hdr: camera.hdr, }); } - // sort by order and ensure within an order, RenderTargets of the same type are packed together - sorted_cameras - .0 - .sort_by(|c1, c2| (c1.order, &c1.target).cmp(&(c2.order, &c2.target))); - let mut previous_order_target = None; + // sort by order and ensure within an order. + sorted_cameras.0.sort_by(|c1, c2| c1.order.cmp(&c2.order)); + let mut previous_order = None; let mut ambiguities = >::default(); - let mut target_counts = >::default(); for sorted_camera in &mut sorted_cameras.0 { - let new_order_target = (sorted_camera.order, sorted_camera.target.clone()); - if let Some(previous_order_target) = previous_order_target - && previous_order_target == new_order_target + let new_order = sorted_camera.order; + if let Some(previous_order) = previous_order + && previous_order == new_order { - ambiguities.insert(new_order_target.clone()); - } - if let Some(target) = &sorted_camera.target { - let count = target_counts - .entry((target.clone(), sorted_camera.hdr)) - .or_insert(0usize); - let (_, mut camera) = cameras.get_mut(sorted_camera.entity).unwrap(); - camera.sorted_camera_index_for_target = *count; - *count += 1; + ambiguities.insert(new_order); } - previous_order_target = Some(new_order_target); + previous_order = Some(new_order); } if !ambiguities.is_empty() { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index b447125c36d09..dcbcb189bb686 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -68,8 +68,7 @@ pub mod view; pub mod prelude { #[doc(hidden)] pub use crate::{ - camera::NormalizedRenderTargetExt as _, texture::ManualTextureViews, view::Msaa, - ExtractSchedule, + camera::NormalizedRenderTargetExt as _, texture::ManualTextureViews, ExtractSchedule, }; } diff --git a/crates/bevy_render/src/render_graph/camera_driver_node.rs b/crates/bevy_render/src/render_graph/camera_driver_node.rs index 837f8db87f0ad..892e3ce10fa93 100644 --- a/crates/bevy_render/src/render_graph/camera_driver_node.rs +++ b/crates/bevy_render/src/render_graph/camera_driver_node.rs @@ -40,7 +40,7 @@ impl Node for CameraDriverNode { }; let mut run_graph = true; - if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.target { + if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.output_color_target { let window_entity = window_ref.entity(); if windows .windows diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index f17d815e87dee..2d09a2958c2b8 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -87,7 +87,7 @@ pub fn render_system( for window in windows.values_mut() { let view_needs_present = views.iter().any(|(view_target, camera)| { matches!( - camera.target, + camera.output_color_target, Some(NormalizedRenderTarget::Window(w)) if w.entity() == window.entity ) && view_target.needs_present() }); diff --git a/crates/bevy_render/src/texture/gpu_image.rs b/crates/bevy_render/src/texture/gpu_image.rs index fc9a949cd1f91..73524c10a9ec5 100644 --- a/crates/bevy_render/src/texture/gpu_image.rs +++ b/crates/bevy_render/src/texture/gpu_image.rs @@ -69,10 +69,11 @@ impl RenderAsset for GpuImage { let had_data = image.data.is_some(); let texture = if let Some(prev) = previous_asset && prev.texture_descriptor == image.texture_descriptor - && prev - .texture_descriptor - .usage - .contains(TextureUsages::COPY_DST) + && (!had_data + || prev + .texture_descriptor + .usage + .contains(TextureUsages::COPY_DST)) && let Some(block_bytes) = image.texture_descriptor.format.block_copy_size(None) { if let Some(ref data) = image.data { diff --git a/crates/bevy_render/src/texture/texture_attachment.rs b/crates/bevy_render/src/texture/texture_attachment.rs index 3e4fbb83327d9..2d164b831e796 100644 --- a/crates/bevy_render/src/texture/texture_attachment.rs +++ b/crates/bevy_render/src/texture/texture_attachment.rs @@ -11,7 +11,7 @@ use wgpu::{ #[derive(Clone)] pub struct ColorAttachment { pub texture: CachedTexture, - pub resolve_target: Option, + pub multisampled: Option, pub previous_frame_texture: Option, clear_color: Option, is_first_call: Arc, @@ -20,13 +20,13 @@ pub struct ColorAttachment { impl ColorAttachment { pub fn new( texture: CachedTexture, - resolve_target: Option, + multisampled: Option, previous_frame_texture: Option, clear_color: Option, ) -> Self { Self { texture, - resolve_target, + multisampled, previous_frame_texture, clear_color, is_first_call: Arc::new(AtomicBool::new(true)), @@ -38,11 +38,11 @@ impl ColorAttachment { /// /// The returned attachment will always have writing enabled (`store: StoreOp::Load`). pub fn get_attachment(&self) -> RenderPassColorAttachment<'_> { - if let Some(resolve_target) = self.resolve_target.as_ref() { + if let Some(multisampled) = self.multisampled.as_ref() { let first_call = self.is_first_call.fetch_and(false, Ordering::SeqCst); RenderPassColorAttachment { - view: &resolve_target.default_view, + view: &multisampled.default_view, depth_slice: None, resolve_target: Some(&self.texture.default_view), ops: Operations { diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 7a14f86e49b9d..8ec77db323454 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -2,15 +2,18 @@ pub mod visibility; pub mod window; use bevy_camera::{ - primitives::Frustum, CameraMainTextureUsages, ClearColor, ClearColorConfig, Exposure, - MainPassResolutionOverride, NormalizedRenderTarget, + primitives::Frustum, ClearColor, ClearColorConfig, Exposure, MainPassResolutionOverride, + NormalizedRenderTarget, }; use bevy_diagnostic::FrameCount; pub use visibility::*; pub use window::*; use crate::{ - camera::{ExtractedCamera, MipBias, NormalizedRenderTargetExt as _, TemporalJitter}, + camera::{ + ExtractedCamera, ExtractedMainColorTarget, MipBias, NormalizedRenderTargetExt as _, + TemporalJitter, + }, extract_component::ExtractComponentPlugin, occlusion_culling::OcclusionCulling, render_asset::RenderAssets, @@ -20,7 +23,7 @@ use crate::{ sync_world::MainEntity, texture::{ CachedTexture, ColorAttachment, DepthAttachment, GpuImage, ManualTextureViews, - OutputColorAttachment, TextureCache, + OutputColorAttachment, }, Render, RenderApp, RenderSystems, }; @@ -29,7 +32,6 @@ use bevy_app::{App, Plugin}; use bevy_color::LinearRgba; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; -use bevy_image::{BevyDefault as _, ToExtents}; use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles}; use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -42,7 +44,7 @@ use core::{ }; use wgpu::{ BufferUsages, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp, - TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + TextureFormat, }; /// The matrix that converts from the RGB to the LMS color space. @@ -104,7 +106,6 @@ impl Plugin for ViewPlugin { // NOTE: windows.is_changed() handles cases where a window was resized .add_plugins(( ExtractComponentPlugin::::default(), - ExtractComponentPlugin::::default(), ExtractComponentPlugin::::default(), RenderVisibilityRangePlugin, )); @@ -128,7 +129,7 @@ impl Plugin for ViewPlugin { .in_set(RenderSystems::ManageViews) .after(prepare_windows) .after(crate::render_asset::prepare_assets::) - .ambiguous_with(crate::camera::sort_cameras), // doesn't use `sorted_camera_index_for_target` + .ambiguous_with(crate::camera::sort_cameras), prepare_view_uniforms.in_set(RenderSystems::PrepareResources), ), ); @@ -139,54 +140,7 @@ impl Plugin for ViewPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .init_resource::(); - } - } -} - -/// Component for configuring the number of samples for [Multi-Sample Anti-Aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing) -/// for a [`Camera`](bevy_camera::Camera). -/// -/// Defaults to 4 samples. A higher number of samples results in smoother edges. -/// -/// Some advanced rendering features may require that MSAA is disabled. -/// -/// Note that the web currently only supports 1 or 4 samples. -#[derive( - Component, - Default, - Clone, - Copy, - ExtractComponent, - Reflect, - PartialEq, - PartialOrd, - Eq, - Hash, - Debug, -)] -#[reflect(Component, Default, PartialEq, Hash, Debug)] -pub enum Msaa { - Off = 1, - Sample2 = 2, - #[default] - Sample4 = 4, - Sample8 = 8, -} - -impl Msaa { - #[inline] - pub fn samples(&self) -> u32 { - *self as u32 - } - - pub fn from_samples(samples: u32) -> Self { - match samples { - 1 => Msaa::Off, - 2 => Msaa::Sample2, - 4 => Msaa::Sample4, - 8 => Msaa::Sample8, - _ => panic!("Unsupported MSAA sample count: {samples}"), + .init_resource::(); } } } @@ -315,6 +269,8 @@ pub struct ExtractedView { /// /// This setting doesn't affect materials that disable backface culling. pub invert_culling: bool, + pub color_target_format: TextureFormat, + pub msaa_samples: u32, } impl ExtractedView { @@ -616,11 +572,11 @@ pub struct ViewUniformOffset { #[derive(Component, Clone)] pub struct ViewTarget { - main_textures: MainTargetTextures, - main_texture_format: TextureFormat, + main_texture_a: ColorAttachment, + main_texture_b: Option, /// 0 represents `main_textures.a`, 1 represents `main_textures.b` /// This is shared across view targets with the same render target - main_texture: Arc, + main_texture_flag: Option>, out_texture: OutputColorAttachment, } @@ -629,7 +585,7 @@ pub struct ViewTarget { /// the default output color attachment for a specific target can do so by adding a /// [`OutputColorAttachment`] to this resource before [`prepare_view_targets`] is called. #[derive(Resource, Default, Deref, DerefMut)] -pub struct ViewTargetAttachments(HashMap); +pub struct ViewOutputTargetAttachments(HashMap); pub struct PostProcessWrite<'a> { pub source: &'a TextureView, @@ -735,32 +691,51 @@ impl From for ColorGradingUniform { pub struct NoIndirectDrawing; 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() + if let Some(b) = &self.main_texture_b + && self + .main_texture_flag + .as_ref() + .unwrap() + .load(Ordering::SeqCst) + == 1 + { + b.get_attachment() } else { - self.main_textures.b.get_attachment() + self.main_texture_a.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() + if let Some(b) = &self.main_texture_b + && self + .main_texture_flag + .as_ref() + .unwrap() + .load(Ordering::SeqCst) + == 1 + { + b.get_unsampled_attachment() } else { - self.main_textures.b.get_unsampled_attachment() + self.main_texture_a.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 + if let Some(b) = &self.main_texture_b + && self + .main_texture_flag + .as_ref() + .unwrap() + .load(Ordering::SeqCst) + == 1 + { + &b.texture.texture } else { - &self.main_textures.b.texture.texture + &self.main_texture_a.texture.texture } } @@ -771,19 +746,35 @@ impl ViewTarget { /// 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 + let Some(b) = &self.main_texture_b else { + panic!() + }; + if self + .main_texture_flag + .as_ref() + .unwrap() + .load(Ordering::SeqCst) + == 1 + { + &self.main_texture_a.texture.texture } else { - &self.main_textures.a.texture.texture + &b.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 + if let Some(b) = &self.main_texture_b + && self + .main_texture_flag + .as_ref() + .unwrap() + .load(Ordering::SeqCst) + == 1 + { + &b.texture.default_view } else { - &self.main_textures.b.texture.default_view + &self.main_texture_a.texture.default_view } } @@ -794,42 +785,38 @@ impl ViewTarget { /// 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 + let Some(b) = &self.main_texture_b else { + panic!("Main color target is not double buffered thus can't be used for post process") + }; + if self + .main_texture_flag + .as_ref() + .unwrap() + .load(Ordering::SeqCst) + == 0 + { + &b.texture.default_view } else { - &self.main_textures.a.texture.default_view + &self.main_texture_a.texture.default_view } } /// The "main" sampled texture. pub fn sampled_main_texture(&self) -> Option<&Texture> { - self.main_textures - .a - .resolve_target + self.main_texture_a + .multisampled .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 + self.main_texture_a + .multisampled .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 { @@ -862,23 +849,30 @@ impl ViewTarget { /// _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); + let Some(main_texture_b) = &self.main_texture_b else { + panic!("Main color target is not double buffered thus can't be used for post process") + }; + let old_is_a_main_texture = self + .main_texture_flag + .as_ref() + .unwrap() + .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(); + main_texture_b.mark_as_cleared(); PostProcessWrite { - source: &self.main_textures.a.texture.default_view, - source_texture: &self.main_textures.a.texture.texture, - destination: &self.main_textures.b.texture.default_view, - destination_texture: &self.main_textures.b.texture.texture, + source: &self.main_texture_a.texture.default_view, + source_texture: &self.main_texture_a.texture.texture, + destination: &main_texture_b.texture.default_view, + destination_texture: &main_texture_b.texture.texture, } } else { - self.main_textures.a.mark_as_cleared(); + self.main_texture_a.mark_as_cleared(); PostProcessWrite { - source: &self.main_textures.b.texture.default_view, - source_texture: &self.main_textures.b.texture.texture, - destination: &self.main_textures.a.texture.default_view, - destination_texture: &self.main_textures.a.texture.texture, + source: &main_texture_b.texture.default_view, + source_texture: &main_texture_b.texture.texture, + destination: &self.main_texture_a.texture.default_view, + destination_texture: &self.main_texture_a.texture.texture, } } } @@ -999,25 +993,17 @@ pub fn prepare_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, -} - /// Prepares the view target [`OutputColorAttachment`] for each view in the current frame. pub fn prepare_view_attachments( windows: Res, images: Res>, manual_texture_views: Res, cameras: Query<&ExtractedCamera>, - mut view_target_attachments: ResMut, + mut view_target_attachments: ResMut, + query_main_color_targets: Query<&ExtractedMainColorTarget>, ) { for camera in cameras.iter() { - let Some(target) = &camera.target else { + let Some(target) = &camera.output_color_target else { continue; }; @@ -1025,9 +1011,19 @@ pub fn prepare_view_attachments( Entry::Occupied(_) => {} Entry::Vacant(entry) => { let Some(attachment) = target - .get_texture_view(&windows, &images, &manual_texture_views) + .get_texture_view( + &windows, + &images, + &manual_texture_views, + &query_main_color_targets, + ) .cloned() - .zip(target.get_texture_view_format(&windows, &images, &manual_texture_views)) + .zip(target.get_texture_view_format( + &windows, + &images, + &manual_texture_views, + &query_main_color_targets, + )) .map(|(view, format)| OutputColorAttachment::new(view.clone(), format)) else { continue; @@ -1039,7 +1035,7 @@ pub fn prepare_view_attachments( } /// Clears the view target [`OutputColorAttachment`]s. -pub fn clear_view_attachments(mut view_target_attachments: ResMut) { +pub fn clear_view_attachments(mut view_target_attachments: ResMut) { view_target_attachments.clear(); } @@ -1049,7 +1045,7 @@ pub fn cleanup_view_targets_for_resize( cameras: Query<(Entity, &ExtractedCamera), With>, ) { for (entity, camera) in &cameras { - if let Some(NormalizedRenderTarget::Window(window_ref)) = &camera.target + if let Some(NormalizedRenderTarget::Window(window_ref)) = &camera.output_color_target && let Some(window) = windows.get(&window_ref.entity()) && (window.size_changed || window.present_mode_changed) { @@ -1060,39 +1056,73 @@ pub fn cleanup_view_targets_for_resize( pub fn prepare_view_targets( mut commands: Commands, + cameras: Query<(Entity, &ExtractedCamera, &ExtractedView)>, + main_color_targets: Query<&ExtractedMainColorTarget>, clear_color_global: Res, - render_device: Res, - mut texture_cache: ResMut, - cameras: Query<( - Entity, - &ExtractedCamera, - &ExtractedView, - &CameraMainTextureUsages, - &Msaa, - )>, - view_target_attachments: Res, + images: Res>, + view_output_target_attachments: Res, ) { - let mut textures = >::default(); - for (entity, camera, view, texture_usage, msaa) in cameras.iter() { - let (Some(target_size), Some(out_attachment)) = ( - camera.physical_target_size, + for (entity, camera, view) in cameras.iter() { + let (Some(out_attachment), Ok(main_color_target)) = ( camera - .target + .output_color_target .as_ref() - .and_then(|target| view_target_attachments.get(target)), + .and_then(|target| view_output_target_attachments.get(target)), + main_color_targets.get(camera.main_color_target), ) else { // If we can't find an output attachment we need to remove the ViewTarget // component to make sure the camera doesn't try rendering to an invalid // output attachment. commands.entity(entity).try_remove::(); - continue; }; - let main_texture_format = if view.hdr { - ViewTarget::TEXTURE_FORMAT_HDR + let Some(main_texture_a) = images.get(main_color_target.main_a) else { + commands.entity(entity).try_remove::(); + continue; + }; + if main_texture_a.view_format() != view.color_target_format { + commands.entity(entity).try_remove::(); + continue; + } + if main_texture_a.size_2d() != camera.main_color_target_size { + commands.entity(entity).try_remove::(); + continue; + } + let main_texture_a = CachedTexture { + texture: main_texture_a.texture.clone(), + default_view: main_texture_a.texture_view.clone(), + }; + let main_texture_b = if let Some(main_b) = &main_color_target.main_b { + let Some(tex) = images.get(*main_b) else { + commands.entity(entity).try_remove::(); + continue; + }; + Some(CachedTexture { + texture: tex.texture.clone(), + default_view: tex.texture_view.clone(), + }) + } else { + None + }; + let main_texture_multisampled = if let Some(multisampled) = &main_color_target.multisampled + && view.msaa_samples > 1 + { + let Some(tex) = images.get(*multisampled) else { + commands.entity(entity).try_remove::(); + continue; + }; + + if tex.texture.sample_count() != view.msaa_samples { + commands.entity(entity).try_remove::(); + continue; + } + Some(CachedTexture { + texture: tex.texture.clone(), + default_view: tex.texture_view.clone(), + }) } else { - TextureFormat::bevy_default() + None }; let clear_color = match camera.clear_color { @@ -1101,71 +1131,23 @@ pub fn prepare_view_targets( _ => Some(clear_color_global.0), }; - let (a, b, sampled, main_texture) = textures - .entry((camera.target.clone(), texture_usage.0, view.hdr, msaa)) - .or_insert_with(|| { - let descriptor = TextureDescriptor { - label: None, - size: target_size.to_extents(), - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: main_texture_format, - usage: texture_usage.0, - 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: target_size.to_extents(), - 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 - }; - let main_texture = Arc::new(AtomicUsize::new(0)); - (a, b, sampled, main_texture) - }); - let converted_clear_color = clear_color.map(Into::into); - - let main_textures = MainTargetTextures { - a: ColorAttachment::new(a.clone(), sampled.clone(), None, converted_clear_color), - b: ColorAttachment::new(b.clone(), sampled.clone(), None, converted_clear_color), - main_texture: main_texture.clone(), - }; - commands.entity(entity).insert(ViewTarget { - main_texture: main_textures.main_texture.clone(), - main_textures, - main_texture_format, + main_texture_flag: main_color_target.main_target_flag.clone(), + main_texture_a: ColorAttachment::new( + main_texture_a, + main_texture_multisampled.clone(), + None, + converted_clear_color, + ), + main_texture_b: main_texture_b.map(|main_texture_b| { + ColorAttachment::new( + main_texture_b, + main_texture_multisampled, + None, + converted_clear_color, + ) + }), out_texture: out_attachment.clone(), }); } diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 143c8b6245ed4..ca3c06e2147a5 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -1,5 +1,6 @@ use super::ExtractedWindows; use crate::{ + camera::ExtractedMainColorTarget, gpu_readback, render_asset::RenderAssets, render_resource::{ @@ -7,8 +8,11 @@ use crate::{ SpecializedRenderPipeline, SpecializedRenderPipelines, Texture, TextureUsages, TextureView, }, renderer::RenderDevice, + sync_world::RenderEntity, texture::{GpuImage, ManualTextureViews, OutputColorAttachment}, - view::{prepare_view_attachments, prepare_view_targets, ViewTargetAttachments, WindowSurfaces}, + view::{ + prepare_view_attachments, prepare_view_targets, ViewOutputTargetAttachments, WindowSurfaces, + }, ExtractSchedule, MainWorld, Render, RenderApp, RenderStartup, RenderSystems, }; use alloc::{borrow::Cow, sync::Arc}; @@ -220,6 +224,7 @@ fn extract_screenshots( Commands, Query>, Query<(Entity, &Screenshot), Without>, + Query>, )>, >, >, @@ -229,7 +234,8 @@ fn extract_screenshots( *system_state = Some(SystemState::new(&mut main_world)); } let system_state = system_state.as_mut().unwrap(); - let (mut commands, primary_window, screenshots) = system_state.get_mut(&mut main_world); + let (mut commands, primary_window, screenshots, query_main_color_targets) = + system_state.get_mut(&mut main_world); targets.clear(); seen_targets.clear(); @@ -238,7 +244,13 @@ fn extract_screenshots( for (entity, screenshot) in screenshots.iter() { let render_target = screenshot.0.clone(); - let Some(render_target) = render_target.normalize(primary_window) else { + let main_color_target_render_entity = match render_target { + RenderTarget::MainColorTarget(entity) => query_main_color_targets.get(entity).ok(), + _ => None, + }; + let Some(render_target) = + render_target.normalize(primary_window, main_color_target_render_entity) + else { warn!( "Unknown render target for screenshot, skipping: {:?}", render_target @@ -272,7 +284,8 @@ fn prepare_screenshots( mut pipelines: ResMut>, images: Res>, manual_texture_views: Res, - mut view_target_attachments: ResMut, + mut view_target_attachments: ResMut, + query_main_color_targets: Query<&ExtractedMainColorTarget>, ) { prepared.clear(); for (entity, target) in targets.iter() { @@ -349,6 +362,41 @@ fn prepare_screenshots( OutputColorAttachment::new(texture_view.clone(), view_format), ); } + NormalizedRenderTarget::MainColorTarget { render_entity, .. } => { + let Some(render_entity) = *render_entity else { + warn!("Unknown main color target for screenshot whose render entity is None, skipping"); + continue; + }; + let Ok(main_color_target) = query_main_color_targets.get(render_entity) else { + warn!( + "Unknown main color target for screenshot, skipping: {:?}", + render_entity + ); + continue; + }; + let image_id = main_color_target.current_target(); + let Some(gpu_image) = images.get(image_id) else { + warn!( + "Unknown image of main color target for screenshot, skipping: {:?}", + image_id + ); + continue; + }; + let view_format = gpu_image.view_format(); + let (texture_view, state) = prepare_screenshot_state( + gpu_image.texture_descriptor.size, + view_format, + &render_device, + &screenshot_pipeline, + &pipeline_cache, + &mut pipelines, + ); + prepared.insert(*entity, state); + view_target_attachments.insert( + target.clone(), + OutputColorAttachment::new(texture_view.clone(), view_format), + ); + } NormalizedRenderTarget::None { .. } => { // Nothing to screenshot! } @@ -569,6 +617,45 @@ pub(crate) fn submit_screenshot_commands(world: &World, encoder: &mut CommandEnc texture_view, ); } + NormalizedRenderTarget::MainColorTarget { render_entity, .. } => { + let Some(render_entity) = *render_entity else { + warn!("Unknown main color target for screenshot whose render entity is None, skipping"); + continue; + }; + let Some(main_color_target) = world + .entity(render_entity) + .get::() + else { + warn!( + "Unknown main color target for screenshot, skipping: {:?}", + render_entity + ); + continue; + }; + let image_id = main_color_target.current_target(); + let Some(gpu_image) = gpu_images.get(image_id) else { + warn!( + "Unknown image of main color target for screenshot, skipping: {:?}", + image_id + ); + continue; + }; + + let width = gpu_image.texture_descriptor.size.width; + let height = gpu_image.texture_descriptor.size.height; + let texture_format = gpu_image.texture_descriptor.format; + let texture_view = gpu_image.texture_view.deref(); + render_screenshot( + encoder, + prepared, + pipelines, + entity, + width, + height, + texture_format, + texture_view, + ); + } NormalizedRenderTarget::None { .. } => { // Nothing to screenshot! } diff --git a/crates/bevy_solari/src/pathtracer/node.rs b/crates/bevy_solari/src/pathtracer/node.rs index 1c5ec8e72092b..df3083ab06c15 100644 --- a/crates/bevy_solari/src/pathtracer/node.rs +++ b/crates/bevy_solari/src/pathtracer/node.rs @@ -52,10 +52,9 @@ impl ViewNode for PathtracerNode { let pipeline_cache = world.resource::(); let scene_bindings = world.resource::(); let view_uniforms = world.resource::(); - let (Some(pipeline), Some(scene_bindings), Some(viewport), Some(view_uniforms)) = ( + let (Some(pipeline), Some(scene_bindings), Some(view_uniforms)) = ( pipeline_cache.get_compute_pipeline(self.pipeline), &scene_bindings.bind_group, - camera.physical_viewport_size, view_uniforms.uniforms.binding(), ) else { return Ok(()); @@ -87,7 +86,11 @@ impl ViewNode for PathtracerNode { pass.set_pipeline(pipeline); pass.set_bind_group(0, scene_bindings, &[]); pass.set_bind_group(1, &bind_group, &[view_uniform_offset.offset]); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + pass.dispatch_workgroups( + camera.main_color_target_size.x.div_ceil(8), + camera.main_color_target_size.y.div_ceil(8), + 1, + ); Ok(()) } @@ -104,10 +107,7 @@ impl FromWorld for PathtracerNode { ShaderStages::COMPUTE, ( texture_storage_2d(TextureFormat::Rgba32Float, StorageTextureAccess::ReadWrite), - texture_storage_2d( - ViewTarget::TEXTURE_FORMAT_HDR, - StorageTextureAccess::WriteOnly, - ), + texture_storage_2d(TextureFormat::Rgba16Float, StorageTextureAccess::WriteOnly), uniform_buffer::(true), ), ), diff --git a/crates/bevy_solari/src/pathtracer/prepare.rs b/crates/bevy_solari/src/pathtracer/prepare.rs index ddef965222b5a..1d6403824d100 100644 --- a/crates/bevy_solari/src/pathtracer/prepare.rs +++ b/crates/bevy_solari/src/pathtracer/prepare.rs @@ -23,9 +23,7 @@ pub fn prepare_pathtracer_accumulation_texture( mut commands: Commands, ) { for (entity, camera) in &query { - let Some(viewport) = camera.physical_viewport_size else { - continue; - }; + let viewport = camera.main_color_target_size; let descriptor = TextureDescriptor { label: Some("pathtracer_accumulation_texture"), diff --git a/crates/bevy_solari/src/realtime/mod.rs b/crates/bevy_solari/src/realtime/mod.rs index 36cf54d04b009..f7e310a303709 100644 --- a/crates/bevy_solari/src/realtime/mod.rs +++ b/crates/bevy_solari/src/realtime/mod.rs @@ -86,8 +86,7 @@ impl Plugin for SolariLightingPlugin { /// A component for a 3d camera entity to enable the Solari raytraced lighting system. /// -/// Must be used with `CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING)`, and -/// `Msaa::Off`. +/// The main color target must be used with `CameraMainColorTargetConfig::default().with_msaa_off().with_usage(TextureUsages::STORAGE_BINDING)`. #[derive(Component, Reflect, Clone)] #[reflect(Component, Default, Clone)] #[require( diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index c984281c585ed..62b79546489d7 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -403,10 +403,7 @@ impl FromWorld for SolariLightingNode { &BindGroupLayoutEntries::sequential( ShaderStages::COMPUTE, ( - texture_storage_2d( - ViewTarget::TEXTURE_FORMAT_HDR, - StorageTextureAccess::ReadWrite, - ), + texture_storage_2d(TextureFormat::Rgba16Float, StorageTextureAccess::ReadWrite), storage_buffer_sized(false, None), storage_buffer_sized(false, None), texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), diff --git a/crates/bevy_solari/src/realtime/prepare.rs b/crates/bevy_solari/src/realtime/prepare.rs index 865c8c8fba807..573f6ebc33ec4 100644 --- a/crates/bevy_solari/src/realtime/prepare.rs +++ b/crates/bevy_solari/src/realtime/prepare.rs @@ -93,9 +93,8 @@ pub fn prepare_solari_lighting_resources( let (entity, camera, solari_lighting_resources, resolution_override, has_dlss_rr) = query_item; - let Some(mut view_size) = camera.physical_viewport_size else { - continue; - }; + let mut view_size = camera.main_color_target_size; + if let Some(MainPassResolutionOverride(resolution_override)) = resolution_override { view_size = *resolution_override; } diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 4a0a9d354e084..5b689391aedfb 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -160,7 +160,7 @@ fn sprite_picking( })?; if render_target - .normalize(primary_window) + .normalize(primary_window,None) .is_none_or(|x| x != location.target) { return None; diff --git a/crates/bevy_sprite_render/src/mesh2d/mesh.rs b/crates/bevy_sprite_render/src/mesh2d/mesh.rs index a1f03b85acfa1..c38f3f9f116dc 100644 --- a/crates/bevy_sprite_render/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite_render/src/mesh2d/mesh.rs @@ -20,10 +20,8 @@ use bevy_ecs::{ query::ROQueryItem, system::{lifetimeless::*, SystemParamItem}, }; -use bevy_image::BevyDefault; use bevy_math::{Affine3, Vec4}; use bevy_mesh::{Mesh, Mesh2d, MeshTag, MeshVertexBufferLayoutRef}; -use bevy_render::prelude::Msaa; use bevy_render::RenderSystems::PrepareAssets; use bevy_render::{ batching::{ @@ -45,7 +43,7 @@ use bevy_render::{ renderer::RenderDevice, sync_world::{MainEntity, MainEntityHashMap}, texture::{FallbackImage, GpuImage}, - view::{ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, + view::{ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_transform::components::GlobalTransform; @@ -125,15 +123,14 @@ pub fn check_views_need_specialization( views: Query<( &MainEntity, &ExtractedView, - &Msaa, Option<&Tonemapping>, Option<&DebandDither>, )>, ticks: SystemChangeTick, ) { - for (view_entity, view, msaa, tonemapping, dither) in &views { - let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) - | Mesh2dPipelineKey::from_hdr(view.hdr); + for (view_entity, view, tonemapping, dither) in &views { + let mut view_key = Mesh2dPipelineKey::from_msaa_samples(view.msaa_samples) + | Mesh2dPipelineKey::from_color_target_format(view.color_target_format); if !view.hdr { if let Some(tonemapping) = tonemapping { @@ -440,6 +437,18 @@ bitflags::bitflags! { const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RESERVED_BITS = Self::COLOR_TARGET_FORMAT_MASK_BITS << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_R8UNORM = 0 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RG8UNORM = 1 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA8UNORM = 2 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA8UNORMSRGB = 3 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_BGRA8UNORM = 4 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_BGRA8UNORMSRGB = 5 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_R16FLOAT = 6 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RG16FLOAT = 7 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA16FLOAT = 8 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RB11B10FLOAT = 9 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGB10A2UNORM = 10 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; } } @@ -451,6 +460,9 @@ impl Mesh2dPipelineKey { const TONEMAP_METHOD_MASK_BITS: u32 = 0b111; const TONEMAP_METHOD_SHIFT_BITS: u32 = Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones(); + const COLOR_TARGET_FORMAT_MASK_BITS: u32 = 0b1111; + const COLOR_TARGET_FORMAT_SHIFT_BITS: u32 = + Self::TONEMAP_METHOD_SHIFT_BITS - Self::COLOR_TARGET_FORMAT_MASK_BITS.count_ones(); pub fn from_msaa_samples(msaa_samples: u32) -> Self { let msaa_bits = @@ -458,14 +470,6 @@ impl Mesh2dPipelineKey { Self::from_bits_retain(msaa_bits) } - pub fn from_hdr(hdr: bool) -> Self { - if hdr { - Mesh2dPipelineKey::HDR - } else { - Mesh2dPipelineKey::NONE - } - } - pub fn msaa_samples(&self) -> u32 { 1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } @@ -489,6 +493,57 @@ impl Mesh2dPipelineKey { _ => PrimitiveTopology::default(), } } + + /// Create a pipeline key from view target format. + #[inline] + pub fn from_color_target_format(format: TextureFormat) -> Self { + match format { + TextureFormat::R8Unorm => Self::COLOR_TARGET_FORMAT_R8UNORM, + TextureFormat::Rg8Unorm => Self::COLOR_TARGET_FORMAT_RG8UNORM, + TextureFormat::Rgba8Unorm => Self::COLOR_TARGET_FORMAT_RGBA8UNORM, + TextureFormat::Rgba8UnormSrgb => Self::COLOR_TARGET_FORMAT_RGBA8UNORMSRGB, + TextureFormat::Bgra8Unorm => Self::COLOR_TARGET_FORMAT_BGRA8UNORM, + TextureFormat::Bgra8UnormSrgb => Self::COLOR_TARGET_FORMAT_BGRA8UNORMSRGB, + TextureFormat::R16Float => Self::COLOR_TARGET_FORMAT_R16FLOAT, + TextureFormat::Rg16Float => Self::COLOR_TARGET_FORMAT_RG16FLOAT, + TextureFormat::Rgba16Float => Self::COLOR_TARGET_FORMAT_RGBA16FLOAT, + TextureFormat::Rg11b10Ufloat => Self::COLOR_TARGET_FORMAT_RB11B10FLOAT, + TextureFormat::Rgb10a2Unorm => Self::COLOR_TARGET_FORMAT_RGB10A2UNORM, + _ => unreachable!("Unsupported view target format"), + } + } + + /// Get the view target format of this pipeline key. + #[inline] + pub fn color_target_format(&self) -> TextureFormat { + let target_format = *self & Self::COLOR_TARGET_FORMAT_RESERVED_BITS; + + if target_format == Self::COLOR_TARGET_FORMAT_R8UNORM { + TextureFormat::R8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RG8UNORM { + TextureFormat::Rg8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA8UNORM { + TextureFormat::Rgba8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA8UNORMSRGB { + TextureFormat::Rgba8UnormSrgb + } else if target_format == Self::COLOR_TARGET_FORMAT_BGRA8UNORM { + TextureFormat::Bgra8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_BGRA8UNORMSRGB { + TextureFormat::Bgra8UnormSrgb + } else if target_format == Self::COLOR_TARGET_FORMAT_R16FLOAT { + TextureFormat::R16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RG16FLOAT { + TextureFormat::Rg16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA16FLOAT { + TextureFormat::Rgba16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RB11B10FLOAT { + TextureFormat::Rg11b10Ufloat + } else if target_format == Self::COLOR_TARGET_FORMAT_RGB10A2UNORM { + TextureFormat::Rgb10a2Unorm + } else { + unreachable!("Unsupported view target format") + } + } } impl SpecializedMeshPipeline for Mesh2dPipeline { @@ -579,11 +634,6 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?; - let format = match key.contains(Mesh2dPipelineKey::HDR) { - true => ViewTarget::TEXTURE_FORMAT_HDR, - false => TextureFormat::bevy_default(), - }; - let (depth_write_enabled, label, blend); if key.contains(Mesh2dPipelineKey::BLEND_ALPHA) { label = "transparent_mesh2d_pipeline"; @@ -606,7 +656,7 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format, + format: key.color_target_format(), blend, write_mask: ColorWrites::ALL, })], diff --git a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs index 5b3bfe028042c..f4093f1424ba4 100644 --- a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs @@ -25,7 +25,6 @@ use bevy_platform::{ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingMode, - camera::ExtractedCamera, diagnostic::RecordDiagnostics, extract_resource::ExtractResource, mesh::{ @@ -352,7 +351,6 @@ impl SpecializedMeshPipeline for Wireframe2dPipeline { struct Wireframe2dNode; impl ViewNode for Wireframe2dNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewTarget, &'static ViewDepthTexture, @@ -362,7 +360,7 @@ impl ViewNode for Wireframe2dNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, + (view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { let Some(wireframe_phase) = @@ -387,10 +385,6 @@ impl ViewNode for Wireframe2dNode { }); let pass_span = diagnostics.pass_span(&mut render_pass, "wireframe_2d"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } - if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) { error!("Error encountered while rendering the stencil phase {err:?}"); return Err(NodeRunError::DrawError(err)); diff --git a/crates/bevy_sprite_render/src/render/mod.rs b/crates/bevy_sprite_render/src/render/mod.rs index 8bcbba16048e9..3f5e8e5c5afe7 100644 --- a/crates/bevy_sprite_render/src/render/mod.rs +++ b/crates/bevy_sprite_render/src/render/mod.rs @@ -17,7 +17,7 @@ use bevy_ecs::{ query::ROQueryItem, system::{lifetimeless::*, SystemParamItem}, }; -use bevy_image::{BevyDefault, Image, TextureAtlasLayout}; +use bevy_image::{Image, TextureAtlasLayout}; use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4}; use bevy_mesh::VertexBufferLayout; use bevy_platform::collections::HashMap; @@ -35,7 +35,7 @@ use bevy_render::{ renderer::{RenderDevice, RenderQueue}, sync_world::RenderEntity, texture::{FallbackImage, GpuImage}, - view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, + view::{ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, }; use bevy_shader::{Shader, ShaderDefVal}; @@ -104,6 +104,18 @@ bitflags::bitflags! { const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RESERVED_BITS = Self::COLOR_TARGET_FORMAT_MASK_BITS << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_R8UNORM = 0 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RG8UNORM = 1 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA8UNORM = 2 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA8UNORMSRGB = 3 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_BGRA8UNORM = 4 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_BGRA8UNORMSRGB = 5 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_R16FLOAT = 6 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RG16FLOAT = 7 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGBA16FLOAT = 8 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RB11B10FLOAT = 9 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; + const COLOR_TARGET_FORMAT_RGB10A2UNORM = 10 << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; } } @@ -113,6 +125,9 @@ impl SpritePipelineKey { const TONEMAP_METHOD_MASK_BITS: u32 = 0b111; const TONEMAP_METHOD_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones(); + const COLOR_TARGET_FORMAT_MASK_BITS: u32 = 0b1111; + const COLOR_TARGET_FORMAT_SHIFT_BITS: u32 = + Self::TONEMAP_METHOD_SHIFT_BITS - Self::COLOR_TARGET_FORMAT_MASK_BITS.count_ones(); #[inline] pub const fn from_msaa_samples(msaa_samples: u32) -> Self { @@ -126,12 +141,54 @@ impl SpritePipelineKey { 1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } + /// Create a pipeline key from view target format. #[inline] - pub const fn from_hdr(hdr: bool) -> Self { - if hdr { - SpritePipelineKey::HDR + pub fn from_color_target_format(format: TextureFormat) -> Self { + match format { + TextureFormat::R8Unorm => Self::COLOR_TARGET_FORMAT_R8UNORM, + TextureFormat::Rg8Unorm => Self::COLOR_TARGET_FORMAT_RG8UNORM, + TextureFormat::Rgba8Unorm => Self::COLOR_TARGET_FORMAT_RGBA8UNORM, + TextureFormat::Rgba8UnormSrgb => Self::COLOR_TARGET_FORMAT_RGBA8UNORMSRGB, + TextureFormat::Bgra8Unorm => Self::COLOR_TARGET_FORMAT_BGRA8UNORM, + TextureFormat::Bgra8UnormSrgb => Self::COLOR_TARGET_FORMAT_BGRA8UNORMSRGB, + TextureFormat::R16Float => Self::COLOR_TARGET_FORMAT_R16FLOAT, + TextureFormat::Rg16Float => Self::COLOR_TARGET_FORMAT_RG16FLOAT, + TextureFormat::Rgba16Float => Self::COLOR_TARGET_FORMAT_RGBA16FLOAT, + TextureFormat::Rg11b10Ufloat => Self::COLOR_TARGET_FORMAT_RB11B10FLOAT, + TextureFormat::Rgb10a2Unorm => Self::COLOR_TARGET_FORMAT_RGB10A2UNORM, + _ => unreachable!("Unsupported view target format"), + } + } + + /// Get the view target format of this pipeline key. + #[inline] + pub fn color_target_format(&self) -> TextureFormat { + let target_format = *self & Self::COLOR_TARGET_FORMAT_RESERVED_BITS; + + if target_format == Self::COLOR_TARGET_FORMAT_R8UNORM { + TextureFormat::R8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RG8UNORM { + TextureFormat::Rg8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA8UNORM { + TextureFormat::Rgba8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA8UNORMSRGB { + TextureFormat::Rgba8UnormSrgb + } else if target_format == Self::COLOR_TARGET_FORMAT_BGRA8UNORM { + TextureFormat::Bgra8Unorm + } else if target_format == Self::COLOR_TARGET_FORMAT_BGRA8UNORMSRGB { + TextureFormat::Bgra8UnormSrgb + } else if target_format == Self::COLOR_TARGET_FORMAT_R16FLOAT { + TextureFormat::R16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RG16FLOAT { + TextureFormat::Rg16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RGBA16FLOAT { + TextureFormat::Rgba16Float + } else if target_format == Self::COLOR_TARGET_FORMAT_RB11B10FLOAT { + TextureFormat::Rg11b10Ufloat + } else if target_format == Self::COLOR_TARGET_FORMAT_RGB10A2UNORM { + TextureFormat::Rgb10a2Unorm } else { - SpritePipelineKey::NONE + unreachable!("Unsupported view target format") } } } @@ -179,11 +236,6 @@ impl SpecializedRenderPipeline for SpritePipeline { } } - let format = match key.contains(SpritePipelineKey::HDR) { - true => ViewTarget::TEXTURE_FORMAT_HDR, - false => TextureFormat::bevy_default(), - }; - let instance_rate_vertex_buffer_layout = VertexBufferLayout { array_stride: 80, step_mode: VertexStepMode::Instance, @@ -232,7 +284,7 @@ impl SpecializedRenderPipeline for SpritePipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format, + format: key.color_target_format(), blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -479,21 +531,22 @@ pub fn queue_sprites( mut views: Query<( &RenderVisibleEntities, &ExtractedView, - &Msaa, + &ViewTarget, Option<&Tonemapping>, Option<&DebandDither>, )>, ) { let draw_sprite_function = draw_functions.read().id::(); - for (visible_entities, view, msaa, tonemapping, dither) in &mut views { + for (visible_entities, view, _view_target, tonemapping, dither) in &mut views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) else { continue; }; - let msaa_key = SpritePipelineKey::from_msaa_samples(msaa.samples()); - let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key; + let msaa_key = SpritePipelineKey::from_msaa_samples(view.msaa_samples); + let mut view_key = + SpritePipelineKey::from_color_target_format(view.color_target_format) | msaa_key; if !view.hdr { if let Some(tonemapping) = tonemapping { diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 3a54721776239..f345e7eae69c8 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -932,7 +932,7 @@ impl> From for UnderlineColor { pub enum FontSmoothing { /// No antialiasing. Useful for when you want to render text with a pixel art aesthetic. /// - /// Combine this with `UiAntiAlias::Off` and `Msaa::Off` on your 2D camera for a fully pixelated look. + /// Combine this with `UiAntiAlias::Off` and MSAA off on your 2D camera for a fully pixelated look. /// /// **Note:** Due to limitations of the underlying text rendering library, /// this may require specially-crafted pixel fonts to look good, especially at small sizes. diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 0f3f0e029a887..8034ae3a08d27 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -192,7 +192,7 @@ pub fn ui_focus_system( .filter_map(|(entity, camera, render_target)| { // Interactions are only supported for cameras rendering to a window. let Some(NormalizedRenderTarget::Window(window_ref)) = - render_target.normalize(primary_window) + render_target.normalize(primary_window, None) else { return None; }; diff --git a/crates/bevy_ui/src/picking_backend.rs b/crates/bevy_ui/src/picking_backend.rs index 58d84ff607120..63e254f70420d 100644 --- a/crates/bevy_ui/src/picking_backend.rs +++ b/crates/bevy_ui/src/picking_backend.rs @@ -128,7 +128,7 @@ pub fn ui_picking( .filter(|(_, _, render_target, cam_can_pick)| { (!settings.require_markers || *cam_can_pick) && render_target - .normalize(primary_window.single().ok()) + .normalize(primary_window.single().ok(), None) .is_some_and(|target| target == pointer_location.target) }) { diff --git a/crates/bevy_ui_render/src/box_shadow.rs b/crates/bevy_ui_render/src/box_shadow.rs index 44eeb9d92f6cd..7fcae7ed5e21b 100644 --- a/crates/bevy_ui_render/src/box_shadow.rs +++ b/crates/bevy_ui_render/src/box_shadow.rs @@ -14,7 +14,6 @@ use bevy_ecs::{ *, }, }; -use bevy_image::BevyDefault as _; use bevy_math::{vec2, Affine2, FloatOrd, Rect, Vec2}; use bevy_mesh::VertexBufferLayout; use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity}; @@ -126,7 +125,7 @@ pub fn init_box_shadow_pipeline(mut commands: Commands, asset_server: Res f32 { pub struct UiGradientPipelineKey { anti_alias: bool, color_space: InterpolationColorSpace, - pub hdr: bool, + pub color_format: TextureFormat, } impl SpecializedRenderPipeline for GradientPipeline { @@ -207,11 +206,7 @@ impl SpecializedRenderPipeline for GradientPipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.color_format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -612,7 +607,7 @@ pub fn queue_gradient( UiGradientPipelineKey { anti_alias: matches!(ui_anti_alias, None | Some(UiAntiAlias::On)), color_space: gradient.color_space, - hdr: view.hdr, + color_format: view.color_target_format, }, ); diff --git a/crates/bevy_ui_render/src/lib.rs b/crates/bevy_ui_render/src/lib.rs index c5289eb2c79e7..c9d90a13c16e9 100644 --- a/crates/bevy_ui_render/src/lib.rs +++ b/crates/bevy_ui_render/src/lib.rs @@ -762,6 +762,7 @@ pub fn extract_ui_camera_view( Or<(With, With)>, >, >, + query_extracted_view: Query<&ExtractedView>, mut live_entities: Local>, ) { live_entities.clear(); @@ -775,6 +776,9 @@ pub fn extract_ui_camera_view( .remove::<(UiCameraView, UiAntiAlias, BoxShadowSamples)>(); continue; } + let Ok(view) = query_extracted_view.get(render_entity) else { + continue; + }; if let Some(physical_viewport_rect) = camera.physical_viewport_rect() { // use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection @@ -809,6 +813,8 @@ pub fn extract_ui_camera_view( )), color_grading: Default::default(), invert_culling: false, + msaa_samples: 1, + color_target_format: view.color_target_format, }, // Link to the main camera view. UiViewTarget(render_entity), @@ -1417,7 +1423,7 @@ pub fn queue_uinodes( &pipeline_cache, &ui_pipeline, UiPipelineKey { - hdr: view.hdr, + texture_format: view.color_target_format, anti_alias: matches!(ui_anti_alias, None | Some(UiAntiAlias::On)), }, ); diff --git a/crates/bevy_ui_render/src/pipeline.rs b/crates/bevy_ui_render/src/pipeline.rs index 559281d2ec6b2..e769fc07f1a3b 100644 --- a/crates/bevy_ui_render/src/pipeline.rs +++ b/crates/bevy_ui_render/src/pipeline.rs @@ -1,13 +1,12 @@ use bevy_asset::{load_embedded_asset, AssetServer, Handle}; use bevy_ecs::prelude::*; -use bevy_image::BevyDefault as _; use bevy_mesh::VertexBufferLayout; use bevy_render::{ render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, *, }, - view::{ViewTarget, ViewUniform}, + view::ViewUniform, }; use bevy_shader::Shader; use bevy_utils::default; @@ -48,7 +47,7 @@ pub fn init_ui_pipeline(mut commands: Commands, asset_server: Res) #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct UiPipelineKey { - pub hdr: bool, + pub texture_format: TextureFormat, pub anti_alias: bool, } @@ -94,11 +93,7 @@ impl SpecializedRenderPipeline for UiPipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.texture_format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], diff --git a/crates/bevy_ui_render/src/render_pass.rs b/crates/bevy_ui_render/src/render_pass.rs index 76381b7fcfd5c..57616b41bd6ea 100644 --- a/crates/bevy_ui_render/src/render_pass.rs +++ b/crates/bevy_ui_render/src/render_pass.rs @@ -9,7 +9,6 @@ use bevy_ecs::{ }; use bevy_math::FloatOrd; use bevy_render::{ - camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::*, render_phase::*, @@ -22,7 +21,7 @@ use tracing::error; pub struct UiPassNode { ui_view_query: QueryState<(&'static ExtractedView, &'static UiViewTarget)>, - ui_view_target_query: QueryState<(&'static ViewTarget, &'static ExtractedCamera)>, + ui_view_target_query: QueryState<&'static ViewTarget>, ui_camera_view_query: QueryState<&'static UiCameraView>, } @@ -64,7 +63,7 @@ impl Node for UiPassNode { return Ok(()); }; - let Ok((target, camera)) = self + let Ok(target) = self .ui_view_target_query .get_manual(world, ui_view_target.0) else { @@ -101,9 +100,6 @@ impl Node for UiPassNode { }); let pass_span = diagnostics.pass_span(&mut render_pass, "ui"); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { error!("Error encountered while rendering the ui phase {err:?}"); } diff --git a/crates/bevy_ui_render/src/ui_material.rs b/crates/bevy_ui_render/src/ui_material.rs index 54610e5f2a44c..25f7be4ec19e1 100644 --- a/crates/bevy_ui_render/src/ui_material.rs +++ b/crates/bevy_ui_render/src/ui_material.rs @@ -5,7 +5,7 @@ use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::{ extract_component::ExtractComponent, - render_resource::{AsBindGroup, RenderPipelineDescriptor}, + render_resource::{AsBindGroup, RenderPipelineDescriptor, TextureFormat}, }; use bevy_shader::ShaderRef; use derive_more::derive::From; @@ -125,7 +125,7 @@ pub trait UiMaterial: AsBindGroup + Asset + Clone + Sized { } pub struct UiMaterialKey { - pub hdr: bool, + pub texture_format: TextureFormat, pub bind_group_data: M::Data, } @@ -136,7 +136,7 @@ where M::Data: PartialEq, { fn eq(&self, other: &Self) -> bool { - self.hdr == other.hdr && self.bind_group_data == other.bind_group_data + self.texture_format == other.texture_format && self.bind_group_data == other.bind_group_data } } @@ -146,7 +146,7 @@ where { fn clone(&self) -> Self { Self { - hdr: self.hdr, + texture_format: self.texture_format, bind_group_data: self.bind_group_data.clone(), } } @@ -157,7 +157,7 @@ where M::Data: core::hash::Hash, { fn hash(&self, state: &mut H) { - self.hdr.hash(state); + self.texture_format.hash(state); self.bind_group_data.hash(state); } } diff --git a/crates/bevy_ui_render/src/ui_material_pipeline.rs b/crates/bevy_ui_render/src/ui_material_pipeline.rs index fbe579edbe53d..3729a8de73f33 100644 --- a/crates/bevy_ui_render/src/ui_material_pipeline.rs +++ b/crates/bevy_ui_render/src/ui_material_pipeline.rs @@ -9,7 +9,6 @@ use bevy_ecs::{ *, }, }; -use bevy_image::BevyDefault as _; use bevy_math::{Affine2, FloatOrd, Rect, Vec2}; use bevy_mesh::VertexBufferLayout; use bevy_render::{ @@ -155,11 +154,7 @@ where shader: self.fragment_shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.texture_format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -623,7 +618,7 @@ pub fn queue_ui_material_nodes( &pipeline_cache, &ui_material_pipeline, UiMaterialKey { - hdr: view.hdr, + texture_format: view.color_target_format, bind_group_data: material.key.clone(), }, ); diff --git a/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs b/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs index 03256bb55edb6..d7e39c623ab91 100644 --- a/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs @@ -137,7 +137,7 @@ pub fn init_ui_texture_slice_pipeline(mut commands: Commands, asset_server: Res< #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct UiTextureSlicePipelineKey { - pub hdr: bool, + pub texture_format: TextureFormat, } impl SpecializedRenderPipeline for UiTextureSlicePipeline { @@ -176,11 +176,7 @@ impl SpecializedRenderPipeline for UiTextureSlicePipeline { shader: self.shader.clone(), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.texture_format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -336,7 +332,9 @@ pub fn queue_ui_slices( let pipeline = pipelines.specialize( &pipeline_cache, &ui_slicer_pipeline, - UiTextureSlicePipelineKey { hdr: view.hdr }, + UiTextureSlicePipelineKey { + texture_format: view.color_target_format, + }, ); transparent_phase.add(TransparentUi { diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 8eb5c051c3deb..b471393d1b598 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -23,12 +23,12 @@ use bevy::{ BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, Face, FragmentState, MultisampleState, PipelineCache, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, SpecializedRenderPipeline, - SpecializedRenderPipelines, StencilFaceState, StencilState, TextureFormat, - VertexFormat, VertexState, VertexStepMode, + SpecializedRenderPipelines, StencilFaceState, StencilState, VertexFormat, VertexState, + VertexStepMode, }, sync_component::SyncComponentPlugin, sync_world::{MainEntityHashMap, RenderEntity}, - view::{ExtractedView, RenderVisibleEntities, ViewTarget}, + view::{ExtractedView, RenderVisibleEntities}, Extract, Render, RenderApp, RenderStartup, RenderSystems, }, sprite_render::{ @@ -163,10 +163,7 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline { let vertex_layout = VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats); - let format = match key.contains(Mesh2dPipelineKey::HDR) { - true => ViewTarget::TEXTURE_FORMAT_HDR, - false => TextureFormat::bevy_default(), - }; + let format = key.color_target_format(); RenderPipelineDescriptor { vertex: VertexState { @@ -382,13 +379,13 @@ pub fn queue_colored_mesh2d( render_meshes: Res>, render_mesh_instances: Res, mut transparent_render_phases: ResMut>, - views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>, + views: Query<(&RenderVisibleEntities, &ExtractedView)>, ) { if render_mesh_instances.is_empty() { return; } // Iterate each view (a camera is a view) - for (visible_entities, view, msaa) in &views { + for (visible_entities, view) in &views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) else { continue; @@ -396,8 +393,8 @@ pub fn queue_colored_mesh2d( let draw_colored_mesh2d = transparent_draw_functions.read().id::(); - let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) - | Mesh2dPipelineKey::from_hdr(view.hdr); + let mesh_key = Mesh2dPipelineKey::from_msaa_samples(view.msaa_samples) + | Mesh2dPipelineKey::from_color_target_format(view.color_target_format); // Queue all entities visible to that view for (render_entity, visible_entity) in visible_entities.iter::() { diff --git a/examples/2d/pixel_grid_snap.rs b/examples/2d/pixel_grid_snap.rs index 607dcdf568877..978baa63a978a 100644 --- a/examples/2d/pixel_grid_snap.rs +++ b/examples/2d/pixel_grid_snap.rs @@ -1,8 +1,7 @@ //! Shows how to create graphics that snap to the pixel grid by rendering to a texture in 2D use bevy::{ - camera::visibility::RenderLayers, - camera::RenderTarget, + camera::{visibility::RenderLayers, CameraMainColorTargetConfig, RenderTarget}, color::palettes::css::GRAY, prelude::*, render::render_resource::{ @@ -120,7 +119,7 @@ fn setup_camera(mut commands: Commands, mut images: ResMut>) { ..default() }, RenderTarget::Image(image_handle.clone().into()), - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), InGameCamera, PIXEL_PERFECT_LAYERS, )); @@ -130,7 +129,12 @@ fn setup_camera(mut commands: Commands, mut images: ResMut>) { // The "outer" camera renders whatever is on `HIGH_RES_LAYERS` to the screen. // here, the canvas and one of the sample sprites will be rendered by this camera - commands.spawn((Camera2d, Msaa::Off, OuterCamera, HIGH_RES_LAYERS)); + commands.spawn(( + Camera2d, + CameraMainColorTargetConfig::default().with_msaa_off(), + OuterCamera, + HIGH_RES_LAYERS, + )); } /// Rotates entities to demonstrate grid snapping. diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index 46502a18da51e..3f0e9755af1b6 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -2,6 +2,8 @@ use std::{f32::consts::PI, fmt::Write}; +#[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] +use bevy::camera::CameraMainColorTargetConfig; use bevy::{ anti_alias::{ contrast_adaptive_sharpening::ContrastAdaptiveSharpening, @@ -71,7 +73,7 @@ fn modify_aa( Option<&mut Fxaa>, Option<&mut Smaa>, Option<&TemporalAntiAliasing>, - &mut Msaa, + &mut CameraMainColorTargetConfig, Option<&mut Dlss>, ), With, @@ -82,7 +84,7 @@ fn modify_aa( Option<&mut Fxaa>, Option<&mut Smaa>, Option<&TemporalAntiAliasing>, - &mut Msaa, + &mut CameraMainColorTargetConfig, ), With, >, @@ -92,14 +94,14 @@ fn modify_aa( mut commands: Commands, ) { #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - let (camera_entity, fxaa, smaa, taa, mut msaa, dlss) = camera.into_inner(); + let (camera_entity, fxaa, smaa, taa, mut color_target_config, dlss) = camera.into_inner(); #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] - let (camera_entity, fxaa, smaa, taa, mut msaa) = camera.into_inner(); + let (camera_entity, fxaa, smaa, taa, mut color_target_config) = camera.into_inner(); let mut camera = commands.entity(camera_entity); // No AA if keys.just_pressed(KeyCode::Digit1) { - *msaa = Msaa::Off; + color_target_config.sample_count = 1; camera .remove::() .remove::() @@ -108,32 +110,32 @@ fn modify_aa( } // MSAA - if keys.just_pressed(KeyCode::Digit2) && *msaa == Msaa::Off { + if keys.just_pressed(KeyCode::Digit2) && color_target_config.sample_count == 1 { camera .remove::() .remove::() .remove::() .remove::(); - *msaa = Msaa::Sample4; + color_target_config.sample_count = 4; } // MSAA Sample Count - if *msaa != Msaa::Off { + if color_target_config.sample_count != 1 { if keys.just_pressed(KeyCode::KeyQ) { - *msaa = Msaa::Sample2; + color_target_config.sample_count = 2; } if keys.just_pressed(KeyCode::KeyW) { - *msaa = Msaa::Sample4; + color_target_config.sample_count = 4; } if keys.just_pressed(KeyCode::KeyE) { - *msaa = Msaa::Sample8; + color_target_config.sample_count = 8; } } // FXAA if keys.just_pressed(KeyCode::Digit3) && fxaa.is_none() { - *msaa = Msaa::Off; + color_target_config.sample_count = 1; camera .remove::() .remove::() @@ -167,7 +169,7 @@ fn modify_aa( // SMAA if keys.just_pressed(KeyCode::Digit4) && smaa.is_none() { - *msaa = Msaa::Off; + color_target_config.sample_count = 1; camera .remove::() .remove::() @@ -193,7 +195,7 @@ fn modify_aa( // TAA if keys.just_pressed(KeyCode::Digit5) && taa.is_none() { - *msaa = Msaa::Off; + color_target_config.sample_count = 1; camera .remove::() .remove::() @@ -204,7 +206,7 @@ fn modify_aa( // DLSS #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] if keys.just_pressed(KeyCode::Digit6) && dlss.is_none() && dlss_supported.is_some() { - *msaa = Msaa::Off; + color_target_config.sample_count = 1; camera .remove::() .remove::() @@ -286,7 +288,7 @@ fn update_ui( Option<&Smaa>, Option<&TemporalAntiAliasing>, &ContrastAdaptiveSharpening, - &Msaa, + &CameraMainColorTargetConfig, Option<&Dlss>, ), With, @@ -298,7 +300,7 @@ fn update_ui( Option<&Smaa>, Option<&TemporalAntiAliasing>, &ContrastAdaptiveSharpening, - &Msaa, + &CameraMainColorTargetConfig, ), With, >, @@ -308,9 +310,9 @@ fn update_ui( >, ) { #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - let (projection, fxaa, smaa, taa, cas, msaa, dlss) = *camera; + let (projection, fxaa, smaa, taa, cas, color_target_config, dlss) = *camera; #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] - let (projection, fxaa, smaa, taa, cas, msaa) = *camera; + let (projection, fxaa, smaa, taa, cas, color_target_config) = *camera; let ui = &mut ui.0; *ui = "Antialias Method\n".to_string(); @@ -324,9 +326,13 @@ fn update_ui( ui, "No AA", '1', - *msaa == Msaa::Off && fxaa.is_none() && taa.is_none() && smaa.is_none() && dlss_none, + color_target_config.sample_count == 1 + && fxaa.is_none() + && taa.is_none() + && smaa.is_none() + && dlss_none, ); - draw_selectable_menu_item(ui, "MSAA", '2', *msaa != Msaa::Off); + draw_selectable_menu_item(ui, "MSAA", '2', color_target_config.sample_count > 1); draw_selectable_menu_item(ui, "FXAA", '3', fxaa.is_some()); draw_selectable_menu_item(ui, "SMAA", '4', smaa.is_some()); draw_selectable_menu_item(ui, "TAA", '5', taa.is_some()); @@ -335,11 +341,11 @@ fn update_ui( draw_selectable_menu_item(ui, "DLSS", '6', dlss.is_some()); } - if *msaa != Msaa::Off { + if color_target_config.sample_count != 1 { ui.push_str("\n----------\n\nSample Count\n"); - draw_selectable_menu_item(ui, "2", 'Q', *msaa == Msaa::Sample2); - draw_selectable_menu_item(ui, "4", 'W', *msaa == Msaa::Sample4); - draw_selectable_menu_item(ui, "8", 'E', *msaa == Msaa::Sample8); + draw_selectable_menu_item(ui, "2", 'Q', color_target_config.sample_count == 2); + draw_selectable_menu_item(ui, "4", 'W', color_target_config.sample_count == 4); + draw_selectable_menu_item(ui, "8", 'E', color_target_config.sample_count == 8); } if let Some(fxaa) = fxaa { diff --git a/examples/3d/atmosphere.rs b/examples/3d/atmosphere.rs index da412072a850b..a281652a55e8a 100644 --- a/examples/3d/atmosphere.rs +++ b/examples/3d/atmosphere.rs @@ -5,7 +5,7 @@ use std::f32::consts::PI; use bevy::{ anti_alias::taa::TemporalAntiAliasing, - camera::Exposure, + camera::{CameraMainColorTargetConfig, Exposure}, color::palettes::css::BLACK, core_pipeline::tonemapping::Tonemapping, image::{ @@ -127,7 +127,7 @@ fn setup_camera_fog( ambient_intensity: 0.0, ..default() }, - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), TemporalAntiAliasing::default(), ScreenSpaceReflections::default(), )); diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index 9a5fc5212b527..3943f5f74910f 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -10,6 +10,8 @@ //! | `Spacebar` | Toggle Unlit | //! | `C` | Randomize Colors | +#[cfg(target_arch = "wasm32")] +use bevy::camera::CameraMainColorTargetConfig; use bevy::{color::palettes::css::ORANGE, prelude::*, render::view::Hdr}; use rand::random; @@ -154,7 +156,7 @@ fn setup( // Since this example uses HDR, we must disable MSAA for Wasm builds, at least // until WebGPU is ready and no longer behind a feature flag in Web browsers. #[cfg(target_arch = "wasm32")] - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), )); // Controls Text diff --git a/examples/3d/camera_color_target_graph.rs b/examples/3d/camera_color_target_graph.rs new file mode 100644 index 0000000000000..bcb9cd5b69f0f --- /dev/null +++ b/examples/3d/camera_color_target_graph.rs @@ -0,0 +1,260 @@ +//! Demonstrates connecting color target input and output of multiple cameras. +//! + +use bevy::image::ToExtents; +use bevy::render::{ + render_resource::{ + BlendState, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + }, + view::Hdr, +}; +use bevy::{ + camera::{ + color_target::{ + MainColorTarget, MainColorTargetInput, MainColorTargetInputConfig, + NoAutoConfiguredMainColorTarget, WithMainColorTarget, MAIN_COLOR_TARGET_DEFAULT_USAGES, + }, + visibility::RenderLayers, + CameraOutputMode, RenderTarget, + }, + core_pipeline::tonemapping::Tonemapping, + post_process::{ + bloom::{Bloom, BloomCompositeMode}, + effect_stack::Vignette, + }, + prelude::*, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .run(); +} + +/// Set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + mut images: ResMut>, + asset_server: Res, +) { + let transform = Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y); + let uv_checker_image = asset_server.load::("textures/uv_checker_bw.png"); + + // Plane + commands.spawn(( + Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))), + MeshMaterial3d(materials.add(Color::srgb(0.4, 0.6, 0.4))), + )); + + // Cube + commands.spawn(( + Mesh3d(meshes.add(Cuboid::default())), + MeshMaterial3d(materials.add(Color::linear_rgb(5.0, 30.0, 1.0))), + Transform::from_xyz(0.0, 0.5, 0.0), + )); + + // Light + commands.spawn(( + PointLight { + shadow_maps_enabled: true, + ..Default::default() + }, + Transform::from_xyz(4.0, 8.0, 4.0), + )); + + let main_a = images.add(Image { + data: None, + texture_descriptor: TextureDescriptor { + label: Some("manual_main_texture_a"), + size: UVec2::new(1280, 720).to_extents(), + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rg11b10Ufloat, + usage: MAIN_COLOR_TARGET_DEFAULT_USAGES, + view_formats: &[], + }, + ..Default::default() + }); + let main_b = images.add(Image { + data: None, + texture_descriptor: TextureDescriptor { + label: Some("manual_main_texture_b"), + size: UVec2::new(1280, 720).to_extents(), + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rg11b10Ufloat, + usage: MAIN_COLOR_TARGET_DEFAULT_USAGES, + view_formats: &[], + }, + ..Default::default() + }); + let main_color_target = commands + .spawn(MainColorTarget::new( + main_a, + Some(main_b.clone()), + Some(images.add(Image { + data: None, + texture_descriptor: TextureDescriptor { + label: Some("manual_main_texture_multisampled"), + size: UVec2::new(1280, 720).to_extents(), + mip_level_count: 1, + sample_count: 4, // MSAAx4 + dimension: TextureDimension::D2, + format: TextureFormat::Rg11b10Ufloat, + usage: TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }, + ..Default::default() + })), + )) + .id(); + + // UI + let layer2 = RenderLayers::layer(2); + let ui_l2 = commands + .spawn(( + Text::new( + r#" + pass0 --> pass1 --+ + | + pass3 --> pass2 --+--> pass4 --> screen + +"#, + ), + TextColor(Color::srgba_u8(0x4b, 0xd0, 0x73, 255)), + BackgroundColor(Color::srgba_u8(0x21, 0x4c, 0x76, 200)), + Node { + position_type: PositionType::Absolute, + top: px(0), + left: px(0), + ..default() + }, + layer2.clone(), + )) + .id(); + let layer3 = RenderLayers::layer(3); + let ui_l3 = commands + .spawn(( + BackgroundColor(Color::srgba_u8(0x7a, 0x63, 0xae, 135)), + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + ..default() + }, + layer3.clone(), + )) + .id(); + + // Pass 0: reads from image and renders main textures, no output. + let _pass0 = commands + .spawn(( + Camera3d::default(), + Camera { + order: 0, + // Don't use clear color since the color target will be filled with image. + clear_color: ClearColorConfig::None, + // Skip upscaling to screen. + output_mode: CameraOutputMode::Skip, + ..Default::default() + }, + // Enable Hdr to bypass in-shader tonemapping. + Hdr, + // Bypass tonemapping. + Tonemapping::None, + NoAutoConfiguredMainColorTarget, + WithMainColorTarget(main_color_target), + transform, + )) + .with_related::(MainColorTarget::new(uv_checker_image, None, None)) + .id(); + + // Pass 1: renders main textures with effects. + let layer1 = RenderLayers::layer(1); + let _pass1 = commands + .spawn(( + Camera3d::default(), + Camera { + order: 1, + // Don't use clear color since the color target is filled in pass 0. + clear_color: ClearColorConfig::None, + // Skip upscaling to screen. + output_mode: CameraOutputMode::Skip, + ..Default::default() + }, + Hdr, + Bloom { + composite_mode: BloomCompositeMode::Additive, + intensity: 0.1, + ..Bloom::NATURAL + }, + Tonemapping::TonyMcMapface, + NoAutoConfiguredMainColorTarget, + WithMainColorTarget(main_color_target), + layer1, + )) + .id(); + + // Pass 2: renders UI. + let _pass2 = commands + .spawn(( + Camera3d::default(), + Camera { + order: 2, + clear_color: ClearColorConfig::Custom(Color::NONE), + // Skip upscaling to screen. + output_mode: CameraOutputMode::Skip, + ..Default::default() + }, + MainColorTargetInputConfig { + blend_state: Some(BlendState::ALPHA_BLENDING), + order: 1, + }, + layer2, + )) + .id(); + commands.entity(ui_l2).insert(UiTargetCamera(_pass2)); + + // Pass 3: renders UI. + let _pass3 = commands + .spawn(( + Camera3d::default(), + Camera { + order: 3, + clear_color: ClearColorConfig::Custom(Color::NONE), + output_mode: CameraOutputMode::Write { + blend_state: Some(BlendState::ALPHA_BLENDING), + clear_color: ClearColorConfig::None, + }, + ..Default::default() + }, + RenderTarget::MainColorTarget(_pass2), + layer3, + )) + .id(); + commands.entity(ui_l3).insert(UiTargetCamera(_pass3)); + + // Pass 4: renders to screen. + let layer4 = RenderLayers::layer(4); + let _pass4 = commands + .spawn(( + Camera3d::default(), + Camera { + order: 4, + // Don't use clear color since the color target is filled with `main_color_target`. + clear_color: ClearColorConfig::None, + ..Default::default() + }, + Vignette { + radius: 1.0, + ..Default::default() + }, + layer4, + )) + .add_related::(&[main_color_target, _pass2]) + .id(); +} diff --git a/examples/3d/contact_shadows.rs b/examples/3d/contact_shadows.rs index eb02105157ce8..57f24701a48c1 100644 --- a/examples/3d/contact_shadows.rs +++ b/examples/3d/contact_shadows.rs @@ -2,6 +2,7 @@ use crate::widgets::{RadioButton, RadioButtonText, WidgetClickEvent, WidgetClickSender}; use bevy::anti_alias::taa::TemporalAntiAliasing; +use bevy::camera::CameraMainColorTargetConfig; use bevy::core_pipeline::tonemapping::Tonemapping; use bevy::core_pipeline::Skybox; use bevy::pbr::ScreenSpaceAmbientOcclusion; @@ -127,7 +128,7 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }, ScreenSpaceAmbientOcclusion::default(), - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), Tonemapping::AcesFitted, MotionBlur { shutter_angle: 2.0, // This is really just for fun when spinning the model diff --git a/examples/3d/decal.rs b/examples/3d/decal.rs index e81124275b2c5..1c48ad58194dc 100644 --- a/examples/3d/decal.rs +++ b/examples/3d/decal.rs @@ -3,6 +3,7 @@ use bevy::{ anti_alias::fxaa::Fxaa, + camera::CameraMainColorTargetConfig, camera_controller::free_camera::{FreeCamera, FreeCameraPlugin}, core_pipeline::prepass::DepthPrepass, pbr::decal::{ForwardDecal, ForwardDecalMaterial, ForwardDecalMaterialExt}, @@ -48,7 +49,7 @@ fn setup( // Must enable the depth prepass to render forward decals DepthPrepass, // Must disable MSAA to use decals on WebGPU - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), // FXAA is a fine alternative to MSAA for anti-aliasing Fxaa::default(), Transform::from_xyz(2.0, 9.5, 2.5).looking_at(Vec3::ZERO, Vec3::Y), diff --git a/examples/3d/deferred_rendering.rs b/examples/3d/deferred_rendering.rs index cd58479790ce0..3ae4529f850b4 100644 --- a/examples/3d/deferred_rendering.rs +++ b/examples/3d/deferred_rendering.rs @@ -4,6 +4,7 @@ use std::f32::consts::*; use bevy::{ anti_alias::fxaa::Fxaa, + camera::CameraMainColorTargetConfig, core_pipeline::prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, image::ImageLoaderSettings, light::{ @@ -36,7 +37,7 @@ fn setup( Camera3d::default(), Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), // MSAA needs to be off for Deferred rendering - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), DistanceFog { color: Color::srgb_u8(43, 44, 47), falloff: FogFalloff::Linear { diff --git a/examples/3d/lightmaps.rs b/examples/3d/lightmaps.rs index 862fe11c76500..cc429d20882c0 100644 --- a/examples/3d/lightmaps.rs +++ b/examples/3d/lightmaps.rs @@ -2,6 +2,7 @@ use argh::FromArgs; use bevy::{ + camera::CameraMainColorTargetConfig, core_pipeline::prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass}, gltf::GltfMeshName, pbr::{DefaultOpaqueRendererMethod, Lightmap}, @@ -54,7 +55,7 @@ fn setup(mut commands: Commands, asset_server: Res, args: Res DepthPrepass, MotionVectorPrepass, DeferredPrepass, - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), )); } } diff --git a/examples/3d/meshlet.rs b/examples/3d/meshlet.rs index ab43a908057ce..28fcf33a16365 100644 --- a/examples/3d/meshlet.rs +++ b/examples/3d/meshlet.rs @@ -3,6 +3,7 @@ // Note: This example showcases the meshlet API, but is not the type of scene that would benefit from using meshlets. use bevy::{ + camera::CameraMainColorTargetConfig, camera_controller::free_camera::{FreeCamera, FreeCameraPlugin}, light::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}, pbr::experimental::meshlet::{MeshletMesh3d, MeshletPlugin}, @@ -39,7 +40,7 @@ fn setup( commands.spawn(( Camera3d::default(), Transform::from_translation(Vec3::new(1.8, 0.4, -0.1)).looking_at(Vec3::ZERO, Vec3::Y), - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), EnvironmentMapLight { diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), diff --git a/examples/3d/motion_blur.rs b/examples/3d/motion_blur.rs index d5a03cadefcdb..35d6651bd1776 100644 --- a/examples/3d/motion_blur.rs +++ b/examples/3d/motion_blur.rs @@ -1,6 +1,8 @@ //! Demonstrates how to enable per-object motion blur. This rendering feature can be configured per //! camera using the [`MotionBlur`] component.z +#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))] +use bevy::camera::CameraMainColorTargetConfig; use bevy::{ image::{ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor}, math::ops, @@ -29,7 +31,7 @@ fn setup_camera(mut commands: Commands) { }, // MSAA and Motion Blur together are not compatible on WebGL #[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))] - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), )); } diff --git a/examples/3d/order_independent_transparency.rs b/examples/3d/order_independent_transparency.rs index 8bc161204eac8..eb4beaaf8282d 100644 --- a/examples/3d/order_independent_transparency.rs +++ b/examples/3d/order_independent_transparency.rs @@ -4,7 +4,7 @@ //! //! [`OrderIndependentTransparencyPlugin`]: bevy::core_pipeline::oit::OrderIndependentTransparencyPlugin use bevy::{ - camera::visibility::RenderLayers, + camera::{visibility::RenderLayers, CameraMainColorTargetConfig}, color::palettes::css::{BLUE, GREEN, RED}, core_pipeline::oit::OrderIndependentTransparencySettings, prelude::*, @@ -32,7 +32,7 @@ fn setup( OrderIndependentTransparencySettings::default(), RenderLayers::layer(1), // Msaa currently doesn't work with OIT - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), )); // light diff --git a/examples/3d/pcss.rs b/examples/3d/pcss.rs index 281acf75a4504..b1368895662a5 100644 --- a/examples/3d/pcss.rs +++ b/examples/3d/pcss.rs @@ -9,6 +9,7 @@ use bevy::{ camera::{ primitives::{CubemapFrusta, Frustum}, visibility::{CubemapVisibleEntities, VisibleMeshEntities}, + CameraMainColorTargetConfig, }, core_pipeline::{ prepass::{DepthPrepass, MotionVectorPrepass}, @@ -173,7 +174,7 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) { // `TemporalAntiAliasSettings`. .insert(TemporalJitter::default()) // We want MSAA off for TAA to work properly. - .insert(Msaa::Off) + .insert(CameraMainColorTargetConfig::default().with_msaa_off()) // The depth prepass is needed for TAA. .insert(DepthPrepass) // The motion vector prepass is needed for TAA. diff --git a/examples/3d/scrolling_fog.rs b/examples/3d/scrolling_fog.rs index 9dbdc050cf04e..1e2c016328bae 100644 --- a/examples/3d/scrolling_fog.rs +++ b/examples/3d/scrolling_fog.rs @@ -12,6 +12,7 @@ use bevy::{ anti_alias::taa::TemporalAntiAliasing, + camera::CameraMainColorTargetConfig, image::{ ImageAddressMode, ImageFilterMode, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor, @@ -48,7 +49,7 @@ fn setup( commands.spawn(( Camera3d::default(), Transform::from_xyz(0.0, 2.0, 0.0).looking_at(Vec3::new(-5.0, 3.5, -6.0), Vec3::Y), - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), TemporalAntiAliasing::default(), Bloom::default(), VolumetricFog { diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index 0cfe1cec93607..7970f0a5ec5bc 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -4,6 +4,7 @@ use bevy::anti_alias::taa::TemporalAntiAliasing; use bevy::{ + camera::CameraMainColorTargetConfig, camera_controller::free_camera::{FreeCamera, FreeCameraPlugin}, core_pipeline::Skybox, image::CompressedImageFormats, @@ -72,7 +73,7 @@ fn setup(mut commands: Commands, asset_server: Res) { // camera commands.spawn(( Camera3d::default(), - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), #[cfg(not(target_arch = "wasm32"))] TemporalAntiAliasing::default(), ScreenSpaceAmbientOcclusion::default(), diff --git a/examples/3d/solari.rs b/examples/3d/solari.rs index 06b3b8389f524..4dfee38542b86 100644 --- a/examples/3d/solari.rs +++ b/examples/3d/solari.rs @@ -2,7 +2,7 @@ use argh::FromArgs; use bevy::{ - camera::CameraMainTextureUsages, + camera::CameraMainColorTargetConfig, camera_controller::free_camera::{FreeCamera, FreeCameraPlugin}, diagnostic::{Diagnostic, DiagnosticPath, DiagnosticsStore}, gltf::GltfMaterialName, @@ -145,9 +145,10 @@ fn setup_pica_pica( Transform::from_translation(Vec3::new(0.219417, 2.5764852, 6.9718704)).with_rotation( Quat::from_xyzw(-0.1466768, 0.013738206, 0.002037309, 0.989087), ), - // Msaa::Off and CameraMainTextureUsages with STORAGE_BINDING are required for Solari - CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING), - Msaa::Off, + // MSAA off and STORAGE_BINDING are required for Solari + CameraMainColorTargetConfig::default() + .with_msaa_off() + .with_usage(TextureUsages::STORAGE_BINDING), )); if args.pathtracer == Some(true) { @@ -322,9 +323,10 @@ fn setup_many_lights( Transform::from_translation(Vec3::new(0.0919233, 7.5015035, 28.449198)).with_rotation( Quat::from_xyzw(-0.18394549, 0.0019948867, 0.0003733214, 0.98293436), ), - // Msaa::Off and CameraMainTextureUsages with STORAGE_BINDING are required for Solari - CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING), - Msaa::Off, + // MSAA off and STORAGE_BINDING are required for Solari + CameraMainColorTargetConfig::default() + .with_msaa_off() + .with_usage(TextureUsages::STORAGE_BINDING), Bloom { intensity: 0.1, ..Bloom::NATURAL diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index ea30922d3e541..4a6dd2c8d244b 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -2,8 +2,20 @@ use std::f32::consts::PI; +use bevy::camera::color_target::MAIN_COLOR_TARGET_DEFAULT_USAGES; +use bevy::image::ToExtents; +use bevy::render::render_resource::{ + TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, +}; use bevy::{ - camera::Viewport, light::CascadeShadowConfigBuilder, prelude::*, window::WindowResized, + camera::{ + color_target::{MainColorTarget, NoAutoConfiguredMainColorTarget, WithMainColorTarget}, + Viewport, + }, + light::CascadeShadowConfigBuilder, + post_process::bloom::Bloom, + prelude::*, + window::WindowResized, }; fn main() { @@ -19,12 +31,13 @@ fn setup( mut commands: Commands, asset_server: Res, mut meshes: ResMut>, + mut images: ResMut>, mut materials: ResMut>, ) { // plane commands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(100.0, 100.0))), - MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), + MeshMaterial3d(materials.add(Color::srgb(0.3, 2.0, 0.3))), )); commands.spawn(SceneRoot( @@ -56,6 +69,53 @@ fn setup( .build(), )); + let main_color_target = commands + .spawn(MainColorTarget::new( + images.add(Image { + data: None, + texture_descriptor: TextureDescriptor { + label: Some("main_texture_a"), + size: UVec2::new(1280, 720).to_extents(), + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rg11b10Ufloat, + usage: MAIN_COLOR_TARGET_DEFAULT_USAGES, + view_formats: &[], + }, + ..Default::default() + }), + Some(images.add(Image { + data: None, + texture_descriptor: TextureDescriptor { + label: Some("main_texture_b"), + size: UVec2::new(1280, 720).to_extents(), + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rg11b10Ufloat, + usage: MAIN_COLOR_TARGET_DEFAULT_USAGES, + view_formats: &[], + }, + ..Default::default() + })), + Some(images.add(Image { + data: None, + texture_descriptor: TextureDescriptor { + label: Some("main_texture_multisampled"), + size: UVec2::new(1280, 720).to_extents(), + mip_level_count: 1, + sample_count: 4, // MSAAx4 + dimension: TextureDimension::D2, + format: TextureFormat::Rg11b10Ufloat, + usage: TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }, + ..Default::default() + })), + )) + .id(); + // Cameras and their dedicated UI for (index, (camera_name, camera_pos)) in [ ("Player 1", Vec3::new(0.0, 200.0, -150.0)), @@ -66,20 +126,26 @@ fn setup( .iter() .enumerate() { - let camera = commands - .spawn(( - Camera3d::default(), - Transform::from_translation(*camera_pos).looking_at(Vec3::ZERO, Vec3::Y), - Camera { - // Renders cameras with different priorities to prevent ambiguities - order: index as isize, - ..default() - }, - CameraPosition { - pos: UVec2::new((index % 2) as u32, (index / 2) as u32), - }, - )) - .id(); + let bundle = ( + Camera3d::default(), + Transform::from_translation(*camera_pos).looking_at(Vec3::ZERO, Vec3::Y), + Camera { + // Renders cameras with different priorities to prevent ambiguities + order: index as isize, + ..default() + }, + CameraPosition { + pos: UVec2::new((index % 2) as u32, (index / 2) as u32), + }, + NoAutoConfiguredMainColorTarget, + WithMainColorTarget(main_color_target), + ); + let camera = if index == 0 || index == 1 { + commands.spawn((Bloom::NATURAL, bundle)) + } else { + commands.spawn(bundle) + } + .id(); // Set up UI commands.spawn(( diff --git a/examples/3d/ssao.rs b/examples/3d/ssao.rs index e713e329e3941..143b1e1ba21ff 100644 --- a/examples/3d/ssao.rs +++ b/examples/3d/ssao.rs @@ -2,6 +2,7 @@ use bevy::{ anti_alias::taa::TemporalAntiAliasing, + camera::CameraMainColorTargetConfig, math::ops, pbr::{ScreenSpaceAmbientOcclusion, ScreenSpaceAmbientOcclusionQualityLevel}, prelude::*, @@ -30,7 +31,7 @@ fn setup( Camera3d::default(), Transform::from_xyz(-2.0, 2.0, -2.0).looking_at(Vec3::ZERO, Vec3::Y), Hdr, - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), ScreenSpaceAmbientOcclusion::default(), TemporalAntiAliasing::default(), )); diff --git a/examples/3d/ssr.rs b/examples/3d/ssr.rs index 930f88c3e0c6d..d851e704a0633 100644 --- a/examples/3d/ssr.rs +++ b/examples/3d/ssr.rs @@ -5,6 +5,7 @@ use std::ops::Range; use bevy::{ anti_alias::taa::TemporalAntiAliasing, + camera::CameraMainColorTargetConfig, color::palettes::css::{BLACK, WHITE}, core_pipeline::Skybox, image::{ @@ -418,7 +419,7 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer, app_setting Camera3d::default(), Transform::from_translation(vec3(-1.25, 2.25, 4.5)).looking_at(Vec3::ZERO, Vec3::Y), Hdr, - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), TemporalAntiAliasing::default(), ScreenSpaceReflections { min_perceptual_roughness: app_settings.min_perceptual_roughness.clone(), diff --git a/examples/3d/transmission.rs b/examples/3d/transmission.rs index 4157df7360bf6..59a4b7ccd8c46 100644 --- a/examples/3d/transmission.rs +++ b/examples/3d/transmission.rs @@ -38,7 +38,7 @@ use bevy::{ // it _greatly enhances_ the look of the resulting blur effects. // Sadly, it's not available under WebGL. #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))] -use bevy::anti_alias::taa::TemporalAntiAliasing; +use bevy::{anti_alias::taa::TemporalAntiAliasing, camera::CameraMainColorTargetConfig}; use rand::random; @@ -309,7 +309,7 @@ fn setup( Tonemapping::TonyMcMapface, Exposure { ev100: 6.0 }, #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))] - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))] TemporalAntiAliasing::default(), EnvironmentMapLight { diff --git a/examples/README.md b/examples/README.md index 38675937b46d9..759d84b933475 100644 --- a/examples/README.md +++ b/examples/README.md @@ -153,6 +153,7 @@ Example | Description [Auto Exposure](../examples/3d/auto_exposure.rs) | A scene showcasing auto exposure [Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes [Built-in postprocessing](../examples/3d/post_processing.rs) | Demonstrates the built-in postprocessing features +[Camera color target graph](../examples/3d/camera_color_target_graph.rs) | Demonstrates connecting color target input and output of multiple cameras [Camera sub view](../examples/3d/camera_sub_view.rs) | Demonstrates using different sub view effects on a camera [Clearcoat](../examples/3d/clearcoat.rs) | Demonstrates the clearcoat PBR feature [Clustered Decal Maps](../examples/3d/clustered_decal_maps.rs) | Demonstrates normal and metallic-roughness maps of decals diff --git a/examples/large_scenes/bistro/src/main.rs b/examples/large_scenes/bistro/src/main.rs index 106d6523db6e9..2c99b15c98021 100644 --- a/examples/large_scenes/bistro/src/main.rs +++ b/examples/large_scenes/bistro/src/main.rs @@ -9,7 +9,6 @@ use std::{ }; use argh::FromArgs; -use bevy::pbr::ContactShadows; use bevy::{ anti_alias::taa::TemporalAntiAliasing, camera::visibility::{NoCpuCulling, NoFrustumCulling}, @@ -25,6 +24,7 @@ use bevy::{ }, scene::SceneInstanceReady, }; +use bevy::{camera::CameraMainColorTargetConfig, pbr::ContactShadows}; use bevy::{ camera::ScreenSpaceTransmissionQuality, light::CascadeShadowConfigBuilder, render::view::Hdr, }; @@ -258,7 +258,7 @@ pub fn setup(mut commands: Commands, asset_server: Res, args: Res, mut opaque_render_phases: ResMut>, opaque_draw_functions: Res>, - views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, + views: Query<(&ExtractedView, &RenderVisibleEntities)>, mut next_tick: Local, ) { let draw_custom_phase_item = opaque_draw_functions @@ -226,7 +226,7 @@ fn queue_custom_phase_item( // Render phases are per-view, so we need to iterate over all views so that // the entity appears in them. (In this example, we have only one view, but // it's good practice to loop over all views anyway.) - for (view, view_visible_entities, msaa) in views.iter() { + for (view, view_visible_entities) in views.iter() { let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { continue; }; @@ -240,7 +240,7 @@ fn queue_custom_phase_item( // with the exception of number of MSAA samples. let Ok(pipeline_id) = pipeline .variants - .specialize(&pipeline_cache, CustomPhaseKey(*msaa)) + .specialize(&pipeline_cache, CustomPhaseKey(view.msaa_samples)) else { continue; }; @@ -345,7 +345,7 @@ impl FromWorld for CustomPhasePipeline { } #[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)] -struct CustomPhaseKey(Msaa); +struct CustomPhaseKey(u32); impl Specializer for CustomPhaseSpecializer { type Key = CustomPhaseKey; @@ -355,7 +355,7 @@ impl Specializer for CustomPhaseSpecializer { key: Self::Key, descriptor: &mut RenderPipelineDescriptor, ) -> Result, BevyError> { - descriptor.multisample.count = key.0.samples(); + descriptor.multisample.count = key.0; Ok(key) } } diff --git a/examples/shader_advanced/custom_render_phase.rs b/examples/shader_advanced/custom_render_phase.rs index 7f0d11cec7466..8aaff87097daa 100644 --- a/examples/shader_advanced/custom_render_phase.rs +++ b/examples/shader_advanced/custom_render_phase.rs @@ -12,7 +12,7 @@ use std::ops::Range; -use bevy::camera::Viewport; +use bevy::camera::{CameraMainColorTargetConfig, Viewport}; use bevy::pbr::SetMeshViewEmptyBindGroup; use bevy::{ camera::MainPassResolutionOverride, @@ -37,7 +37,6 @@ use bevy::{ }, GetBatchData, GetFullBatchData, }, - camera::ExtractedCamera, extract_component::{ExtractComponent, ExtractComponentPlugin}, mesh::{allocator::MeshAllocator, RenderMesh}, render_asset::RenderAssets, @@ -107,7 +106,7 @@ fn setup( Camera3d::default(), Transform::from_xyz(-2.0, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), // disable msaa for simplicity - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), )); } @@ -501,10 +500,10 @@ fn queue_custom_meshes( render_meshes: Res>, render_mesh_instances: Res, mut custom_render_phases: ResMut>, - mut views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>, + mut views: Query<(&ExtractedView, &RenderVisibleEntities)>, has_marker: Query<(), With>, ) { - for (view, visible_entities, msaa) in &mut views { + for (view, visible_entities) in &mut views { let Some(custom_phase) = custom_render_phases.get_mut(&view.retained_view_entity) else { continue; }; @@ -512,8 +511,8 @@ fn queue_custom_meshes( // Create the key based on the view. // In this case we only care about MSAA and HDR - let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); + let view_key = MeshPipelineKey::from_msaa_samples(view.msaa_samples) + | MeshPipelineKey::from_color_target_format(view.color_target_format); let rangefinder = view.rangefinder3d(); // Since our phase can work on any 3d mesh we can reuse the default mesh 3d filter @@ -575,7 +574,6 @@ struct CustomDrawPassLabel; struct CustomDrawNode; impl ViewNode for CustomDrawNode { type ViewQuery = ( - &'static ExtractedCamera, &'static ExtractedView, &'static ViewTarget, Option<&'static MainPassResolutionOverride>, @@ -585,7 +583,7 @@ impl ViewNode for CustomDrawNode { &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, view, target, resolution_override): QueryItem<'w, '_, Self::ViewQuery>, + (view, target, resolution_override): QueryItem<'w, '_, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { // First, we need to get our phases resource @@ -615,9 +613,7 @@ impl ViewNode for CustomDrawNode { multiview_mask: None, }); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { + if let Some(viewport) = Viewport::from_main_pass_resolution_override(resolution_override) { render_pass.set_camera_viewport(&viewport); } diff --git a/examples/shader_advanced/custom_shader_instancing.rs b/examples/shader_advanced/custom_shader_instancing.rs index b9d01fdf481e7..bbd867cefb09f 100644 --- a/examples/shader_advanced/custom_shader_instancing.rs +++ b/examples/shader_advanced/custom_shader_instancing.rs @@ -130,19 +130,20 @@ fn queue_custom( render_mesh_instances: Res, material_meshes: Query<(Entity, &MainEntity), With>, mut transparent_render_phases: ResMut>, - views: Query<(&ExtractedView, &Msaa)>, + views: Query<&ExtractedView>, ) { let draw_custom = transparent_3d_draw_functions.read().id::(); - for (view, msaa) in &views { + for view in &views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) else { continue; }; - let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); + let msaa_key = MeshPipelineKey::from_msaa_samples(view.msaa_samples); - let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); + let view_key = + msaa_key | MeshPipelineKey::from_color_target_format(view.color_target_format); let rangefinder = view.rangefinder3d(); for (entity, main_entity) in &material_meshes { let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*main_entity) diff --git a/examples/shader_advanced/render_depth_to_texture.rs b/examples/shader_advanced/render_depth_to_texture.rs index 6cb1d82aa4d8f..983b4ca14baa9 100644 --- a/examples/shader_advanced/render_depth_to_texture.rs +++ b/examples/shader_advanced/render_depth_to_texture.rs @@ -19,7 +19,7 @@ use std::f32::consts::{FRAC_PI_2, PI}; use bevy::{ asset::RenderAssetUsages, - camera::RenderTarget, + camera::{CameraMainColorTargetConfig, RenderTarget}, color::palettes::css::LIME, core_pipeline::{ core_3d::graph::{Core3d, Node3d}, @@ -236,7 +236,7 @@ fn spawn_depth_only_camera(commands: &mut Commands) { // We need to disable multisampling or the depth texture will be // multisampled, which adds complexity we don't care about for this // demo. - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), // Cameras with no render target render *nothing* by default. To get // them to render something, we must add a prepass that specifies what // we want to render: in this case, depth. @@ -250,7 +250,7 @@ fn spawn_main_camera(commands: &mut Commands) { Camera3d::default(), Transform::from_xyz(5.0, 2.0, 30.0).looking_at(vec3(5.0, 2.0, 0.0), Vec3::Y), // Disable antialiasing just for simplicity's sake. - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), )); } diff --git a/examples/shader_advanced/specialized_mesh_pipeline.rs b/examples/shader_advanced/specialized_mesh_pipeline.rs index ee5abdb54d647..266a71bc476b0 100644 --- a/examples/shader_advanced/specialized_mesh_pipeline.rs +++ b/examples/shader_advanced/specialized_mesh_pipeline.rs @@ -31,9 +31,9 @@ use bevy::{ ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, - SpecializedMeshPipelines, TextureFormat, VertexState, + SpecializedMeshPipelines, VertexState, }, - view::{ExtractedView, RenderVisibleEntities, ViewTarget}, + view::{ExtractedView, RenderVisibleEntities}, Render, RenderApp, RenderStartup, RenderSystems, }, }; @@ -223,11 +223,7 @@ impl SpecializedMeshPipeline for CustomMeshPipeline { targets: vec![Some(ColorTargetState { // This isn't required, but bevy supports HDR and non-HDR rendering // so it's generally recommended to specialize the pipeline for that - format: if mesh_key.contains(MeshPipelineKey::HDR) { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: mesh_key.color_target_format(), // For this example we only use opaque meshes, // but if you wanted to use alpha blending you would need to set it here blend: None, @@ -272,7 +268,7 @@ fn queue_custom_mesh_pipeline( Res>, ), mut specialized_mesh_pipelines: ResMut>, - views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>, + views: Query<(&RenderVisibleEntities, &ExtractedView)>, (render_meshes, render_mesh_instances): ( Res>, Res, @@ -289,14 +285,14 @@ fn queue_custom_mesh_pipeline( // Render phases are per-view, so we need to iterate over all views so that // the entity appears in them. (In this example, we have only one view, but // it's good practice to loop over all views anyway.) - for (view_visible_entities, view, msaa) in views.iter() { + for (view_visible_entities, view) in views.iter() { let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { continue; }; // Create the key based on the view. In this case we only care about MSAA and HDR - let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); + let view_key = MeshPipelineKey::from_msaa_samples(view.msaa_samples) + | MeshPipelineKey::from_color_target_format(view.color_target_format); // Find all the custom rendered entities that are visible from this // view. diff --git a/examples/tools/scene_viewer/main.rs b/examples/tools/scene_viewer/main.rs index 015798bc20640..a3a0b865d8689 100644 --- a/examples/tools/scene_viewer/main.rs +++ b/examples/tools/scene_viewer/main.rs @@ -11,7 +11,10 @@ use argh::FromArgs; use bevy::{ asset::UnapprovedPathMode, - camera::primitives::{Aabb, Sphere}, + camera::{ + primitives::{Aabb, Sphere}, + CameraMainColorTargetConfig, + }, camera_controller::free_camera::{FreeCamera, FreeCameraPlugin}, core_pipeline::prepass::{DeferredPrepass, DepthPrepass}, gltf::{convert_coordinates::GltfConvertCoordinates, GltfPlugin}, @@ -228,7 +231,7 @@ fn setup_scene_after_load( // If deferred shading was requested, include the prepass. if args.deferred == Some(true) { camera - .insert(Msaa::Off) + .insert(CameraMainColorTargetConfig::default().with_msaa_off()) .insert(DepthPrepass) .insert(DeferredPrepass); } diff --git a/examples/ui/render_ui_to_texture.rs b/examples/ui/render_ui_to_texture.rs index d2ff76bad34b4..05def9f19097a 100644 --- a/examples/ui/render_ui_to_texture.rs +++ b/examples/ui/render_ui_to_texture.rs @@ -2,6 +2,7 @@ use std::f32::consts::PI; +use bevy::camera::color_target::MainColorTarget; use bevy::picking::PickingSystems; use bevy::{ asset::{uuid::Uuid, RenderAssetUsages}, @@ -183,15 +184,21 @@ fn drive_diegetic_pointer( manual_texture_views: Res, mut window_events: MessageReader, mut pointer_inputs: MessageWriter, + query_main_color_targets: Query<&MainColorTarget>, ) -> Result { // Get the size of the texture, so we can convert from dimensionless UV coordinates that span // from 0 to 1, to pixel coordinates. let target = ui_camera .single()? - .normalize(primary_window.single().ok()) + .normalize(primary_window.single().ok(), None) .unwrap(); let target_info = target - .get_render_target_info(windows, &images, &manual_texture_views) + .get_render_target_info( + windows, + &images, + &manual_texture_views, + &query_main_color_targets, + ) .unwrap(); let size = target_info.physical_size.as_vec2(); diff --git a/tests/3d/test_invalid_skinned_mesh.rs b/tests/3d/test_invalid_skinned_mesh.rs index cbd1082fcdb37..b97d9588960fc 100644 --- a/tests/3d/test_invalid_skinned_mesh.rs +++ b/tests/3d/test_invalid_skinned_mesh.rs @@ -1,5 +1,7 @@ //! Test that the renderer can handle various invalid skinned meshes +#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))] +use bevy::camera::CameraMainColorTargetConfig; use bevy::{ asset::RenderAssetUsages, camera::ScalingMode, @@ -65,7 +67,7 @@ fn setup_environment( }, // MSAA and MotionBlur together are not compatible on WebGL. #[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))] - Msaa::Off, + CameraMainColorTargetConfig::default().with_msaa_off(), )); // Add a directional light to make sure we exercise the renderer's shadow path.