From d2af71edc5f06114c2730e576e9d6c23d29bf01f Mon Sep 17 00:00:00 2001 From: IceSentry Date: Tue, 11 Oct 2022 10:44:03 -0400 Subject: [PATCH 01/86] robswain depth prepass Co-Authored-By: Robert Swain <302146+superdump@users.noreply.github.com> --- assets/shaders/custom_material.wgsl | 4 +- .../src/core_3d/main_pass_3d_node.rs | 41 +- crates/bevy_pbr/src/lib.rs | 1 + crates/bevy_pbr/src/material.rs | 5 +- crates/bevy_pbr/src/render/depth_prepass.rs | 693 ++++++++++++++++++ crates/bevy_pbr/src/render/depth_prepass.wgsl | 99 +++ crates/bevy_pbr/src/render/mesh.rs | 15 +- crates/bevy_pbr/src/render/mod.rs | 2 + .../bevy_render/macros/src/as_bind_group.rs | 96 +-- examples/3d/3d_scene.rs | 45 +- examples/shader/shader_material.rs | 4 +- 11 files changed, 934 insertions(+), 71 deletions(-) create mode 100644 crates/bevy_pbr/src/render/depth_prepass.rs create mode 100644 crates/bevy_pbr/src/render/depth_prepass.wgsl diff --git a/assets/shaders/custom_material.wgsl b/assets/shaders/custom_material.wgsl index 95b1b7d26a196..65dd816b0e29e 100644 --- a/assets/shaders/custom_material.wgsl +++ b/assets/shaders/custom_material.wgsl @@ -1,5 +1,5 @@ struct CustomMaterial { - color: vec4, + color: vec3, }; @group(1) @binding(0) @@ -13,5 +13,5 @@ var base_color_sampler: sampler; fn fragment( #import bevy_pbr::mesh_vertex_output ) -> @location(0) vec4 { - return material.color * textureSample(base_color_texture, base_color_sampler, uv); + return vec4(material.color, 1.0) * textureSample(base_color_texture, base_color_sampler, uv); } diff --git a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs index cf3a3d38fa603..b7f281ed56d63 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs @@ -14,6 +14,17 @@ use bevy_render::{ #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; +/// Add a DepthPrepassSettings component to a view to perform a depth prepass and use it in a main +/// pass to reduce overdraw for opaque meshes. +#[derive(Clone, Component)] +pub struct DepthPrepassSettings { + /// If true then a ViewNormalsTexture component will be added to the view, and the world + /// normals will be output from the depth prepass for use in subsequent passes. + pub output_normals: bool, +} + +use super::Camera3dDepthLoadOp; + pub struct MainPass3dNode { query: QueryState< ( @@ -24,6 +35,7 @@ pub struct MainPass3dNode { &'static Camera3d, &'static ViewTarget, &'static ViewDepthTexture, + Option<&'static DepthPrepassSettings>, ), With, >, @@ -55,13 +67,21 @@ impl Node for MainPass3dNode { world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.get_input_entity(Self::IN_VIEW)?; - let (camera, opaque_phase, alpha_mask_phase, transparent_phase, camera_3d, target, depth) = - match self.query.get_manual(world, view_entity) { - Ok(query) => query, - Err(_) => { - return Ok(()); - } // No window - }; + let ( + camera, + opaque_phase, + alpha_mask_phase, + transparent_phase, + camera_3d, + target, + depth, + maybe_depth_prepass_settings, + ) = match self.query.get_manual(world, view_entity) { + Ok(query) => query, + Err(_) => { + return Ok(()); + } // No window + }; // Always run opaque pass to ensure screen is cleared { @@ -88,7 +108,12 @@ impl Node for MainPass3dNode { // NOTE: The opaque main pass loads the depth buffer and possibly overwrites it depth_ops: Some(Operations { // NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections. - load: camera_3d.depth_load_op.clone().into(), + load: if maybe_depth_prepass_settings.is_some() { + Camera3dDepthLoadOp::Load + } else { + camera_3d.depth_load_op.clone() + } + .into(), store: true, }), stencil_ops: None, diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 4a2fa66ec4714..d62e3bf061eed 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -129,6 +129,7 @@ impl Plugin for PbrPlugin { .register_type::() .add_plugin(MeshRenderPlugin) .add_plugin(MaterialPlugin::::default()) + .add_plugin(DepthPrepassPlugin) .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index d744ec77bb041..d1aa166e9cc95 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,6 +1,6 @@ use crate::{ - AlphaMode, DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, - SetMeshViewBindGroup, + queue_depth_prepass_material_meshes, AlphaMode, DrawMesh, MeshPipeline, MeshPipelineKey, + MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup, }; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; @@ -180,6 +180,7 @@ where RenderStage::Prepare, prepare_materials::.after(PrepareAssetLabel::PreAssetPrepare), ) + .add_system_to_stage(RenderStage::Queue, queue_depth_prepass_material_meshes::) .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); } } diff --git a/crates/bevy_pbr/src/render/depth_prepass.rs b/crates/bevy_pbr/src/render/depth_prepass.rs new file mode 100644 index 0000000000000..2677b92520159 --- /dev/null +++ b/crates/bevy_pbr/src/render/depth_prepass.rs @@ -0,0 +1,693 @@ +use bevy_app::Plugin; +use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; +use bevy_core_pipeline::{core_3d::DepthPrepassSettings, prelude::Camera3d}; +use bevy_ecs::{ + prelude::{Component, Entity}, + query::{QueryState, With}, + system::{ + lifetimeless::{Read, SQuery, SRes}, + Commands, Query, Res, ResMut, Resource, SystemParamItem, + }, + world::{FromWorld, World}, +}; +use bevy_reflect::TypeUuid; +use bevy_render::{ + camera::ExtractedCamera, + mesh::MeshVertexBufferLayout, + prelude::{Camera, Color, Mesh}, + render_asset::RenderAssets, + render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, + render_phase::{ + sort_phase_system, AddRenderCommand, CachedRenderPipelinePhaseItem, DrawFunctionId, + DrawFunctions, EntityPhaseItem, EntityRenderCommand, PhaseItem, RenderCommandResult, + RenderPhase, SetItemPipeline, TrackedRenderPass, + }, + render_resource::{ + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingType, BlendState, BufferBindingType, CachedRenderPipelineId, + ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, + Extent3d, FragmentState, FrontFace, LoadOp, MultisampleState, Operations, PipelineCache, + PolygonMode, PrimitiveState, RenderPassColorAttachment, RenderPassDepthStencilAttachment, + RenderPassDescriptor, RenderPipelineDescriptor, Shader, ShaderStages, ShaderType, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, + StencilFaceState, StencilState, Texture, TextureDescriptor, TextureDimension, + TextureFormat, TextureUsages, TextureView, VertexState, + }, + renderer::{RenderContext, RenderDevice}, + texture::TextureCache, + view::{ + ExtractedView, Msaa, ViewDepthTexture, ViewUniform, ViewUniformOffset, ViewUniforms, + VisibleEntities, + }, + Extract, RenderApp, RenderStage, +}; +use bevy_utils::{tracing::error, FloatOrd, HashMap}; + +use crate::{ + AlphaMode, DrawMesh, Material, MeshPipeline, MeshPipelineKey, MeshUniform, RenderMaterials, + SetMeshBindGroup, +}; + +use std::hash::Hash; + +pub mod draw_3d_graph { + pub mod node { + /// Label for the depth prepass node. + pub const DEPTH_PREPASS: &str = "depth_prepass"; + } +} +pub const DEPTH_PREPASS_FORMAT: TextureFormat = TextureFormat::Depth32Float; + +pub const DEPTH_PREPASS_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 17179930919397780179); + +pub struct DepthPrepassPlugin; + +impl Plugin for DepthPrepassPlugin { + fn build(&self, app: &mut bevy_app::App) { + load_internal_asset!( + app, + DEPTH_PREPASS_SHADER_HANDLE, + "depth_prepass.wgsl", + Shader::from_wgsl + ); + + let render_app = match app.get_sub_app_mut(RenderApp) { + Ok(render_app) => render_app, + Err(_) => return, + }; + + render_app + .add_system_to_stage( + RenderStage::Extract, + extract_core_3d_camera_depth_prepass_phase, + ) + .add_system_to_stage(RenderStage::Prepare, prepare_core_3d_normal_textures) + .add_system_to_stage(RenderStage::Queue, queue_depth_prepass_view_bind_group) + .add_system_to_stage( + RenderStage::PhaseSort, + sort_phase_system::, + ) + .add_system_to_stage( + RenderStage::PhaseSort, + sort_phase_system::, + ) + .init_resource::() + .init_resource::>() + .init_resource::>() + .init_resource::() + .init_resource::>(); + + let depth_prepass_node = DepthPrepassNode::new(&mut render_app.world); + render_app + .add_render_command::() + .add_render_command::(); + let mut graph = render_app.world.resource_mut::(); + let draw_3d_graph = graph + .get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) + .unwrap(); + draw_3d_graph.add_node(draw_3d_graph::node::DEPTH_PREPASS, depth_prepass_node); + draw_3d_graph + .add_node_edge( + draw_3d_graph::node::DEPTH_PREPASS, + bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, + ) + .unwrap(); + draw_3d_graph + .add_slot_edge( + draw_3d_graph.input_node().unwrap().id, + bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, + draw_3d_graph::node::DEPTH_PREPASS, + DepthPrepassNode::IN_VIEW, + ) + .unwrap(); + } +} + +#[derive(Resource)] +pub struct DepthPrepassPipeline { + pub view_layout: BindGroupLayout, + pub mesh_layout: BindGroupLayout, + pub skinned_mesh_layout: BindGroupLayout, +} + +impl FromWorld for DepthPrepassPipeline { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + + let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + // View + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: Some(ViewUniform::min_size()), + }, + count: None, + }, + ], + label: Some("depth_prepass_view_layout"), + }); + + let mesh_pipeline = world.resource::(); + let skinned_mesh_layout = mesh_pipeline.skinned_mesh_layout.clone(); + + Self { + view_layout, + mesh_layout: mesh_pipeline.mesh_layout.clone(), + skinned_mesh_layout, + } + } +} + +impl SpecializedMeshPipeline for DepthPrepassPipeline { + type Key = MeshPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + let mut bind_group_layout = vec![self.view_layout.clone()]; + let mut shader_defs = Vec::new(); + + if key.contains(MeshPipelineKey::ALPHA_MASK) { + shader_defs.push(String::from("ALPHA_MASK")); + // // FIXME: This needs to be implemented per-material! + // bind_group_layout.push(self.material_layout); + } + + let mut vertex_attributes = vec![Mesh::ATTRIBUTE_POSITION.at_shader_location(0)]; + + if key.contains(MeshPipelineKey::DEPTH_PREPASS_NORMALS) { + vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); + shader_defs.push(String::from("OUTPUT_NORMALS")); + } + + if layout.contains(Mesh::ATTRIBUTE_UV_0) { + shader_defs.push(String::from("VERTEX_UVS")); + vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2)); + } + + if layout.contains(Mesh::ATTRIBUTE_TANGENT) { + shader_defs.push(String::from("VERTEX_TANGENTS")); + vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); + } + + if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) + && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) + { + shader_defs.push(String::from("SKINNED")); + vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(4)); + vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(5)); + bind_group_layout.push(self.skinned_mesh_layout.clone()); + } else { + bind_group_layout.push(self.mesh_layout.clone()); + } + + let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; + + let fragment = if key.contains(MeshPipelineKey::DEPTH_PREPASS_NORMALS) + || key.contains(MeshPipelineKey::ALPHA_MASK) + { + Some(FragmentState { + shader: DEPTH_PREPASS_SHADER_HANDLE.typed::(), + entry_point: "fragment".into(), + shader_defs: shader_defs.clone(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::Rgb10a2Unorm, + blend: Some(BlendState::REPLACE), + write_mask: ColorWrites::ALL, + })], + }) + } else { + None + }; + + Ok(RenderPipelineDescriptor { + vertex: VertexState { + shader: DEPTH_PREPASS_SHADER_HANDLE.typed::(), + entry_point: "vertex".into(), + shader_defs, + buffers: vec![vertex_buffer_layout], + }, + fragment, + layout: Some(bind_group_layout), + primitive: PrimitiveState { + topology: key.primitive_topology(), + strip_index_format: None, + front_face: FrontFace::Ccw, + // FIXME: Should use from material... but that would need specialization + cull_mode: None, + unclipped_depth: false, + polygon_mode: PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(DepthStencilState { + // FIXME: Same as main pass + format: DEPTH_PREPASS_FORMAT, + depth_write_enabled: true, + depth_compare: CompareFunction::GreaterEqual, + stencil: StencilState { + front: StencilFaceState::IGNORE, + back: StencilFaceState::IGNORE, + read_mask: 0, + write_mask: 0, + }, + bias: DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: MultisampleState { + count: key.msaa_samples(), + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some("depth_prepass_pipeline".into()), + }) + } +} + +pub fn extract_core_3d_camera_depth_prepass_phase( + mut commands: Commands, + cameras_3d: Extract>>, +) { + for (entity, camera, depth_prepass_settings) in cameras_3d.iter() { + if camera.is_active { + commands.get_or_spawn(entity).insert_bundle(( + RenderPhase::::default(), + RenderPhase::::default(), + depth_prepass_settings.clone(), + )); + } + } +} + +#[derive(Component)] +pub struct ViewNormalTexture { + pub texture: Texture, + pub view: TextureView, +} + +pub fn prepare_core_3d_normal_textures( + mut commands: Commands, + mut texture_cache: ResMut, + msaa: Res, + render_device: Res, + views_3d: Query< + (Entity, &ExtractedCamera, &DepthPrepassSettings), + ( + With>, + With>, + ), + >, +) { + let mut textures = HashMap::default(); + for (entity, camera, depth_prepass_settings) in &views_3d { + if !depth_prepass_settings.output_normals { + continue; + } + if let Some(physical_target_size) = camera.physical_target_size { + let cached_texture = textures + .entry(camera.target.clone()) + .or_insert_with(|| { + texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("view_normal_texture"), + size: Extent3d { + depth_or_array_layers: 1, + width: physical_target_size.x, + height: physical_target_size.y, + }, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: TextureFormat::Rgb10a2Unorm, + usage: TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, + }, + ) + }) + .clone(); + commands.entity(entity).insert(ViewNormalTexture { + texture: cached_texture.texture, + view: cached_texture.default_view, + }); + } + } +} + +#[derive(Default, Resource)] +pub struct DepthPrepassViewBindGroup { + bind_group: Option, +} + +pub fn queue_depth_prepass_view_bind_group( + render_device: Res, + depth_prepass_pipeline: Res, + view_uniforms: Res, + mut depth_prepass_view_bind_group: ResMut, +) { + if let Some(view_binding) = view_uniforms.uniforms.binding() { + depth_prepass_view_bind_group.bind_group = + Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("depth_prepass_view_bind_group"), + layout: &depth_prepass_pipeline.view_layout, + })); + } +} + +#[allow(clippy::too_many_arguments)] +pub fn queue_depth_prepass_material_meshes( + opaque_draw_functions: Res>, + alpha_mask_draw_functions: Res>, + // material_pipeline: Res>, + depth_prepass_pipeline: Res, + // mut pipelines: ResMut>>, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, + msaa: Res, + render_meshes: Res>, + render_materials: Res>, + material_meshes: Query<(&Handle, &Handle, &MeshUniform)>, + mut views: Query<( + &ExtractedView, + &VisibleEntities, + &DepthPrepassSettings, + &mut RenderPhase, + &mut RenderPhase, + )>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + let opaque_draw_depth = opaque_draw_functions.read().get_id::().unwrap(); + let alpha_mask_draw_depth = alpha_mask_draw_functions + .read() + .get_id::() + .unwrap(); + for (view, visible_entities, depth_prepass_settings, mut opaque_phase, mut alpha_mask_phase) in + &mut views + { + let rangefinder = view.rangefinder3d(); + + let mut view_key = + MeshPipelineKey::DEPTH_PREPASS | MeshPipelineKey::from_msaa_samples(msaa.samples); + if depth_prepass_settings.output_normals { + view_key |= MeshPipelineKey::DEPTH_PREPASS_NORMALS; + } + + for visible_entity in &visible_entities.entities { + if let Ok((material_handle, mesh_handle, mesh_uniform)) = + material_meshes.get(*visible_entity) + { + if let Some(material) = render_materials.get(material_handle) { + if let Some(mesh) = render_meshes.get(mesh_handle) { + let mut key = + MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) + | view_key; + let alpha_mode = material.properties.alpha_mode; + match alpha_mode { + AlphaMode::Opaque => {} + AlphaMode::Mask(_) => key |= MeshPipelineKey::ALPHA_MASK, + AlphaMode::Blend => continue, + } + + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &depth_prepass_pipeline, + key, + &mesh.layout, + ); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + let distance = rangefinder.distance(&mesh_uniform.transform) + + material.properties.depth_bias; + match alpha_mode { + AlphaMode::Opaque => { + opaque_phase.add(OpaqueDepthPrepass { + entity: *visible_entity, + draw_function: opaque_draw_depth, + pipeline_id, + distance, + }); + } + AlphaMode::Mask(_) => { + alpha_mask_phase.add(AlphaMaskDepthPrepass { + entity: *visible_entity, + draw_function: alpha_mask_draw_depth, + pipeline_id, + distance, + }); + } + AlphaMode::Blend => {} + } + } + } + } + } + } +} + +pub struct OpaqueDepthPrepass { + pub distance: f32, + pub entity: Entity, + pub pipeline_id: CachedRenderPipelineId, + pub draw_function: DrawFunctionId, +} + +impl PhaseItem for OpaqueDepthPrepass { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } + + #[inline] + fn sort(items: &mut [Self]) { + radsort::sort_by_key(items, |item| item.distance); + } +} + +impl EntityPhaseItem for OpaqueDepthPrepass { + fn entity(&self) -> Entity { + self.entity + } +} + +impl CachedRenderPipelinePhaseItem for OpaqueDepthPrepass { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline_id + } +} + +pub struct AlphaMaskDepthPrepass { + pub distance: f32, + pub entity: Entity, + pub pipeline_id: CachedRenderPipelineId, + pub draw_function: DrawFunctionId, +} + +impl PhaseItem for AlphaMaskDepthPrepass { + type SortKey = FloatOrd; + + #[inline] + fn sort_key(&self) -> Self::SortKey { + FloatOrd(self.distance) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } + + #[inline] + fn sort(items: &mut [Self]) { + radsort::sort_by_key(items, |item| item.distance); + } +} + +impl EntityPhaseItem for AlphaMaskDepthPrepass { + fn entity(&self) -> Entity { + self.entity + } +} + +impl CachedRenderPipelinePhaseItem for AlphaMaskDepthPrepass { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline_id + } +} + +pub struct DepthPrepassNode { + main_view_query: QueryState< + ( + &'static ExtractedCamera, + &'static RenderPhase, + &'static RenderPhase, + &'static ViewDepthTexture, + Option<&'static ViewNormalTexture>, + ), + With, + >, +} + +impl DepthPrepassNode { + pub const IN_VIEW: &'static str = "view"; + + pub fn new(world: &mut World) -> Self { + Self { + main_view_query: QueryState::new(world), + } + } +} + +impl Node for DepthPrepassNode { + fn input(&self) -> Vec { + vec![SlotInfo::new(DepthPrepassNode::IN_VIEW, SlotType::Entity)] + } + + fn update(&mut self, world: &mut World) { + self.main_view_query.update_archetypes(world); + } + + fn run( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + if let Ok(( + camera, + opaque_depth_prepass_phase, + alpha_mask_depth_prepass_phase, + view_depth_texture, + maybe_view_normal_texture, + )) = self.main_view_query.get_manual(world, view_entity) + { + if opaque_depth_prepass_phase.items.is_empty() + && alpha_mask_depth_prepass_phase.items.is_empty() + { + return Ok(()); + } + + let mut color_attachments = vec![]; + if let Some(view_normal_texture) = maybe_view_normal_texture { + color_attachments.push(Some(RenderPassColorAttachment { + view: &view_normal_texture.view, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Color::BLACK.into()), + store: true, + }, + })); + } + + // Set up the pass descriptor with the depth attachment and maybe colour attachment + let pass_descriptor = RenderPassDescriptor { + label: Some("depth_prepass"), + color_attachments: &color_attachments, + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &view_depth_texture.view, + depth_ops: Some(Operations { + load: LoadOp::Clear(0.0), + store: true, + }), + stencil_ops: None, + }), + }; + let render_pass = render_context + .command_encoder + .begin_render_pass(&pass_descriptor); + let mut tracked_pass = TrackedRenderPass::new(render_pass); + if let Some(viewport) = camera.viewport.as_ref() { + tracked_pass.set_camera_viewport(viewport); + } + + { + // Run the depth prepass, sorted front-to-back + #[cfg(feature = "trace")] + let _opaque_depth_prepass_span = info_span!("opaque_depth_prepass").entered(); + let draw_functions = world.resource::>(); + + let mut draw_functions = draw_functions.write(); + for item in &opaque_depth_prepass_phase.items { + let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); + draw_function.draw(world, &mut tracked_pass, view_entity, item); + } + } + + { + // Run the depth prepass, sorted front-to-back + #[cfg(feature = "trace")] + let _alpha_mask_depth_prepass_span = + info_span!("alpha_mask_depth_prepass").entered(); + let draw_functions = world.resource::>(); + + let mut draw_functions = draw_functions.write(); + for item in &alpha_mask_depth_prepass_phase.items { + let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); + draw_function.draw(world, &mut tracked_pass, view_entity, item); + } + } + } + + Ok(()) + } +} + +pub type DrawDepth = ( + SetItemPipeline, + SetDepthViewBindGroup<0>, + SetMeshBindGroup<1>, + DrawMesh, +); + +pub struct SetDepthViewBindGroup; +impl EntityRenderCommand for SetDepthViewBindGroup { + type Param = ( + SRes, + SQuery>, + ); + #[inline] + fn render<'w>( + view: Entity, + _item: Entity, + (depth_prepass_view_bind_group, view_query): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let view_uniform_offset = view_query.get(view).unwrap(); + let depth_prepass_view_bind_group = depth_prepass_view_bind_group.into_inner(); + pass.set_bind_group( + I, + depth_prepass_view_bind_group.bind_group.as_ref().unwrap(), + &[view_uniform_offset.offset], + ); + + RenderCommandResult::Success + } +} diff --git a/crates/bevy_pbr/src/render/depth_prepass.wgsl b/crates/bevy_pbr/src/render/depth_prepass.wgsl new file mode 100644 index 0000000000000..40ff86ff27fe2 --- /dev/null +++ b/crates/bevy_pbr/src/render/depth_prepass.wgsl @@ -0,0 +1,99 @@ +#import bevy_pbr::mesh_view_types +#import bevy_pbr::mesh_types + +@group(0) @binding(0) +var view: View; + +@group(1) @binding(0) +var mesh: Mesh; + +#ifdef SKINNED +@group(1) @binding(1) +var joint_matrices: SkinnedMesh; +#import bevy_pbr::skinning +#endif + +// NOTE: Bindings must come before functions that use them! +#import bevy_pbr::mesh_functions + +struct Vertex { + @location(0) position: vec3, + +#ifdef OUTPUT_NORMALS + @location(1) normal: vec3, +#ifdef VERTEX_UVS + @location(2) uv: vec2, +#endif // VERTEX_UVS +#ifdef VERTEX_TANGENTS + @location(3) tangent: vec4, +#endif // VERTEX_TANGENTS +#endif // OUTPUT_NORMALS + +#ifdef SKINNED + @location(4) joint_indices: vec4, + @location(5) joint_weights: vec4, +#endif // SKINNED +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + +#ifdef OUTPUT_NORMALS + @location(0) world_normal: vec3, +#ifdef VERTEX_UVS + @location(1) uv: vec2, +#endif // VERTEX_UVS +#ifdef VERTEX_TANGENTS + @location(2) world_tangent: vec4, +#endif // VERTEX_TANGENTS +#endif // OUTPUT_NORMALS +} + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + +#ifdef SKINNED + var model = skin_model(vertex.joint_indices, vertex.joint_weights); +#else // SKINNED + var model = mesh.model; +#endif // SKINNED + + out.clip_position = mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); + +#ifdef OUTPUT_NORMALS +#ifdef SKINNED + out.world_normal = skin_normals(model, vertex.normal); +#else // SKINNED + out.world_normal = mesh_normal_local_to_world(vertex.normal); +#endif // SKINNED + +#ifdef VERTEX_UVS + out.uv = vertex.uv; +#endif // VERTEX_UVS + +#ifdef VERTEX_TANGENTS + out.world_tangent = mesh_tangent_local_to_world(model, vertex.tangent); +#endif // VERTEX_TANGENTS +#endif // OUTPUT_NORMALS + + return out; +} + +#ifdef OUTPUT_NORMALS +struct FragmentInput { + @builtin(front_facing) is_front: bool, + @location(0) world_normal: vec3, +#ifdef VERTEX_UVS + @location(1) uv: vec2, +#endif +#ifdef VERTEX_TANGENTS + @location(2) world_tangent: vec4, +#endif +} + +@fragment +fn fragment(in: FragmentInput) -> @location(0) vec4 { + return vec4(in.world_normal * 0.5 + vec3(0.5), 1.0); +} +#endif // OUTPUT_NORMALS diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 698d34b384d6f..af8649af71eef 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -516,8 +516,11 @@ bitflags::bitflags! { pub struct MeshPipelineKey: u32 { const NONE = 0; const TRANSPARENT_MAIN_PASS = (1 << 0); - const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; - const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; + const DEPTH_PREPASS = (1 << 1); + const DEPTH_PREPASS_NORMALS = (1 << 2); + const ALPHA_MASK = (1 << 3); + const MSAA_RESERVED_BITS = MeshPipelineKey::MSAA_MASK_BITS << MeshPipelineKey::MSAA_SHIFT_BITS; + const PRIMITIVE_TOPOLOGY_RESERVED_BITS = MeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << MeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } } @@ -621,7 +624,7 @@ impl SpecializedMeshPipeline for MeshPipeline { // For the opaque and alpha mask passes, fragments that are closer will replace // the current fragment value in the output and the depth is written to the // depth buffer - depth_write_enabled = true; + depth_write_enabled = !key.contains(MeshPipelineKey::DEPTH_PREPASS); } Ok(RenderPipelineDescriptor { @@ -654,7 +657,11 @@ impl SpecializedMeshPipeline for MeshPipeline { depth_stencil: Some(DepthStencilState { format: TextureFormat::Depth32Float, depth_write_enabled, - depth_compare: CompareFunction::Greater, + depth_compare: if key.contains(MeshPipelineKey::DEPTH_PREPASS) { + CompareFunction::Equal + } else { + CompareFunction::GreaterEqual + }, stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 353f05be1db94..b32d2bed78213 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -1,5 +1,7 @@ +mod depth_prepass; mod light; mod mesh; +pub use depth_prepass::*; pub use light::*; pub use mesh::*; diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index 30888a5227055..da0ce28c8f8d6 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -275,53 +275,53 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { resource: bindings[#binding_vec_index].get_binding(), } }); - // single field uniform bindings for a given index can use a straightforward binding - if uniform_fields.len() == 1 { - let field = &uniform_fields[0]; - let field_name = field.ident.as_ref().unwrap(); - let field_ty = &field.ty; - binding_impls.push(quote! {{ - let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); - buffer.write(&self.#field_name).unwrap(); - #render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data( - &#render_path::render_resource::BufferInitDescriptor { - label: None, - usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::UNIFORM, - contents: buffer.as_ref(), - }, - )) - }}); - - binding_layouts.push(quote!{ - #render_path::render_resource::BindGroupLayoutEntry { - binding: #binding_index, - visibility: #render_path::render_resource::ShaderStages::all(), - ty: #render_path::render_resource::BindingType::Buffer { - ty: #render_path::render_resource::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()), - }, - count: None, - } - }); - // multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType - } else { - let uniform_struct_name = Ident::new( - &format!("_{struct_name}AsBindGroupUniformStructBindGroup{binding_index}"), - Span::call_site(), - ); - - let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap()); - let field_type = uniform_fields.iter().map(|f| &f.ty); - field_struct_impls.push(quote! { - #[derive(#render_path::render_resource::ShaderType)] - struct #uniform_struct_name<'a> { - #(#field_name: &'a #field_type,)* - } - }); + // // single field uniform bindings for a given index can use a straightforward binding + // if uniform_fields.len() == 1 { + // let field = &uniform_fields[0]; + // let field_name = field.ident.as_ref().unwrap(); + // let field_ty = &field.ty; + // binding_impls.push(quote! {{ + // let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); + // buffer.write(&self.#field_name).unwrap(); + // #render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data( + // &#render_path::render_resource::BufferInitDescriptor { + // label: None, + // usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::UNIFORM, + // contents: buffer.as_ref(), + // }, + // )) + // }}); + + // binding_layouts.push(quote!{ + // #render_path::render_resource::BindGroupLayoutEntry { + // binding: #binding_index, + // visibility: #render_path::render_resource::ShaderStages::all(), + // ty: #render_path::render_resource::BindingType::Buffer { + // ty: #render_path::render_resource::BufferBindingType::Uniform, + // has_dynamic_offset: false, + // min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()), + // }, + // count: None, + // } + // }); + // // multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType + // } else { + let uniform_struct_name = Ident::new( + &format!("_{struct_name}AsBindGroupUniformStructBindGroup{binding_index}"), + Span::call_site(), + ); + + let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap()); + let field_type = uniform_fields.iter().map(|f| &f.ty); + field_struct_impls.push(quote! { + #[derive(#render_path::render_resource::ShaderType)] + struct #uniform_struct_name<'a> { + #(#field_name: &'a #field_type,)* + } + }); - let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap()); - binding_impls.push(quote! {{ + let field_name = uniform_fields.iter().map(|f| f.ident.as_ref().unwrap()); + binding_impls.push(quote! {{ let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); buffer.write(&#uniform_struct_name { #(#field_name: &self.#field_name,)* @@ -335,7 +335,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { )) }}); - binding_layouts.push(quote!{ + binding_layouts.push(quote!{ #render_path::render_resource::BindGroupLayoutEntry { binding: #binding_index, visibility: #render_path::render_resource::ShaderStages::all(), @@ -347,7 +347,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { count: None, } }); - } + // } } } diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index b2db6e3a43c30..3dfae412944d4 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -1,6 +1,14 @@ //! A simple 3D scene with light shining over a cube sitting on a plane. -use bevy::prelude::*; +use bevy::{ + core_pipeline::core_3d::DepthPrepassSettings, + prelude::*, + reflect::TypeUuid, + render::{ + render_asset::RenderAssets, + render_resource::{AsBindGroup, AsBindGroupShaderType, ShaderType}, + }, +}; fn main() { App::new() @@ -9,11 +17,23 @@ fn main() { .run(); } +#[derive(AsBindGroup, TypeUuid, Debug, Clone)] +#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] +pub struct CustomMaterial { + #[uniform(0)] + color: Vec3, + #[texture(1)] + #[sampler(2)] + color_texture: Option>, + alpha_mode: AlphaMode, +} + /// set up a simple 3D scene fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, + mut cmaterials: ResMut>, ) { // plane commands.spawn(PbrBundle { @@ -28,6 +48,17 @@ fn setup( transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() }); + // cube + commands.spawn_bundle(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: cmaterials.add(CustomMaterial { + color: Vec3::ONE, + color_texture: None, + alpha_mode: AlphaMode::Opaque, + }), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + ..default() + }); // light commands.spawn(PointLightBundle { point_light: PointLight { @@ -39,8 +70,12 @@ fn setup( ..default() }); // camera - commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), - ..default() - }); + commands + .spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }) + .insert(DepthPrepassSettings { + output_normals: true, + }); } diff --git a/examples/shader/shader_material.rs b/examples/shader/shader_material.rs index d501d6c092952..18bcd1425ce92 100644 --- a/examples/shader/shader_material.rs +++ b/examples/shader/shader_material.rs @@ -26,7 +26,7 @@ fn setup( mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), transform: Transform::from_xyz(0.0, 0.5, 0.0), material: materials.add(CustomMaterial { - color: Color::BLUE, + color: Vec3::ONE, color_texture: Some(asset_server.load("branding/icon.png")), alpha_mode: AlphaMode::Blend, }), @@ -57,7 +57,7 @@ impl Material for CustomMaterial { #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] pub struct CustomMaterial { #[uniform(0)] - color: Color, + color: Vec3, #[texture(1)] #[sampler(2)] color_texture: Option>, From 0ecce688b4c64d3b0d7f550d8a597e39b84a96fa Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 11 Oct 2022 11:31:19 -0400 Subject: [PATCH 02/86] robtfm depth prepass Co-Authored-By: robtfm <50659922+robtfm@users.noreply.github.com> --- .../src/core_3d/main_pass_3d_node.rs | 7 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 10 +- crates/bevy_pbr/src/render/depth_prepass.rs | 203 ++++++----- crates/bevy_pbr/src/render/mesh.rs | 317 ++++++++++++------ .../src/render/mesh_view_bindings.wgsl | 12 + crates/bevy_pbr/src/render/pbr.wgsl | 2 + crates/bevy_pbr/src/render/utils.wgsl | 18 + .../bevy_render/src/texture/fallback_image.rs | 134 ++++++-- crates/bevy_render/src/texture/mod.rs | 2 + examples/3d/3d_scene.rs | 57 +++- 10 files changed, 533 insertions(+), 229 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs index b7f281ed56d63..cc6d87e6ad676 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs @@ -14,12 +14,13 @@ use bevy_render::{ #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; -/// Add a DepthPrepassSettings component to a view to perform a depth prepass and use it in a main +/// Add a `DepthPrepassSettings` component to a view to perform a depth prepass and use it in a main /// pass to reduce overdraw for opaque meshes. #[derive(Clone, Component)] pub struct DepthPrepassSettings { - /// If true then a ViewNormalsTexture component will be added to the view, and the world - /// normals will be output from the depth prepass for use in subsequent passes. + /// if true then depth values will be copied to a separate texture available to the main pass + pub depth_resource: bool, + /// If true then vertex world normals will be output from the depth prepass for use in subsequent passes. pub output_normals: bool, } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 4faa8ea1ca227..c7c00904dd7ef 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -228,7 +228,7 @@ pub fn prepare_core_3d_depth_textures( msaa: Res, render_device: Res, views_3d: Query< - (Entity, &ExtractedCamera), + (Entity, &ExtractedCamera, Option<&DepthPrepassSettings>), ( With>, With>, @@ -237,11 +237,15 @@ pub fn prepare_core_3d_depth_textures( >, ) { let mut textures = HashMap::default(); - for (entity, camera) in &views_3d { + for (entity, camera, maybe_prepass) in &views_3d { if let Some(physical_target_size) = camera.physical_target_size { let cached_texture = textures .entry(camera.target.clone()) .or_insert_with(|| { + let mut usage = TextureUsages::RENDER_ATTACHMENT; + if maybe_prepass.map_or(false, |prepass_settings| prepass_settings.depth_resource) { + usage |= TextureUsages::COPY_SRC; + } texture_cache.get( &render_device, TextureDescriptor { @@ -256,7 +260,7 @@ pub fn prepare_core_3d_depth_textures( dimension: TextureDimension::D2, format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24 * bit depth for better performance */ - usage: TextureUsages::RENDER_ATTACHMENT, + usage, }, ) }) diff --git a/crates/bevy_pbr/src/render/depth_prepass.rs b/crates/bevy_pbr/src/render/depth_prepass.rs index 2677b92520159..3450abe417838 100644 --- a/crates/bevy_pbr/src/render/depth_prepass.rs +++ b/crates/bevy_pbr/src/render/depth_prepass.rs @@ -30,11 +30,11 @@ use bevy_render::{ PolygonMode, PrimitiveState, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Shader, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, - StencilFaceState, StencilState, Texture, TextureDescriptor, TextureDimension, - TextureFormat, TextureUsages, TextureView, VertexState, + StencilFaceState, StencilState, TextureDescriptor, TextureDimension, TextureFormat, + TextureUsages, VertexState, }, renderer::{RenderContext, RenderDevice}, - texture::TextureCache, + texture::{CachedTexture, TextureCache}, view::{ ExtractedView, Msaa, ViewDepthTexture, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities, @@ -289,9 +289,10 @@ pub fn extract_core_3d_camera_depth_prepass_phase( } #[derive(Component)] -pub struct ViewNormalTexture { - pub texture: Texture, - pub view: TextureView, +pub struct ViewPrepassTextures { + pub depth: Option, + pub normal: Option, + pub size: Extent3d, } pub fn prepare_core_3d_normal_textures( @@ -307,37 +308,67 @@ pub fn prepare_core_3d_normal_textures( ), >, ) { - let mut textures = HashMap::default(); + let mut depth_textures = HashMap::default(); + let mut normal_textures = HashMap::default(); for (entity, camera, depth_prepass_settings) in &views_3d { - if !depth_prepass_settings.output_normals { - continue; - } if let Some(physical_target_size) = camera.physical_target_size { - let cached_texture = textures - .entry(camera.target.clone()) - .or_insert_with(|| { - texture_cache.get( - &render_device, - TextureDescriptor { - label: Some("view_normal_texture"), - size: Extent3d { - depth_or_array_layers: 1, - width: physical_target_size.x, - height: physical_target_size.y, - }, - mip_level_count: 1, - sample_count: msaa.samples, - dimension: TextureDimension::D2, - format: TextureFormat::Rgb10a2Unorm, - usage: TextureUsages::RENDER_ATTACHMENT - | TextureUsages::TEXTURE_BINDING, - }, - ) - }) - .clone(); - commands.entity(entity).insert(ViewNormalTexture { - texture: cached_texture.texture, - view: cached_texture.default_view, + let size = Extent3d { + depth_or_array_layers: 1, + width: physical_target_size.x, + height: physical_target_size.y, + }; + + let cached_depth_texture = match depth_prepass_settings.depth_resource { + true => Some( + depth_textures + .entry(camera.target.clone()) + .or_insert_with(|| { + texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("view_depth_texture_resource"), + size, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: TextureFormat::Depth32Float, + usage: TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, + }, + ) + }) + .clone(), + ), + false => None, + }; + let cached_normal_texture = match depth_prepass_settings.output_normals { + true => Some( + normal_textures + .entry(camera.target.clone()) + .or_insert_with(|| { + texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("view_normal_texture"), + size, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: TextureFormat::Rgb10a2Unorm, + usage: TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, + }, + ) + }) + .clone(), + ), + false => None, + }; + commands.entity(entity).insert(ViewPrepassTextures { + depth: cached_depth_texture, + normal: cached_normal_texture, + size, }); } } @@ -549,7 +580,7 @@ pub struct DepthPrepassNode { &'static RenderPhase, &'static RenderPhase, &'static ViewDepthTexture, - Option<&'static ViewNormalTexture>, + &'static ViewPrepassTextures, ), With, >, @@ -586,7 +617,7 @@ impl Node for DepthPrepassNode { opaque_depth_prepass_phase, alpha_mask_depth_prepass_phase, view_depth_texture, - maybe_view_normal_texture, + view_prepass_textures, )) = self.main_view_query.get_manual(world, view_entity) { if opaque_depth_prepass_phase.items.is_empty() @@ -596,9 +627,9 @@ impl Node for DepthPrepassNode { } let mut color_attachments = vec![]; - if let Some(view_normal_texture) = maybe_view_normal_texture { + if let Some(view_normal_texture) = &view_prepass_textures.normal { color_attachments.push(Some(RenderPassColorAttachment { - view: &view_normal_texture.view, + view: &view_normal_texture.default_view, resolve_target: None, ops: Operations { load: LoadOp::Clear(Color::BLACK.into()), @@ -607,53 +638,65 @@ impl Node for DepthPrepassNode { })); } - // Set up the pass descriptor with the depth attachment and maybe colour attachment - let pass_descriptor = RenderPassDescriptor { - label: Some("depth_prepass"), - color_attachments: &color_attachments, - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &view_depth_texture.view, - depth_ops: Some(Operations { - load: LoadOp::Clear(0.0), - store: true, + { + // Set up the pass descriptor with the depth attachment and maybe colour attachment + let pass_descriptor = RenderPassDescriptor { + label: Some("depth_prepass"), + color_attachments: &color_attachments, + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &view_depth_texture.view, + depth_ops: Some(Operations { + load: LoadOp::Clear(0.0), + store: true, + }), + stencil_ops: None, }), - stencil_ops: None, - }), - }; - let render_pass = render_context - .command_encoder - .begin_render_pass(&pass_descriptor); - let mut tracked_pass = TrackedRenderPass::new(render_pass); - if let Some(viewport) = camera.viewport.as_ref() { - tracked_pass.set_camera_viewport(viewport); - } + }; + + let render_pass = render_context + .command_encoder + .begin_render_pass(&pass_descriptor); + let mut tracked_pass = TrackedRenderPass::new(render_pass); + if let Some(viewport) = camera.viewport.as_ref() { + tracked_pass.set_camera_viewport(viewport); + } - { - // Run the depth prepass, sorted front-to-back - #[cfg(feature = "trace")] - let _opaque_depth_prepass_span = info_span!("opaque_depth_prepass").entered(); - let draw_functions = world.resource::>(); - - let mut draw_functions = draw_functions.write(); - for item in &opaque_depth_prepass_phase.items { - let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_entity, item); + { + // Run the depth prepass, sorted front-to-back + #[cfg(feature = "trace")] + let _opaque_depth_prepass_span = info_span!("opaque_depth_prepass").entered(); + let draw_functions = world.resource::>(); + + let mut draw_functions = draw_functions.write(); + for item in &opaque_depth_prepass_phase.items { + let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); + draw_function.draw(world, &mut tracked_pass, view_entity, item); + } } - } - { - // Run the depth prepass, sorted front-to-back - #[cfg(feature = "trace")] - let _alpha_mask_depth_prepass_span = - info_span!("alpha_mask_depth_prepass").entered(); - let draw_functions = world.resource::>(); - - let mut draw_functions = draw_functions.write(); - for item in &alpha_mask_depth_prepass_phase.items { - let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_entity, item); + { + // Run the depth prepass, sorted front-to-back + #[cfg(feature = "trace")] + let _alpha_mask_depth_prepass_span = + info_span!("alpha_mask_depth_prepass").entered(); + let draw_functions = world.resource::>(); + + let mut draw_functions = draw_functions.write(); + for item in &alpha_mask_depth_prepass_phase.items { + let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); + draw_function.draw(world, &mut tracked_pass, view_entity, item); + } } } + + if let Some(view_depth_texture_resource) = &view_prepass_textures.depth { + // copy depth buffer to texture + render_context.command_encoder.copy_texture_to_texture( + view_depth_texture.texture.as_image_copy(), + view_depth_texture_resource.texture.as_image_copy(), + view_prepass_textures.size, + ); + } } Ok(()) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index af8649af71eef..b736e28e4660b 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,7 +1,7 @@ use crate::{ GlobalLightMeta, GpuLights, GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver, - ShadowPipeline, ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings, - CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, + ShadowPipeline, ViewClusterBindings, ViewLightsUniformOffset, ViewPrepassTextures, + ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, }; use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; @@ -18,11 +18,15 @@ use bevy_render::{ skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, GpuBufferInfo, Mesh, MeshVertexBufferLayout, }, + prelude::Msaa, render_asset::RenderAssets, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, - renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, - texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo}, + renderer::{RenderDevice, RenderQueue}, + texture::{ + BevyDefault, DefaultImageSampler, FallbackImagesDepth, FallbackImagesMsaa, GpuImage, Image, + ImageSampler, TextureFormatPixelInfo, + }, view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, RenderApp, RenderStage, }; @@ -255,6 +259,7 @@ pub fn extract_skinned_meshes( #[derive(Resource, Clone)] pub struct MeshPipeline { pub view_layout: BindGroupLayout, + pub view_layout_multisampled: BindGroupLayout, pub mesh_layout: BindGroupLayout, pub skinned_mesh_layout: BindGroupLayout, // This dummy white texture is to be used in place of optional StandardMaterial textures @@ -275,114 +280,111 @@ impl FromWorld for MeshPipeline { let clustered_forward_buffer_binding_type = render_device .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); - let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - // View - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(ViewUniform::min_size()), - }, - count: None, + let mut layout_entries = vec![ + // View + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: Some(ViewUniform::min_size()), }, - // Lights - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(GpuLights::min_size()), - }, - count: None, - }, - // Point Shadow Texture Cube Array - BindGroupLayoutEntry { - binding: 2, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Depth, - #[cfg(not(feature = "webgl"))] - view_dimension: TextureViewDimension::CubeArray, - #[cfg(feature = "webgl")] - view_dimension: TextureViewDimension::Cube, - }, - count: None, + count: None, + }, + // Lights + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: Some(GpuLights::min_size()), }, - // Point Shadow Texture Array Sampler - BindGroupLayoutEntry { - binding: 3, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Comparison), - count: None, + count: None, + }, + // Point Shadow Texture Cube Array + BindGroupLayoutEntry { + binding: 2, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Depth, + #[cfg(not(feature = "webgl"))] + view_dimension: TextureViewDimension::CubeArray, + #[cfg(feature = "webgl")] + view_dimension: TextureViewDimension::Cube, }, - // Directional Shadow Texture Array - BindGroupLayoutEntry { - binding: 4, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Depth, - #[cfg(not(feature = "webgl"))] - view_dimension: TextureViewDimension::D2Array, - #[cfg(feature = "webgl")] - view_dimension: TextureViewDimension::D2, - }, - count: None, + count: None, + }, + // Point Shadow Texture Array Sampler + BindGroupLayoutEntry { + binding: 3, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Comparison), + count: None, + }, + // Directional Shadow Texture Array + BindGroupLayoutEntry { + binding: 4, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Depth, + #[cfg(not(feature = "webgl"))] + view_dimension: TextureViewDimension::D2Array, + #[cfg(feature = "webgl")] + view_dimension: TextureViewDimension::D2, }, - // Directional Shadow Texture Array Sampler - BindGroupLayoutEntry { - binding: 5, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Comparison), - count: None, + count: None, + }, + // Directional Shadow Texture Array Sampler + BindGroupLayoutEntry { + binding: 5, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Comparison), + count: None, + }, + // PointLights + BindGroupLayoutEntry { + binding: 6, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: clustered_forward_buffer_binding_type, + has_dynamic_offset: false, + min_binding_size: Some(GpuPointLights::min_size( + clustered_forward_buffer_binding_type, + )), }, - // PointLights - BindGroupLayoutEntry { - binding: 6, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: clustered_forward_buffer_binding_type, - has_dynamic_offset: false, - min_binding_size: Some(GpuPointLights::min_size( + count: None, + }, + // ClusteredLightIndexLists + BindGroupLayoutEntry { + binding: 7, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: clustered_forward_buffer_binding_type, + has_dynamic_offset: false, + min_binding_size: Some( + ViewClusterBindings::min_size_cluster_light_index_lists( clustered_forward_buffer_binding_type, - )), - }, - count: None, - }, - // ClusteredLightIndexLists - BindGroupLayoutEntry { - binding: 7, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: clustered_forward_buffer_binding_type, - has_dynamic_offset: false, - min_binding_size: Some( - ViewClusterBindings::min_size_cluster_light_index_lists( - clustered_forward_buffer_binding_type, - ), ), - }, - count: None, + ), }, - // ClusterOffsetsAndCounts - BindGroupLayoutEntry { - binding: 8, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: clustered_forward_buffer_binding_type, - has_dynamic_offset: false, - min_binding_size: Some( - ViewClusterBindings::min_size_cluster_offsets_and_counts( - clustered_forward_buffer_binding_type, - ), + count: None, + }, + // ClusterOffsetsAndCounts + BindGroupLayoutEntry { + binding: 8, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: clustered_forward_buffer_binding_type, + has_dynamic_offset: false, + min_binding_size: Some( + ViewClusterBindings::min_size_cluster_offsets_and_counts( + clustered_forward_buffer_binding_type, ), - }, - count: None, + ), }, BindGroupLayoutEntry { binding: 9, @@ -394,10 +396,54 @@ impl FromWorld for MeshPipeline { }, count: None, }, - ], + // depth texture + BindGroupLayoutEntry { + binding: 9, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Depth, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + // normal texture + BindGroupLayoutEntry { + binding: 10, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }, + ]; + + let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + // depth texture label: Some("mesh_view_layout"), + entries: &layout_entries, }); + // modify for multisampled + layout_entries[9].ty = BindingType::Texture { + multisampled: true, + sample_type: TextureSampleType::Depth, + view_dimension: TextureViewDimension::D2, + }; + layout_entries[10].ty = BindingType::Texture { + multisampled: true, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }; + + let view_layout_multisampled = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("mesh_view_layout_multisampled"), + entries: &layout_entries, + }); + let mesh_binding = BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, @@ -483,6 +529,7 @@ impl FromWorld for MeshPipeline { MeshPipeline { view_layout, + view_layout_multisampled, mesh_layout, skinned_mesh_layout, clustered_forward_buffer_binding_type, @@ -597,7 +644,14 @@ impl SpecializedMeshPipeline for MeshPipeline { vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4)); } - let mut bind_group_layout = vec![self.view_layout.clone()]; + let mut bind_group_layout = match key.msaa_samples() { + 1 => vec![self.view_layout.clone()], + _ => { + shader_defs.push("MULTISAMPLED".into()); + vec![self.view_layout_multisampled.clone()] + } + }; + if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) { @@ -789,7 +843,15 @@ pub fn queue_mesh_view_bind_groups( light_meta: Res, global_light_meta: Res, view_uniforms: Res, - views: Query<(Entity, &ViewShadowBindings, &ViewClusterBindings)>, + views: Query<( + Entity, + &ViewShadowBindings, + &ViewClusterBindings, + Option<&ViewPrepassTextures>, + )>, + mut fallback_images: FallbackImagesMsaa, + mut fallback_depths: FallbackImagesDepth, + msaa: Res, globals_buffer: Res, ) { if let (Some(view_binding), Some(light_binding), Some(point_light_binding), Some(globals)) = ( @@ -798,7 +860,38 @@ pub fn queue_mesh_view_bind_groups( global_light_meta.gpu_point_lights.binding(), globals_buffer.buffer.binding(), ) { - for (entity, view_shadow_bindings, view_cluster_bindings) in &views { + for (entity, view_shadow_bindings, view_cluster_bindings, maybe_prepass_textures) in &views + { + let depth_view = if let Some(ViewPrepassTextures { + depth: Some(depth_prepass), + .. + }) = &maybe_prepass_textures + { + &depth_prepass.default_view + } else { + &fallback_depths + .image_for_samplecount(msaa.samples) + .texture_view + }; + + let normal_view = if let Some(ViewPrepassTextures { + normal: Some(normal_prepass), + .. + }) = &maybe_prepass_textures + { + &normal_prepass.default_view + } else { + &fallback_images + .image_for_samplecount(msaa.samples) + .texture_view + }; + + let layout = if msaa.samples > 1 { + &mesh_pipeline.view_layout_multisampled + } else { + &mesh_pipeline.view_layout + }; + let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[ BindGroupEntry { @@ -847,9 +940,17 @@ pub fn queue_mesh_view_bind_groups( binding: 9, resource: globals.clone(), }, + BindGroupEntry { + binding: 9, + resource: BindingResource::TextureView(depth_view), + }, + BindGroupEntry { + binding: 10, + resource: BindingResource::TextureView(normal_view), + }, ], label: Some("mesh_view_bind_group"), - layout: &mesh_pipeline.view_layout, + layout, }); commands.entity(entity).insert(MeshViewBindGroup { diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 9756ff05d6501..574b2eb3959e6 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -43,3 +43,15 @@ var cluster_offsets_and_counts: ClusterOffsetsAndCounts; @group(0) @binding(9) var globals: Globals; + +#ifdef MULTISAMPLED +@group(0) @binding(9) +var depth_prepass_texture: texture_depth_multisampled_2d; +@group(0) @binding(10) +var normal_prepass_texture: texture_multisampled_2d; +#else +@group(0) @binding(9) +var depth_prepass_texture: texture_depth_2d; +@group(0) @binding(10) +var normal_prepass_texture: texture_2d; +#endif \ No newline at end of file diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index cf71d1829a7a6..f7729970c6d52 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -11,6 +11,7 @@ struct FragmentInput { @builtin(front_facing) is_front: bool, @builtin(position) frag_coord: vec4, + @builtin(sample_index) sample_index: u32, #import bevy_pbr::mesh_vertex_output }; @@ -76,6 +77,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { pbr_input.N = prepare_normal( material.flags, in.world_normal, + // prepass_normal(in.frag_coord, in.sample_index), #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP in.world_tangent, diff --git a/crates/bevy_pbr/src/render/utils.wgsl b/crates/bevy_pbr/src/render/utils.wgsl index 90ad0934e595a..1cdb9926c3722 100644 --- a/crates/bevy_pbr/src/render/utils.wgsl +++ b/crates/bevy_pbr/src/render/utils.wgsl @@ -29,3 +29,21 @@ fn random1D(s: f32) -> f32 { fn coords_to_viewport_uv(position: vec2, viewport: vec4) -> vec2 { return (position - viewport.xy) / viewport.zw; } + +fn prepass_normal(frag_coord: vec4, sample_index: u32) -> vec3 { +#ifdef MULTISAMPLED + let normal_sample: vec4 = textureLoad(normal_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); +#else + let normal_sample: vec4 = textureLoad(normal_prepass_texture, vec2(frag_coord.xy), 0); +#endif + return normal_sample.xyz * 2.0 - vec3(1.0); +} + +fn prepass_depth(frag_coord: vec4, sample_index: u32) -> f32 { +#ifdef MULTISAMPLED + let depth_sample: f32 = textureLoad(depth_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); +#else + let depth_sample: f32 = textureLoad(depth_prepass_texture, vec2(frag_coord.xy), 0); +#endif + return depth_sample; +} diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index 6f0a32e3604ac..687c14a91374f 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -1,13 +1,18 @@ use crate::{render_resource::*, texture::DefaultImageSampler}; -use bevy_derive::Deref; -use bevy_ecs::{prelude::FromWorld, system::Resource}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + prelude::{FromWorld, Res, ResMut}, + system::{Resource, SystemParam}, +}; use bevy_math::Vec2; +use bevy_utils::HashMap; +use std::marker::PhantomData; use wgpu::{Extent3d, TextureDimension, TextureFormat}; use crate::{ prelude::Image, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, GpuImage, ImageSampler}, + texture::{image::TextureFormatPixelInfo, BevyDefault, GpuImage, ImageSampler}, }; /// A [`RenderApp`](crate::RenderApp) resource that contains the default "fallback image", @@ -17,36 +22,111 @@ use crate::{ #[derive(Resource, Deref)] pub struct FallbackImage(GpuImage); +fn fallback_image_new( + render_device: &RenderDevice, + render_queue: &RenderQueue, + default_sampler: &DefaultImageSampler, + format: TextureFormat, + samples: u32, +) -> GpuImage { + let data = vec![255; format.pixel_size() as usize]; + + let mut image = Image::new_fill(Extent3d::default(), TextureDimension::D2, &data, format); + + image.texture_descriptor.sample_count = samples; + image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; + + let texture = match format.describe().sample_type { + // can't initialize depth textures with data + TextureSampleType::Depth => render_device.create_texture(&image.texture_descriptor), + _ => render_device.create_texture_with_data( + render_queue, + &image.texture_descriptor, + &image.data, + ), + }; + + let texture_view = texture.create_view(&TextureViewDescriptor::default()); + let sampler = match image.sampler_descriptor { + ImageSampler::Default => (**default_sampler).clone(), + ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), + }; + GpuImage { + texture, + texture_view, + texture_format: image.texture_descriptor.format, + sampler, + size: Vec2::new( + image.texture_descriptor.size.width as f32, + image.texture_descriptor.size.height as f32, + ), + } +} + impl FromWorld for FallbackImage { fn from_world(world: &mut bevy_ecs::prelude::World) -> Self { let render_device = world.resource::(); let render_queue = world.resource::(); let default_sampler = world.resource::(); - let image = Image::new_fill( - Extent3d::default(), - TextureDimension::D2, - &[255u8; 4], - TextureFormat::bevy_default(), - ); - let texture = render_device.create_texture_with_data( + Self(fallback_image_new( + render_device, render_queue, - &image.texture_descriptor, - &image.data, - ); - let texture_view = texture.create_view(&TextureViewDescriptor::default()); - let sampler = match image.sampler_descriptor { - ImageSampler::Default => (**default_sampler).clone(), - ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), - }; - Self(GpuImage { - texture, - texture_view, - texture_format: image.texture_descriptor.format, - sampler, - size: Vec2::new( - image.texture_descriptor.size.width as f32, - image.texture_descriptor.size.height as f32, - ), + default_sampler, + TextureFormat::bevy_default(), + 1, + )) + } +} + +#[derive(Resource, Deref, DerefMut, Default)] +pub struct FallbackImageMsaaCache(HashMap); +#[derive(Resource, Deref, DerefMut, Default)] +pub struct FallbackImageDepthCache(HashMap); + +#[derive(SystemParam)] +pub struct FallbackImagesMsaa<'w, 's> { + cache: ResMut<'w, FallbackImageMsaaCache>, + render_device: Res<'w, RenderDevice>, + render_queue: Res<'w, RenderQueue>, + default_sampler: Res<'w, DefaultImageSampler>, + #[system_param(ignore)] + _p: PhantomData<&'s ()>, +} + +impl<'w, 's> FallbackImagesMsaa<'w, 's> { + pub fn image_for_samplecount(&mut self, sample_count: u32) -> &GpuImage { + self.cache.entry(sample_count).or_insert_with(|| { + fallback_image_new( + &self.render_device, + &self.render_queue, + &self.default_sampler, + TextureFormat::bevy_default(), + sample_count, + ) + }) + } +} + +#[derive(SystemParam)] +pub struct FallbackImagesDepth<'w, 's> { + cache: ResMut<'w, FallbackImageDepthCache>, + render_device: Res<'w, RenderDevice>, + render_queue: Res<'w, RenderQueue>, + default_sampler: Res<'w, DefaultImageSampler>, + #[system_param(ignore)] + _p: PhantomData<&'s ()>, +} + +impl<'w, 's> FallbackImagesDepth<'w, 's> { + pub fn image_for_samplecount(&mut self, sample_count: u32) -> &GpuImage { + self.cache.entry(sample_count).or_insert_with(|| { + fallback_image_new( + &self.render_device, + &self.render_queue, + &self.default_sampler, + TextureFormat::Depth32Float, + sample_count, + ) }) } } diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index cebb5d34cf92a..b0db8338a955a 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -80,6 +80,8 @@ impl Plugin for ImagePlugin { .insert_resource(DefaultImageSampler(default_sampler)) .init_resource::() .init_resource::() + .init_resource::() + .init_resource::() .add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system); } } diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index 3dfae412944d4..598334df13851 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -4,16 +4,16 @@ use bevy::{ core_pipeline::core_3d::DepthPrepassSettings, prelude::*, reflect::TypeUuid, - render::{ - render_asset::RenderAssets, - render_resource::{AsBindGroup, AsBindGroupShaderType, ShaderType}, - }, + render::render_resource::{AsBindGroup, ShaderRef}, }; fn main() { App::new() + .insert_resource(Msaa { samples: 1 }) .add_plugins(DefaultPlugins) + .add_plugin(MaterialPlugin::::default()) .add_startup_system(setup) + .add_system(rotate) .run(); } @@ -28,6 +28,16 @@ pub struct CustomMaterial { alpha_mode: AlphaMode, } +impl Material for CustomMaterial { + fn fragment_shader() -> ShaderRef { + "shaders/custom_material.wgsl".into() + } + + fn alpha_mode(&self) -> AlphaMode { + self.alpha_mode + } +} + /// set up a simple 3D scene fn setup( mut commands: Commands, @@ -38,14 +48,33 @@ fn setup( // plane commands.spawn(PbrBundle { mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), - material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + material: materials.add(StandardMaterial { + unlit: true, + ..Color::rgb(0.3, 0.5, 0.3).into() + }), ..default() }); // cube - commands.spawn(PbrBundle { + commands + .spawn_bundle(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(StandardMaterial { + unlit: true, + ..Color::rgb(0.8, 0.7, 0.6).into() + }), + transform: Transform::from_xyz(-1.0, 0.5, 0.0), + ..default() + }) + .insert(Rotates); + // cube + commands.spawn_bundle(MaterialMeshBundle { mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), - material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), - transform: Transform::from_xyz(0.0, 0.5, 0.0), + material: cmaterials.add(CustomMaterial { + color: Vec3::ONE, + color_texture: None, + alpha_mode: AlphaMode::Opaque, + }), + transform: Transform::from_xyz(1.0, 0.5, 0.0), ..default() }); // cube @@ -76,6 +105,18 @@ fn setup( ..default() }) .insert(DepthPrepassSettings { + depth_resource: true, output_normals: true, }); } + +#[derive(Component)] +struct Rotates; + +fn rotate(mut q: Query<&mut Transform, With>, time: Res