diff --git a/pipelined/bevy_core_pipeline/src/lib.rs b/pipelined/bevy_core_pipeline/src/lib.rs index 6ad959ee7497f..1ce7ab334e41d 100644 --- a/pipelined/bevy_core_pipeline/src/lib.rs +++ b/pipelined/bevy_core_pipeline/src/lib.rs @@ -24,7 +24,7 @@ use bevy_render2::{ render_resource::*, renderer::RenderDevice, texture::{Image, TextureCache}, - view::ExtractedView, + view::{ExtractedView, Msaa, ViewDepthTexture}, RenderApp, RenderStage, RenderWorld, }; @@ -53,7 +53,6 @@ pub mod draw_2d_graph { pub const NAME: &str = "draw_2d"; pub mod input { pub const VIEW_ENTITY: &str = "view_entity"; - pub const RENDER_TARGET: &str = "render_target"; } pub mod node { pub const MAIN_PASS: &str = "main_pass"; @@ -64,8 +63,6 @@ pub mod draw_3d_graph { pub const NAME: &str = "draw_3d"; pub mod input { pub const VIEW_ENTITY: &str = "view_entity"; - pub const RENDER_TARGET: &str = "render_target"; - pub const DEPTH: &str = "depth"; } pub mod node { pub const MAIN_PASS: &str = "main_pass"; @@ -95,10 +92,10 @@ impl Plugin for CorePipelinePlugin { let mut draw_2d_graph = RenderGraph::default(); draw_2d_graph.add_node(draw_2d_graph::node::MAIN_PASS, pass_node_2d); - let input_node_id = draw_2d_graph.set_input(vec![ - SlotInfo::new(draw_2d_graph::input::VIEW_ENTITY, SlotType::Entity), - SlotInfo::new(draw_2d_graph::input::RENDER_TARGET, SlotType::TextureView), - ]); + let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new( + draw_2d_graph::input::VIEW_ENTITY, + SlotType::Entity, + )]); draw_2d_graph .add_slot_edge( input_node_id, @@ -107,23 +104,14 @@ impl Plugin for CorePipelinePlugin { MainPass2dNode::IN_VIEW, ) .unwrap(); - draw_2d_graph - .add_slot_edge( - input_node_id, - draw_2d_graph::input::RENDER_TARGET, - draw_2d_graph::node::MAIN_PASS, - MainPass2dNode::IN_COLOR_ATTACHMENT, - ) - .unwrap(); graph.add_sub_graph(draw_2d_graph::NAME, draw_2d_graph); let mut draw_3d_graph = RenderGraph::default(); draw_3d_graph.add_node(draw_3d_graph::node::MAIN_PASS, pass_node_3d); - let input_node_id = draw_3d_graph.set_input(vec![ - SlotInfo::new(draw_3d_graph::input::VIEW_ENTITY, SlotType::Entity), - SlotInfo::new(draw_3d_graph::input::RENDER_TARGET, SlotType::TextureView), - SlotInfo::new(draw_3d_graph::input::DEPTH, SlotType::TextureView), - ]); + let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new( + draw_3d_graph::input::VIEW_ENTITY, + SlotType::Entity, + )]); draw_3d_graph .add_slot_edge( input_node_id, @@ -132,22 +120,6 @@ impl Plugin for CorePipelinePlugin { MainPass3dNode::IN_VIEW, ) .unwrap(); - draw_3d_graph - .add_slot_edge( - input_node_id, - draw_3d_graph::input::RENDER_TARGET, - draw_3d_graph::node::MAIN_PASS, - MainPass3dNode::IN_COLOR_ATTACHMENT, - ) - .unwrap(); - draw_3d_graph - .add_slot_edge( - input_node_id, - draw_3d_graph::input::DEPTH, - draw_3d_graph::node::MAIN_PASS, - MainPass3dNode::IN_DEPTH, - ) - .unwrap(); graph.add_sub_graph(draw_3d_graph::NAME, draw_3d_graph); graph.add_node(node::MAIN_PASS_DEPENDENCIES, EmptyNode); @@ -235,11 +207,6 @@ impl RenderCommand for SetItemPipeline { } } -pub struct ViewDepthTexture { - pub texture: Texture, - pub view: TextureView, -} - pub fn extract_clear_color(clear_color: Res, mut render_world: ResMut) { // If the clear color has changed if clear_color.is_changed() { @@ -271,10 +238,11 @@ pub fn extract_core_pipeline_camera_phases( pub fn prepare_core_views_system( mut commands: Commands, mut texture_cache: ResMut, + msaa: Res, render_device: Res, - views: Query<(Entity, &ExtractedView), With>>, + views_3d: Query<(Entity, &ExtractedView), With>>, ) { - for (entity, view) in views.iter() { + for (entity, view) in views_3d.iter() { let cached_texture = texture_cache.get( &render_device, TextureDescriptor { @@ -285,7 +253,7 @@ pub fn prepare_core_views_system( height: view.height as u32, }, mip_level_count: 1, - sample_count: 1, + sample_count: msaa.samples, dimension: TextureDimension::D2, format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24 * bit depth for better performance */ diff --git a/pipelined/bevy_core_pipeline/src/main_pass_2d.rs b/pipelined/bevy_core_pipeline/src/main_pass_2d.rs index 32996fc728807..86b1baa49e99c 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_2d.rs +++ b/pipelined/bevy_core_pipeline/src/main_pass_2d.rs @@ -5,15 +5,15 @@ use bevy_render2::{ render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass}, render_resource::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor}, renderer::RenderContext, - view::ExtractedView, + view::{ExtractedView, ViewTarget}, }; pub struct MainPass2dNode { - query: QueryState<&'static RenderPhase, With>, + query: + QueryState<(&'static RenderPhase, &'static ViewTarget), With>, } impl MainPass2dNode { - pub const IN_COLOR_ATTACHMENT: &'static str = "color_attachment"; pub const IN_VIEW: &'static str = "view"; pub fn new(world: &mut World) -> Self { @@ -25,10 +25,7 @@ impl MainPass2dNode { impl Node for MainPass2dNode { fn input(&self) -> Vec { - vec![ - SlotInfo::new(MainPass2dNode::IN_COLOR_ATTACHMENT, SlotType::TextureView), - SlotInfo::new(MainPass2dNode::IN_VIEW, SlotType::Entity), - ] + vec![SlotInfo::new(MainPass2dNode::IN_VIEW, SlotType::Entity)] } fn update(&mut self, world: &mut World) { @@ -41,12 +38,16 @@ impl Node for MainPass2dNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let color_attachment_texture = graph.get_input_texture(Self::IN_COLOR_ATTACHMENT)?; + let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let (transparent_phase, target) = self + .query + .get_manual(world, view_entity) + .expect("view entity should exist"); let clear_color = world.get_resource::().unwrap(); let pass_descriptor = RenderPassDescriptor { label: Some("main_pass_2d"), color_attachments: &[RenderPassColorAttachment { - view: color_attachment_texture, + view: &target.view, resolve_target: None, ops: Operations { load: LoadOp::Clear(clear_color.0.into()), @@ -56,16 +57,10 @@ impl Node for MainPass2dNode { depth_stencil_attachment: None, }; - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; let draw_functions = world .get_resource::>() .unwrap(); - let transparent_phase = self - .query - .get_manual(world, view_entity) - .expect("view entity should exist"); - let render_pass = render_context .command_encoder .begin_render_pass(&pass_descriptor); diff --git a/pipelined/bevy_core_pipeline/src/main_pass_3d.rs b/pipelined/bevy_core_pipeline/src/main_pass_3d.rs index b6ade776781ff..5ecc53eda80cb 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_3d.rs +++ b/pipelined/bevy_core_pipeline/src/main_pass_3d.rs @@ -8,16 +8,21 @@ use bevy_render2::{ RenderPassDescriptor, }, renderer::RenderContext, - view::ExtractedView, + view::{ExtractedView, ViewDepthTexture, ViewTarget}, }; pub struct MainPass3dNode { - query: QueryState<&'static RenderPhase, With>, + query: QueryState< + ( + &'static RenderPhase, + &'static ViewTarget, + &'static ViewDepthTexture, + ), + With, + >, } impl MainPass3dNode { - pub const IN_COLOR_ATTACHMENT: &'static str = "color_attachment"; - pub const IN_DEPTH: &'static str = "depth"; pub const IN_VIEW: &'static str = "view"; pub fn new(world: &mut World) -> Self { @@ -29,11 +34,7 @@ impl MainPass3dNode { impl Node for MainPass3dNode { fn input(&self) -> Vec { - vec![ - SlotInfo::new(MainPass3dNode::IN_COLOR_ATTACHMENT, SlotType::TextureView), - SlotInfo::new(MainPass3dNode::IN_DEPTH, SlotType::TextureView), - SlotInfo::new(MainPass3dNode::IN_VIEW, SlotType::Entity), - ] + vec![SlotInfo::new(MainPass3dNode::IN_VIEW, SlotType::Entity)] } fn update(&mut self, world: &mut World) { @@ -46,21 +47,32 @@ impl Node for MainPass3dNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let color_attachment_texture = graph.get_input_texture(Self::IN_COLOR_ATTACHMENT)?; + let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let (transparent_phase, target, depth) = self + .query + .get_manual(world, view_entity) + .expect("view entity should exist"); let clear_color = world.get_resource::().unwrap(); - let depth_texture = graph.get_input_texture(Self::IN_DEPTH)?; let pass_descriptor = RenderPassDescriptor { label: Some("main_pass_3d"), color_attachments: &[RenderPassColorAttachment { - view: color_attachment_texture, - resolve_target: None, + view: if let Some(sampled_target) = &target.sampled_target { + sampled_target + } else { + &target.view + }, + resolve_target: if target.sampled_target.is_some() { + Some(&target.view) + } else { + None + }, ops: Operations { load: LoadOp::Clear(clear_color.0.into()), store: true, }, }], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: depth_texture, + view: &depth.view, depth_ops: Some(Operations { load: LoadOp::Clear(0.0), store: true, @@ -69,14 +81,9 @@ impl Node for MainPass3dNode { }), }; - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; let draw_functions = world .get_resource::>() .unwrap(); - let transparent_phase = self - .query - .get_manual(world, view_entity) - .expect("view entity should exist"); let render_pass = render_context .command_encoder diff --git a/pipelined/bevy_core_pipeline/src/main_pass_driver.rs b/pipelined/bevy_core_pipeline/src/main_pass_driver.rs index 6744f8273e2cf..07382d499598f 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_driver.rs +++ b/pipelined/bevy_core_pipeline/src/main_pass_driver.rs @@ -1,10 +1,8 @@ -use crate::ViewDepthTexture; use bevy_ecs::world::World; use bevy_render2::{ - camera::{CameraPlugin, ExtractedCamera, ExtractedCameraNames}, + camera::{CameraPlugin, ExtractedCameraNames}, render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue}, renderer::RenderContext, - view::ExtractedWindows, }; pub struct MainPassDriverNode; @@ -17,41 +15,17 @@ impl Node for MainPassDriverNode { world: &World, ) -> Result<(), NodeRunError> { let extracted_cameras = world.get_resource::().unwrap(); - let extracted_windows = world.get_resource::().unwrap(); - if let Some(camera_2d) = extracted_cameras.entities.get(CameraPlugin::CAMERA_2D) { - let extracted_camera = world.entity(*camera_2d).get::().unwrap(); - let extracted_window = extracted_windows.get(&extracted_camera.window_id).unwrap(); - let swap_chain_texture = extracted_window - .swap_chain_texture - .as_ref() - .unwrap() - .clone(); graph.run_sub_graph( crate::draw_2d_graph::NAME, - vec![ - SlotValue::Entity(*camera_2d), - SlotValue::TextureView(swap_chain_texture), - ], + vec![SlotValue::Entity(*camera_2d)], )?; } if let Some(camera_3d) = extracted_cameras.entities.get(CameraPlugin::CAMERA_3D) { - let extracted_camera = world.entity(*camera_3d).get::().unwrap(); - let depth_texture = world.entity(*camera_3d).get::().unwrap(); - let extracted_window = extracted_windows.get(&extracted_camera.window_id).unwrap(); - let swap_chain_texture = extracted_window - .swap_chain_texture - .as_ref() - .unwrap() - .clone(); graph.run_sub_graph( crate::draw_3d_graph::NAME, - vec![ - SlotValue::Entity(*camera_3d), - SlotValue::TextureView(swap_chain_texture), - SlotValue::TextureView(depth_texture.view.clone()), - ], + vec![SlotValue::Entity(*camera_3d)], )?; } diff --git a/pipelined/bevy_pbr2/src/render/mod.rs b/pipelined/bevy_pbr2/src/render/mod.rs index 3125ca2a28fb8..36e6c43e81b66 100644 --- a/pipelined/bevy_pbr2/src/render/mod.rs +++ b/pipelined/bevy_pbr2/src/render/mod.rs @@ -21,7 +21,7 @@ use bevy_render2::{ render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, - view::{ExtractedView, ViewUniformOffset, ViewUniforms}, + view::{ExtractedView, Msaa, ViewUniformOffset, ViewUniforms}, }; use bevy_transform::components::GlobalTransform; use crevice::std140::AsStd140; @@ -369,16 +369,34 @@ impl FromWorld for PbrPipeline { } } -// TODO: add actual specialization key: MSAA, normal maps, shadeless, etc bitflags::bitflags! { #[repr(transparent)] - pub struct PbrPipelineKey: u32 { } + // NOTE: Apparently quadro drivers support up to 64x MSAA. + /// MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA. + pub struct PbrPipelineKey: u32 { + const NONE = 0; + const MSAA_RESERVED_BITS = PbrPipelineKey::MSAA_MASK_BITS << PbrPipelineKey::MSAA_SHIFT_BITS; + } +} + +impl PbrPipelineKey { + const MSAA_MASK_BITS: u32 = 0b111111; + const MSAA_SHIFT_BITS: u32 = 32 - 6; + + pub fn from_msaa_samples(msaa_samples: u32) -> Self { + let msaa_bits = ((msaa_samples - 1) & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; + PbrPipelineKey::from_bits(msaa_bits).unwrap() + } + + pub fn msaa_samples(&self) -> u32 { + ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + 1 + } } impl SpecializedPipeline for PbrPipeline { type Key = PbrPipelineKey; - fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { RenderPipelineDescriptor { vertex: VertexState { shader: PBR_SHADER_HANDLE.typed::(), @@ -461,7 +479,7 @@ impl SpecializedPipeline for PbrPipeline { }, }), multisample: MultisampleState { - count: 1, + count: key.msaa_samples(), mask: !0, alpha_to_coverage_enabled: false, }, @@ -508,6 +526,7 @@ pub fn queue_meshes( mut pipelines: ResMut>, mut pipeline_cache: ResMut, light_meta: Res, + msaa: Res, view_uniforms: Res, render_materials: Res>, standard_material_meshes: Query< @@ -525,6 +544,7 @@ pub fn queue_meshes( view_uniforms.uniforms.binding(), light_meta.view_gpu_lights.binding(), ) { + let msaa_key = PbrPipelineKey::from_msaa_samples(msaa.samples); for (entity, view, view_lights, mut transparent_phase) in views.iter_mut() { let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[ @@ -580,8 +600,8 @@ pub fn queue_meshes( continue; } - let key = PbrPipelineKey::empty(); - let pipeline_id = pipelines.specialize(&mut pipeline_cache, &pbr_pipeline, key); + let pipeline_id = + pipelines.specialize(&mut pipeline_cache, &pbr_pipeline, msaa_key); // NOTE: row 2 of the view matrix dotted with column 3 of the model matrix // gives the z component of translation of the mesh in view space let mesh_z = view_row_2.dot(mesh_uniform.transform.col(3)); @@ -692,3 +712,14 @@ impl RenderCommand for DrawMesh { } } } + +#[cfg(test)] +mod tests { + use super::PbrPipelineKey; + #[test] + fn pbr_key_msaa_samples() { + for i in 1..=64 { + assert_eq!(PbrPipelineKey::from_msaa_samples(i).msaa_samples(), i); + } + } +} diff --git a/pipelined/bevy_render2/src/renderer/mod.rs b/pipelined/bevy_render2/src/renderer/mod.rs index 840faed624036..d75bf035987ae 100644 --- a/pipelined/bevy_render2/src/renderer/mod.rs +++ b/pipelined/bevy_render2/src/renderer/mod.rs @@ -5,7 +5,10 @@ use bevy_utils::tracing::{info, info_span}; pub use graph_runner::*; pub use render_device::*; -use crate::{render_graph::RenderGraph, view::ExtractedWindows}; +use crate::{ + render_graph::RenderGraph, + view::{ExtractedWindows, ViewTarget}, +}; use bevy_ecs::prelude::*; use std::sync::Arc; use wgpu::{Backends, CommandEncoder, DeviceDescriptor, Instance, Queue, RequestAdapterOptions}; @@ -27,6 +30,17 @@ pub fn render_system(world: &mut World) { { let span = info_span!("present_frames"); let _guard = span.enter(); + + // Remove ViewTarget components to ensure swap chain TextureViews are dropped. + // If all TextureViews aren't dropped before present, acquiring the next swap chain texture will fail. + let view_entities = world + .query_filtered::>() + .iter(world) + .collect::>(); + for view_entity in view_entities { + world.entity_mut(view_entity).remove::(); + } + let mut windows = world.get_resource_mut::().unwrap(); for window in windows.values_mut() { if let Some(texture_view) = window.swap_chain_texture.take() { diff --git a/pipelined/bevy_render2/src/view/mod.rs b/pipelined/bevy_render2/src/view/mod.rs index 94d98bba85019..c350e6519838e 100644 --- a/pipelined/bevy_render2/src/view/mod.rs +++ b/pipelined/bevy_render2/src/view/mod.rs @@ -1,10 +1,13 @@ pub mod window; +use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages}; pub use window::*; use crate::{ - render_resource::DynamicUniformVec, + camera::{ExtractedCamera, ExtractedCameraNames}, + render_resource::{DynamicUniformVec, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, + texture::{BevyDefault, TextureCache}, RenderApp, RenderStage, }; use bevy_app::{App, Plugin}; @@ -17,12 +20,38 @@ pub struct ViewPlugin; impl Plugin for ViewPlugin { fn build(&self, app: &mut App) { + app.init_resource::(); app.sub_app(RenderApp) .init_resource::() - .add_system_to_stage(RenderStage::Prepare, prepare_views); + .add_system_to_stage(RenderStage::Extract, extract_msaa) + .add_system_to_stage(RenderStage::Prepare, prepare_view_uniforms) + .add_system_to_stage( + RenderStage::Prepare, + prepare_view_targets.after(WindowSystem::Prepare), + ); } } +#[derive(Clone)] +pub struct Msaa { + /// The number of samples to run for Multi-Sample Anti-Aliasing. Higher numbers result in + /// smoother edges. Note that WGPU currently only supports 1 or 4 samples. + /// Ultimately we plan on supporting whatever is natively supported on a given device. + /// Check out this issue for more info: https://github.com/gfx-rs/wgpu/issues/1832 + pub samples: u32, +} + +impl Default for Msaa { + fn default() -> Self { + Self { samples: 4 } + } +} + +pub fn extract_msaa(mut commands: Commands, msaa: Res) { + // NOTE: windows.is_changed() handles cases where a window was resized + commands.insert_resource(msaa.clone()); +} + pub struct ExtractedView { pub projection: Mat4, pub transform: GlobalTransform, @@ -46,17 +75,27 @@ pub struct ViewUniformOffset { pub offset: u32, } -fn prepare_views( +pub struct ViewTarget { + pub view: TextureView, + pub sampled_target: Option, +} + +pub struct ViewDepthTexture { + pub texture: Texture, + pub view: TextureView, +} + +fn prepare_view_uniforms( mut commands: Commands, render_device: Res, render_queue: Res, mut view_uniforms: ResMut, - mut extracted_views: Query<(Entity, &ExtractedView)>, + mut views: Query<(Entity, &ExtractedView)>, ) { view_uniforms .uniforms - .reserve_and_clear(extracted_views.iter_mut().len(), &render_device); - for (entity, camera) in extracted_views.iter() { + .reserve_and_clear(views.iter_mut().len(), &render_device); + for (entity, camera) in views.iter() { let projection = camera.projection; let view_uniforms = ViewUniformOffset { offset: view_uniforms.uniforms.push(ViewUniform { @@ -71,3 +110,57 @@ fn prepare_views( view_uniforms.uniforms.write_buffer(&render_queue); } + +fn prepare_view_targets( + mut commands: Commands, + camera_names: Res, + windows: Res, + msaa: Res, + render_device: Res, + mut texture_cache: ResMut, + cameras: Query<&ExtractedCamera>, +) { + for entity in camera_names.entities.values().copied() { + let camera = if let Ok(camera) = cameras.get(entity) { + camera + } else { + continue; + }; + let window = if let Some(window) = windows.get(&camera.window_id) { + window + } else { + continue; + }; + let swap_chain_texture = if let Some(texture) = &window.swap_chain_texture { + texture + } else { + continue; + }; + let sampled_target = if msaa.samples > 1 { + let sampled_texture = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("sampled_color_attachment_texture"), + size: Extent3d { + width: window.physical_width, + height: window.physical_height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: TextureFormat::bevy_default(), + usage: TextureUsages::RENDER_ATTACHMENT, + }, + ); + Some(sampled_texture.default_view.clone()) + } else { + None + }; + + commands.entity(entity).insert(ViewTarget { + view: swap_chain_texture.clone(), + sampled_target, + }); + } +} diff --git a/pipelined/bevy_render2/src/view/window.rs b/pipelined/bevy_render2/src/view/window.rs index 80838540e35b0..00aea10aa14d6 100644 --- a/pipelined/bevy_render2/src/view/window.rs +++ b/pipelined/bevy_render2/src/view/window.rs @@ -17,6 +17,11 @@ pub struct NonSendMarker; pub struct WindowRenderPlugin; +#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)] +pub enum WindowSystem { + Prepare, +} + impl Plugin for WindowRenderPlugin { fn build(&self, app: &mut App) { app.sub_app(RenderApp) @@ -24,7 +29,10 @@ impl Plugin for WindowRenderPlugin { .init_resource::() .init_resource::() .add_system_to_stage(RenderStage::Extract, extract_windows) - .add_system_to_stage(RenderStage::Prepare, prepare_windows); + .add_system_to_stage( + RenderStage::Prepare, + prepare_windows.label(WindowSystem::Prepare), + ); } }