From fb433800b78f7ef3aac36f0a8cc2bf4c429e63b5 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Fri, 12 Nov 2021 21:20:13 -0800 Subject: [PATCH 01/12] Mesh refactor and mesh pipeline specialization --- crates/bevy_gltf/src/loader.rs | 10 +- crates/bevy_pbr/src/lib.rs | 4 +- crates/bevy_pbr/src/material.rs | 146 +++++----- crates/bevy_pbr/src/pbr_material.rs | 7 +- crates/bevy_pbr/src/render/light.rs | 108 ++----- crates/bevy_pbr/src/render/mesh.rs | 88 ++---- crates/bevy_pbr/src/wireframe.rs | 29 +- crates/bevy_render/src/mesh/mesh/mod.rs | 267 ++++++++++++++---- crates/bevy_render/src/mesh/shape/capsule.rs | 6 +- .../bevy_render/src/mesh/shape/icosphere.rs | 6 +- crates/bevy_render/src/mesh/shape/mod.rs | 18 +- crates/bevy_render/src/mesh/shape/torus.rs | 6 +- crates/bevy_render/src/mesh/shape/uvsphere.rs | 6 +- .../src/render_resource/pipeline.rs | 32 ++- .../src/render_resource/pipeline_cache.rs | 4 +- .../render_resource/pipeline_specializer.rs | 70 ++++- crates/bevy_sprite/src/mesh2d/material.rs | 86 +++--- crates/bevy_sprite/src/mesh2d/mesh.rs | 88 ++---- crates/bevy_sprite/src/render/mod.rs | 39 ++- crates/bevy_ui/src/render/pipeline.rs | 34 +-- examples/2d/mesh2d_manual.rs | 4 +- examples/shader/animate_shader.rs | 38 ++- examples/shader/shader_defs.rs | 32 ++- examples/shader/shader_instancing.rs | 43 +-- examples/shader/shader_material_glsl.rs | 7 +- 25 files changed, 664 insertions(+), 514 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 1e85d483fd4d1..5b8cca3b9b7e1 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -125,33 +125,33 @@ async fn load_gltf<'a, 'b>( .read_positions() .map(|v| VertexAttributeValues::Float32x3(v.collect())) { - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vertex_attribute); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_attribute); } if let Some(vertex_attribute) = reader .read_normals() .map(|v| VertexAttributeValues::Float32x3(v.collect())) { - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); } if let Some(vertex_attribute) = reader .read_tangents() .map(|v| VertexAttributeValues::Float32x4(v.collect())) { - mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); + mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); } if let Some(vertex_attribute) = reader .read_tex_coords(0) .map(|v| VertexAttributeValues::Float32x2(v.into_f32().collect())) { - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vertex_attribute); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vertex_attribute); } else { let len = mesh.count_vertices(); let uvs = vec![[0.0, 0.0]; len]; bevy_log::debug!("missing `TEXCOORD_0` vertex attribute, loading zeroed out UVs"); - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); } // if let Some(vertex_attribute) = reader diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 8325030519b98..8b5d4ad8e50a6 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -40,7 +40,7 @@ use bevy_render::{ prelude::Color, render_graph::RenderGraph, render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, - render_resource::{Shader, SpecializedPipelines}, + render_resource::{Shader, SpecializedMeshPipelines}, view::VisibilitySystems, RenderApp, RenderStage, }; @@ -178,7 +178,7 @@ impl Plugin for PbrPlugin { .init_resource::>() .init_resource::() .init_resource::() - .init_resource::>(); + .init_resource::>(); let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); render_app.add_render_command::(); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index d9ee95446a887..e63fdfd54698d 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -15,7 +15,7 @@ use bevy_ecs::{ world::FromWorld, }; use bevy_render::{ - mesh::Mesh, + mesh::{Mesh, MeshVertexBufferLayout}, render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}, render_component::ExtractComponentPlugin, render_phase::{ @@ -24,7 +24,7 @@ use bevy_render::{ }, render_resource::{ BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, - SpecializedPipeline, SpecializedPipelines, + SpecializedMeshPipeline, SpecializedMeshPipelines, }, renderer::RenderDevice, view::{ExtractedView, Msaa, VisibleEntities}, @@ -81,7 +81,12 @@ impl SpecializedMaterial for M { fn key(_material: &::PreparedAsset) -> Self::Key {} #[inline] - fn specialize(_key: Self::Key, _descriptor: &mut RenderPipelineDescriptor) {} + fn specialize( + _descriptor: &mut RenderPipelineDescriptor, + _key: Self::Key, + _layout: &MeshVertexBufferLayout, + ) { + } #[inline] fn bind_group(material: &::PreparedAsset) -> &BindGroup { @@ -130,7 +135,11 @@ pub trait SpecializedMaterial: Asset + RenderAsset { fn key(material: &::PreparedAsset) -> Self::Key; /// Specializes the given `descriptor` according to the given `key`. - fn specialize(key: Self::Key, descriptor: &mut RenderPipelineDescriptor); + fn specialize( + descriptor: &mut RenderPipelineDescriptor, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ); /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial::bind_group_layout`]. fn bind_group(material: &::PreparedAsset) -> &BindGroup; @@ -188,7 +197,7 @@ impl Plugin for MaterialPlugin { .add_render_command::>() .add_render_command::>() .init_resource::>() - .init_resource::>>() + .init_resource::>>() .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); } } @@ -202,11 +211,15 @@ pub struct MaterialPipeline { marker: PhantomData, } -impl SpecializedPipeline for MaterialPipeline { +impl SpecializedMeshPipeline for MaterialPipeline { type Key = (MeshPipelineKey, M::Key); - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key.0); + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(key.0, layout); if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -220,7 +233,7 @@ impl SpecializedPipeline for MaterialPipeline { self.mesh_pipeline.mesh_layout.clone(), ]); - M::specialize(key.1, &mut descriptor); + M::specialize(&mut descriptor, key.1, layout); descriptor } } @@ -275,7 +288,7 @@ pub fn queue_material_meshes( alpha_mask_draw_functions: Res>, transparent_draw_functions: Res>, material_pipeline: Res>, - mut pipelines: ResMut>>, + mut pipelines: ResMut>>, mut pipeline_cache: ResMut, msaa: Res, render_meshes: Res>, @@ -307,72 +320,71 @@ pub fn queue_material_meshes( let inverse_view_matrix = view.transform.compute_matrix().inverse(); let inverse_view_row_2 = inverse_view_matrix.row(2); - let mesh_key = MeshPipelineKey::from_msaa_samples(msaa.samples); + let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); 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) { - let mut mesh_key = mesh_key; if let Some(mesh) = render_meshes.get(mesh_handle) { - if mesh.has_tangents { - mesh_key |= MeshPipelineKey::VERTEX_TANGENTS; + let mut mesh_key = + MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) + | msaa_key; + let alpha_mode = M::alpha_mode(material); + if let AlphaMode::Blend = alpha_mode { + mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS; } - mesh_key |= - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - } - let alpha_mode = M::alpha_mode(material); - if let AlphaMode::Blend = alpha_mode { - mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS; - } - let specialized_key = M::key(material); - let pipeline_id = pipelines.specialize( - &mut pipeline_cache, - &material_pipeline, - (mesh_key, specialized_key), - ); - - // NOTE: row 2 of the inverse 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 = inverse_view_row_2.dot(mesh_uniform.transform.col(3)); - match alpha_mode { - AlphaMode::Opaque => { - opaque_phase.add(Opaque3d { - entity: *visible_entity, - draw_function: draw_opaque_pbr, - pipeline: pipeline_id, - // NOTE: Front-to-back ordering for opaque with ascending sort means near should have the - // lowest sort key and getting further away should increase. As we have - // -z in front of the camera, values in view space decrease away from the - // camera. Flipping the sign of mesh_z results in the correct front-to-back ordering - distance: -mesh_z, - }); - } - AlphaMode::Mask(_) => { - alpha_mask_phase.add(AlphaMask3d { - entity: *visible_entity, - draw_function: draw_alpha_mask_pbr, - pipeline: pipeline_id, - // NOTE: Front-to-back ordering for alpha mask with ascending sort means near should have the - // lowest sort key and getting further away should increase. As we have - // -z in front of the camera, values in view space decrease away from the - // camera. Flipping the sign of mesh_z results in the correct front-to-back ordering - distance: -mesh_z, - }); - } - AlphaMode::Blend => { - transparent_phase.add(Transparent3d { - entity: *visible_entity, - draw_function: draw_transparent_pbr, - pipeline: pipeline_id, - // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the - // lowest sort key and getting closer should increase. As we have - // -z in front of the camera, the largest distance is -far with values increasing toward the - // camera. As such we can just use mesh_z as the distance - distance: mesh_z, - }); + let specialized_key = M::key(material); + + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &material_pipeline, + (mesh_key, specialized_key), + &mesh.layout, + ); + + // NOTE: row 2 of the inverse 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 = inverse_view_row_2.dot(mesh_uniform.transform.col(3)); + match alpha_mode { + AlphaMode::Opaque => { + opaque_phase.add(Opaque3d { + entity: *visible_entity, + draw_function: draw_opaque_pbr, + pipeline: pipeline_id, + // NOTE: Front-to-back ordering for opaque with ascending sort means near should have the + // lowest sort key and getting further away should increase. As we have + // -z in front of the camera, values in view space decrease away from the + // camera. Flipping the sign of mesh_z results in the correct front-to-back ordering + distance: -mesh_z, + }); + } + AlphaMode::Mask(_) => { + alpha_mask_phase.add(AlphaMask3d { + entity: *visible_entity, + draw_function: draw_alpha_mask_pbr, + pipeline: pipeline_id, + // NOTE: Front-to-back ordering for alpha mask with ascending sort means near should have the + // lowest sort key and getting further away should increase. As we have + // -z in front of the camera, values in view space decrease away from the + // camera. Flipping the sign of mesh_z results in the correct front-to-back ordering + distance: -mesh_z, + }); + } + AlphaMode::Blend => { + transparent_phase.add(Transparent3d { + entity: *visible_entity, + draw_function: draw_transparent_pbr, + pipeline: pipeline_id, + // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the + // lowest sort key and getting closer should increase. As we have + // -z in front of the camera, the largest distance is -far with values increasing toward the + // camera. As such we can just use mesh_z as the distance + distance: mesh_z, + }); + } } } } diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 6be68dfdd3e64..1e89180ec76f0 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -5,6 +5,7 @@ use bevy_math::Vec4; use bevy_reflect::TypeUuid; use bevy_render::{ color::Color, + mesh::MeshVertexBufferLayout, prelude::Shader, render_asset::{PrepareAssetError, RenderAsset, RenderAssets}, render_resource::{ @@ -338,7 +339,11 @@ impl SpecializedMaterial for StandardMaterial { } } - fn specialize(key: Self::Key, descriptor: &mut RenderPipelineDescriptor) { + fn specialize( + descriptor: &mut RenderPipelineDescriptor, + key: Self::Key, + _layout: &MeshVertexBufferLayout, + ) { if key.normal_map { descriptor .fragment diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index cc606057f2944..7b52a61c82bfc 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -14,7 +14,7 @@ use bevy_math::{const_vec3, Mat4, UVec3, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles}; use bevy_render::{ camera::{Camera, CameraProjection}, color::Color, - mesh::Mesh, + mesh::{Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::{ @@ -214,7 +214,6 @@ bitflags::bitflags! { #[repr(transparent)] pub struct ShadowPipelineKey: u32 { const NONE = 0; - const VERTEX_TANGENTS = (1 << 0); const PRIMITIVE_TOPOLOGY_RESERVED_BITS = ShadowPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << ShadowPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } } @@ -244,76 +243,24 @@ impl ShadowPipelineKey { } } -impl SpecializedPipeline for ShadowPipeline { +impl SpecializedMeshPipeline for ShadowPipeline { type Key = ShadowPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let (vertex_array_stride, vertex_attributes) = - if key.contains(ShadowPipelineKey::VERTEX_TANGENTS) { - ( - 48, - vec![ - // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 12, - shader_location: 0, - }, - // Normal - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 1, - }, - // Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 40, - shader_location: 2, - }, - // Tangent - VertexAttribute { - format: VertexFormat::Float32x4, - offset: 24, - shader_location: 3, - }, - ], - ) - } else { - ( - 32, - vec![ - // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 12, - shader_location: 0, - }, - // Normal - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 1, - }, - // Uv - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 24, - shader_location: 2, - }, - ], - ) - }; + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor { + let vertex_buffer_layout = layout + .get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)]) + .expect("Mesh is missing a vertex attribute"); + RenderPipelineDescriptor { vertex: VertexState { shader: SHADOW_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: vec![], - buffers: vec![VertexBufferLayout { - array_stride: vertex_array_stride, - step_mode: VertexStepMode::Vertex, - attributes: vertex_attributes, - }], + buffers: vec![vertex_buffer_layout.clone()], }, fragment: None, layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]), @@ -1090,7 +1037,7 @@ pub fn queue_shadows( shadow_pipeline: Res, casting_meshes: Query<&Handle, Without>, render_meshes: Res>, - mut pipelines: ResMut>, + mut pipelines: ResMut>, mut pipeline_cache: ResMut, view_lights: Query<&ViewLightEntities>, mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase)>, @@ -1120,23 +1067,24 @@ pub fn queue_shadows( // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued for entity in visible_entities.iter().copied() { - let mut key = ShadowPipelineKey::empty(); if let Ok(mesh_handle) = casting_meshes.get(entity) { if let Some(mesh) = render_meshes.get(mesh_handle) { - if mesh.has_tangents { - key |= ShadowPipelineKey::VERTEX_TANGENTS; - } - key |= ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology); + let key = + ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &shadow_pipeline, + key, + &mesh.layout, + ); + + shadow_phase.add(Shadow { + draw_function: draw_shadow_mesh, + pipeline: pipeline_id, + entity, + distance: 0.0, // TODO: sort back-to-front + }); } - let pipeline_id = - pipelines.specialize(&mut pipeline_cache, &shadow_pipeline, key); - - shadow_phase.add(Shadow { - draw_function: draw_shadow_mesh, - pipeline: pipeline_id, - entity, - distance: 0.0, // TODO: sort back-to-front - }); } } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3e8965aa28ee1..226f86407c132 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ use bevy_math::{Mat4, Size}; use bevy_reflect::TypeUuid; use bevy_render::{ - mesh::{GpuBufferInfo, Mesh}, + mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, @@ -368,8 +368,7 @@ bitflags::bitflags! { /// MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA. pub struct MeshPipelineKey: u32 { const NONE = 0; - const VERTEX_TANGENTS = (1 << 0); - const TRANSPARENT_MAIN_PASS = (1 << 1); + const TRANSPARENT_MAIN_PASS = (1 << 0); 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; } @@ -411,71 +410,30 @@ impl MeshPipelineKey { } } -impl SpecializedPipeline for MeshPipeline { +impl SpecializedMeshPipeline for MeshPipeline { type Key = MeshPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let (vertex_array_stride, vertex_attributes) = - if key.contains(MeshPipelineKey::VERTEX_TANGENTS) { - ( - 48, - vec![ - // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 12, - shader_location: 0, - }, - // Normal - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 1, - }, - // Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 40, - shader_location: 2, - }, - // Tangent - VertexAttribute { - format: VertexFormat::Float32x4, - offset: 24, - shader_location: 3, - }, - ], - ) - } else { - ( - 32, - vec![ - // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 12, - shader_location: 0, - }, - // Normal - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 1, - }, - // Uv - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 24, - shader_location: 2, - }, - ], - ) - }; + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor { + let mut vertex_attributes = vec![ + Mesh::ATTRIBUTE_POSITION.at_shader_location(0), + Mesh::ATTRIBUTE_NORMAL.at_shader_location(1), + Mesh::ATTRIBUTE_UV_0.at_shader_location(2), + ]; + let mut shader_defs = Vec::new(); - if key.contains(MeshPipelineKey::VERTEX_TANGENTS) { + if layout.contains(Mesh::ATTRIBUTE_TANGENT) { shader_defs.push(String::from("VERTEX_TANGENTS")); + vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } + let vertex_buffer_layout = layout + .get_layout(&vertex_attributes) + .expect("Mesh is missing a vertex attribute"); + let (label, blend, depth_write_enabled); if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) { label = "transparent_mesh_pipeline".into(); @@ -500,11 +458,7 @@ impl SpecializedPipeline for MeshPipeline { shader: MESH_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![VertexBufferLayout { - array_stride: vertex_array_stride, - step_mode: VertexStepMode::Vertex, - attributes: vertex_attributes, - }], + buffers: vec![vertex_buffer_layout.clone()], }, fragment: Some(FragmentState { shader: MESH_SHADER_HANDLE.typed::(), diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 9f1308179e518..bda39c0b1b283 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -5,12 +5,15 @@ use bevy_asset::{Assets, Handle, HandleUntyped}; use bevy_core_pipeline::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::{Reflect, TypeUuid}; -use bevy_render::render_resource::PolygonMode; +use bevy_render::mesh::MeshVertexBufferLayout; +use bevy_render::render_resource::{ + PolygonMode, SpecializedMeshPipeline, SpecializedMeshPipelines, +}; use bevy_render::{ mesh::Mesh, render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, - render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines}, + render_resource::{RenderPipelineCache, Shader}, view::{ExtractedView, Msaa}, RenderApp, RenderStage, }; @@ -35,7 +38,7 @@ impl Plugin for WireframePlugin { render_app .add_render_command::() .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_wireframes) .add_system_to_stage(RenderStage::Extract, extract_wireframe_config) .add_system_to_stage(RenderStage::Queue, queue_wireframes); @@ -79,11 +82,15 @@ impl FromWorld for WireframePipeline { } } -impl SpecializedPipeline for WireframePipeline { +impl SpecializedMeshPipeline for WireframePipeline { type Key = MeshPipelineKey; - fn specialize(&self, key: Self::Key) -> bevy_render::render_resource::RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key); + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> bevy_render::render_resource::RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(key, layout); descriptor.vertex.shader = self.shader.clone_weak(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak(); descriptor.primitive.polygon_mode = PolygonMode::Line; @@ -93,13 +100,14 @@ impl SpecializedPipeline for WireframePipeline { } #[allow(clippy::too_many_arguments)] +#[allow(clippy::type_complexity)] fn queue_wireframes( opaque_3d_draw_functions: Res>, render_meshes: Res>, wireframe_config: Res, wireframe_pipeline: Res, mut pipeline_cache: ResMut, - mut specialized_pipelines: ResMut>, + mut specialized_pipelines: ResMut>, msaa: Res, mut material_meshes: QuerySet<( QueryState<(Entity, &Handle, &MeshUniform)>, @@ -111,7 +119,7 @@ fn queue_wireframes( .read() .get_id::() .unwrap(); - let key = MeshPipelineKey::from_msaa_samples(msaa.samples); + let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); for (view, mut transparent_phase) in views.iter_mut() { let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); @@ -119,14 +127,15 @@ fn queue_wireframes( let add_render_phase = |(entity, mesh_handle, mesh_uniform): (Entity, &Handle, &MeshUniform)| { if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = - key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let key = msaa_key + | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); transparent_phase.add(Opaque3d { entity, pipeline: specialized_pipelines.specialize( &mut pipeline_cache, &wireframe_pipeline, key, + &mesh.layout, ), draw_function: draw_custom, distance: view_row_2.dot(mesh_uniform.transform.col(3)), diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 317ce49a30902..1867577d48d43 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -3,17 +3,18 @@ mod conversions; use crate::{ primitives::Aabb, render_asset::{PrepareAssetError, RenderAsset}, - render_resource::Buffer, + render_resource::{Buffer, VertexBufferLayout}, renderer::RenderDevice, }; use bevy_core::cast_slice; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::*; use bevy_reflect::TypeUuid; -use bevy_utils::EnumVariantMeta; -use std::{borrow::Cow, collections::BTreeMap}; +use bevy_utils::{EnumVariantMeta, Hashed}; +use std::{collections::BTreeMap, hash::Hash}; use wgpu::{ - util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexFormat, + util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexAttribute, + VertexFormat, VertexStepMode, }; pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; @@ -25,10 +26,10 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; pub struct Mesh { primitive_topology: PrimitiveTopology, /// `std::collections::BTreeMap` with all defined vertex attributes (Positions, Normals, ...) - /// for this mesh. Attribute name maps to attribute values. + /// for this mesh. Attribute ids to attribute values. /// Uses a BTreeMap because, unlike HashMap, it has a defined iteration order, /// which allows easy stable VertexBuffers (i.e. same buffer order) - attributes: BTreeMap, VertexAttributeValues>, + attributes: BTreeMap, indices: Option, } @@ -50,23 +51,33 @@ pub struct Mesh { /// } /// ``` impl Mesh { - /// Per vertex coloring. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_COLOR: &'static str = "Vertex_Color"; + /// Where the vertex is located in space. Use in conjunction with [`Mesh::set_attribute`] + pub const ATTRIBUTE_POSITION: MeshVertexAttribute = + MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3); + /// The direction the vertex normal is facing in. /// Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_NORMAL: &'static str = "Vertex_Normal"; - /// The direction of the vertex tangent. Used for normal mapping - pub const ATTRIBUTE_TANGENT: &'static str = "Vertex_Tangent"; + pub const ATTRIBUTE_NORMAL: MeshVertexAttribute = + MeshVertexAttribute::new("Vertex_Normal", 1, VertexFormat::Float32x3); - /// Where the vertex is located in space. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_POSITION: &'static str = "Vertex_Position"; /// Texture coordinates for the vertex. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_UV_0: &'static str = "Vertex_Uv"; + pub const ATTRIBUTE_UV_0: MeshVertexAttribute = + MeshVertexAttribute::new("Vertex_Uv", 2, VertexFormat::Float32x2); + + /// The direction of the vertex tangent. Used for normal mapping + pub const ATTRIBUTE_TANGENT: MeshVertexAttribute = + MeshVertexAttribute::new("Vertex_Tangent", 3, VertexFormat::Float32x4); + + /// Per vertex coloring. Use in conjunction with [`Mesh::set_attribute`] + pub const ATTRIBUTE_COLOR: MeshVertexAttribute = + MeshVertexAttribute::new("Vertex_Color", 4, VertexFormat::Uint32); /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_JOINT_WEIGHT: &'static str = "Vertex_JointWeight"; + pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute = + MeshVertexAttribute::new("Vertex_JointWeight", 5, VertexFormat::Float32x4); /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_JOINT_INDEX: &'static str = "Vertex_JointIndex"; + pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute = + MeshVertexAttribute::new("Vertex_JointIndex", 6, VertexFormat::Uint32); /// Construct a new mesh. You need to provide a [`PrimitiveTopology`] so that the /// renderer knows how to treat the vertex data. Most of the time this will be @@ -86,41 +97,62 @@ impl Mesh { /// Sets the data for a vertex attribute (position, normal etc.). The name will /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. - pub fn set_attribute( + #[inline] + pub fn insert_attribute( &mut self, - name: impl Into>, + attribute: MeshVertexAttribute, values: impl Into, ) { - let values: VertexAttributeValues = values.into(); - self.attributes.insert(name.into(), values); + self.attributes.insert( + attribute.id, + MeshAttributeData { + attribute, + values: values.into(), + }, + ); + } + + #[inline] + pub fn contains_attribute(&self, id: impl Into) -> bool { + self.attributes.contains_key(&id.into()) } /// Retrieves the data currently set to the vertex attribute with the specified `name`. - pub fn attribute(&self, name: impl Into>) -> Option<&VertexAttributeValues> { - self.attributes.get(&name.into()) + #[inline] + pub fn attribute( + &self, + id: impl Into, + ) -> Option<&VertexAttributeValues> { + self.attributes.get(&id.into()).map(|data| &data.values) } /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. + #[inline] pub fn attribute_mut( &mut self, - name: impl Into>, + id: impl Into, ) -> Option<&mut VertexAttributeValues> { - self.attributes.get_mut(&name.into()) + self.attributes + .get_mut(&id.into()) + .map(|data| &mut data.values) } /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants /// that use triangles. + #[inline] pub fn set_indices(&mut self, indices: Option) { self.indices = indices; } /// Retrieves the vertex `indices` of the mesh. + #[inline] pub fn indices(&self) -> Option<&Indices> { self.indices.as_ref() } /// Retrieves the vertex `indices` of the mesh mutably. + #[inline] pub fn indices_mut(&mut self) -> Option<&mut Indices> { self.indices.as_mut() } @@ -134,27 +166,32 @@ impl Mesh { }) } - // pub fn get_vertex_buffer_layout(&self) -> VertexBufferLayout { - // let mut attributes = Vec::new(); - // let mut accumulated_offset = 0; - // for (attribute_name, attribute_values) in self.attributes.iter() { - // let vertex_format = VertexFormat::from(attribute_values); - // attributes.push(VertexAttribute { - // name: attribute_name.clone(), - // offset: accumulated_offset, - // format: vertex_format, - // shader_location: 0, - // }); - // accumulated_offset += vertex_format.get_size(); - // } - - // VertexBufferLayout { - // name: Default::default(), - // stride: accumulated_offset, - // step_mode: InputStepMode::Vertex, - // attributes, - // } - // } + /// For a given `descriptor` returns a [`VertexBufferLayout`] compatible with this mesh. If this + /// mesh is not compatible with the given `descriptor` (ex: it is missing vertex attributes), [`None`] will + /// be returned. + pub fn get_mesh_vertex_buffer_layout(&self) -> MeshVertexBufferLayout { + let mut attributes = Vec::with_capacity(self.attributes.len()); + let mut attribute_ids = Vec::with_capacity(self.attributes.len()); + let mut accumulated_offset = 0; + for (index, data) in self.attributes.values().enumerate() { + attribute_ids.push(data.attribute.id); + attributes.push(VertexAttribute { + offset: accumulated_offset, + format: data.attribute.format, + shader_location: index as u32, + }); + accumulated_offset += data.attribute.format.get_size(); + } + + MeshVertexBufferLayout::new(InnerMeshVertexBufferLayout { + layout: VertexBufferLayout { + array_stride: accumulated_offset, + step_mode: VertexStepMode::Vertex, + attributes, + }, + attribute_ids, + }) + } /// Counts all vertices of the mesh. /// @@ -162,11 +199,11 @@ impl Mesh { /// Panics if the attributes have different vertex counts. pub fn count_vertices(&self) -> usize { let mut vertex_count: Option = None; - for (attribute_name, attribute_data) in &self.attributes { - let attribute_len = attribute_data.len(); + for (attribute_id, attribute_data) in self.attributes.iter() { + let attribute_len = attribute_data.values.len(); if let Some(previous_vertex_count) = vertex_count { assert_eq!(previous_vertex_count, attribute_len, - "Attribute {} has a different vertex count ({}) than other attributes ({}) in this mesh.", attribute_name, attribute_len, previous_vertex_count); + "{:?} has a different vertex count ({}) than other attributes ({}) in this mesh.", attribute_id, attribute_len, previous_vertex_count); } vertex_count = Some(attribute_len); } @@ -182,8 +219,8 @@ impl Mesh { /// Panics if the attributes have different vertex counts. pub fn get_vertex_buffer_data(&self) -> Vec { let mut vertex_size = 0; - for attribute_values in self.attributes.values() { - let vertex_format = VertexFormat::from(attribute_values); + for attribute_data in self.attributes.values() { + let vertex_format = attribute_data.attribute.format; vertex_size += vertex_format.get_size() as usize; } @@ -191,10 +228,9 @@ impl Mesh { let mut attributes_interleaved_buffer = vec![0; vertex_count * vertex_size]; // bundle into interleaved buffers let mut attribute_offset = 0; - for attribute_values in self.attributes.values() { - let vertex_format = VertexFormat::from(attribute_values); - let attribute_size = vertex_format.get_size() as usize; - let attributes_bytes = attribute_values.get_bytes(); + for attribute_data in self.attributes.values() { + let attribute_size = attribute_data.attribute.format.get_size() as usize; + let attributes_bytes = attribute_data.values.get_bytes(); for (vertex_index, attribute_bytes) in attributes_bytes.chunks_exact(attribute_size).enumerate() { @@ -232,7 +268,7 @@ impl Mesh { }; for attributes in self.attributes.values_mut() { let indices = indices.iter(); - match attributes { + match &mut attributes.values { VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices), @@ -285,7 +321,7 @@ impl Mesh { .flat_map(|normal| [normal; 3]) .collect(); - self.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); } /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space @@ -314,6 +350,118 @@ impl Mesh { } } +#[derive(Debug, Clone)] +pub struct MeshVertexAttribute { + /// The friendly name of the vertex attribute + pub name: &'static str, + + /// The _unique_ id of the vertex attribute. This will also determine sort ordering + /// when generating vertex buffers. Built-in / standard attributes will use "close to zero" + /// indices. When in doubt, use a random / very large usize to avoid conflicts. + pub id: MeshVertexAttributeId, + + /// The format of the vertex attribute. + pub format: VertexFormat, +} + +impl MeshVertexAttribute { + pub const fn new(name: &'static str, id: usize, format: VertexFormat) -> Self { + Self { + name, + id: MeshVertexAttributeId(id), + format, + } + } + + pub const fn at_shader_location(&self, shader_location: u32) -> VertexAttributeDescriptor { + VertexAttributeDescriptor::new(shader_location, self.id) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct MeshVertexAttributeId(usize); + +impl From for MeshVertexAttributeId { + fn from(attribute: MeshVertexAttribute) -> Self { + attribute.id + } +} + +pub type MeshVertexBufferLayout = Hashed; + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct InnerMeshVertexBufferLayout { + attribute_ids: Vec, + layout: VertexBufferLayout, +} + +impl InnerMeshVertexBufferLayout { + #[inline] + pub fn contains(&self, attribute_id: impl Into) -> bool { + self.attribute_ids.contains(&attribute_id.into()) + } + + // TODO: maybe these should be hidden to ensure specializers can only use this type to generate VertexBufferLayouts? + #[inline] + pub fn attribute_ids(&self) -> &[MeshVertexAttributeId] { + &self.attribute_ids + } + + #[inline] + pub fn layout(&self) -> &VertexBufferLayout { + &self.layout + } + + pub fn get_layout( + &self, + attribute_descriptors: &[VertexAttributeDescriptor], + ) -> Option { + let mut attributes = Vec::with_capacity(attribute_descriptors.len()); + for attribute_descriptor in attribute_descriptors.iter() { + if let Some(index) = self + .attribute_ids + .iter() + .position(|id| *id == attribute_descriptor.id) + { + let layout_attribute = &self.layout.attributes[index]; + attributes.push(VertexAttribute { + format: layout_attribute.format, + offset: layout_attribute.offset, + shader_location: attribute_descriptor.shader_location, + }) + } else { + return None; + } + } + + Some(VertexBufferLayout { + array_stride: self.layout.array_stride, + step_mode: self.layout.step_mode, + attributes, + }) + } +} + +pub struct VertexAttributeDescriptor { + pub shader_location: u32, + pub id: MeshVertexAttributeId, +} + +impl VertexAttributeDescriptor { + pub const fn new(shader_location: u32, id: MeshVertexAttributeId) -> Self { + Self { + shader_location, + id, + } + } +} + +#[derive(Debug, Clone)] +struct MeshAttributeData { + attribute: MeshVertexAttribute, + values: VertexAttributeValues, +} + const VEC3_MIN: Vec3 = const_vec3!([std::f32::MIN, std::f32::MIN, std::f32::MIN]); const VEC3_MAX: Vec3 = const_vec3!([std::f32::MAX, std::f32::MAX, std::f32::MAX]); @@ -521,7 +669,6 @@ impl From<&VertexAttributeValues> for VertexFormat { } } } - /// An array of indices into the [`VertexAttributeValues`] for a mesh. /// /// It describes the order in which the vertex attributes should be joined into faces. @@ -590,8 +737,8 @@ pub struct GpuMesh { /// Contains all attribute data for each vertex. pub vertex_buffer: Buffer, pub buffer_info: GpuBufferInfo, - pub has_tangents: bool, pub primitive_topology: PrimitiveTopology, + pub layout: MeshVertexBufferLayout, } /// The index/vertex buffer info of a [`GpuMesh`]. @@ -645,11 +792,13 @@ impl RenderAsset for Mesh { }, ); + let mesh_vertex_buffer_layout = mesh.get_mesh_vertex_buffer_layout(); + Ok(GpuMesh { vertex_buffer, buffer_info, - has_tangents: mesh.attributes.contains_key(Mesh::ATTRIBUTE_TANGENT), primitive_topology: mesh.primitive_topology(), + layout: mesh_vertex_buffer_layout, }) } } diff --git a/crates/bevy_render/src/mesh/shape/capsule.rs b/crates/bevy_render/src/mesh/shape/capsule.rs index 7b6d268a9a9a6..5459e8d47fe10 100644 --- a/crates/bevy_render/src/mesh/shape/capsule.rs +++ b/crates/bevy_render/src/mesh/shape/capsule.rs @@ -370,9 +370,9 @@ impl From for Mesh { assert_eq!(tris.len(), fs_len); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vs); - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, vns); - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, vts); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vs); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vns); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vts); mesh.set_indices(Some(Indices::U32(tris))); mesh } diff --git a/crates/bevy_render/src/mesh/shape/icosphere.rs b/crates/bevy_render/src/mesh/shape/icosphere.rs index ff2068469ec51..7b0a31962205d 100644 --- a/crates/bevy_render/src/mesh/shape/icosphere.rs +++ b/crates/bevy_render/src/mesh/shape/icosphere.rs @@ -95,9 +95,9 @@ impl From for Mesh { let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.set_indices(Some(indices)); - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, points); - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, points); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh } } diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index 9f128d2ac4c43..31f9f8b5d4bd2 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -112,9 +112,9 @@ impl From for Mesh { ]); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions); - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh.set_indices(Some(indices)); mesh } @@ -171,9 +171,9 @@ impl From for Mesh { let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.set_indices(Some(indices)); - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions); - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh } } @@ -215,9 +215,9 @@ impl From for Mesh { let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.set_indices(Some(indices)); - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions); - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh } } diff --git a/crates/bevy_render/src/mesh/shape/torus.rs b/crates/bevy_render/src/mesh/shape/torus.rs index abe63e00c5c27..4fdc9b13181d5 100644 --- a/crates/bevy_render/src/mesh/shape/torus.rs +++ b/crates/bevy_render/src/mesh/shape/torus.rs @@ -90,9 +90,9 @@ impl From for Mesh { let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.set_indices(Some(Indices::U32(indices))); - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions); - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh } } diff --git a/crates/bevy_render/src/mesh/shape/uvsphere.rs b/crates/bevy_render/src/mesh/shape/uvsphere.rs index 571c3123d9aa1..ccce754e564f2 100644 --- a/crates/bevy_render/src/mesh/shape/uvsphere.rs +++ b/crates/bevy_render/src/mesh/shape/uvsphere.rs @@ -82,9 +82,9 @@ impl From for Mesh { let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.set_indices(Some(Indices::U32(indices))); - mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vertices); - mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); mesh } } diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index d3bd820053fdc..b2c37825ffddc 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -4,7 +4,7 @@ use bevy_reflect::Uuid; use std::{borrow::Cow, ops::Deref, sync::Arc}; use wgpu::{ BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, - VertexAttribute, VertexStepMode, + VertexAttribute, VertexFormat, VertexStepMode, }; /// A [`RenderPipeline`] identifier. @@ -118,7 +118,7 @@ pub struct VertexState { } /// Describes how the vertex buffer is interpreted. -#[derive(Clone, Debug, Hash, Eq, PartialEq)] +#[derive(Default, Clone, Debug, Hash, Eq, PartialEq)] pub struct VertexBufferLayout { /// The stride, in bytes, between elements of this buffer. pub array_stride: BufferAddress, @@ -128,6 +128,34 @@ pub struct VertexBufferLayout { pub attributes: Vec, } +impl VertexBufferLayout { + /// Creates a new densely packed [`VertexBufferLayout`] from an iterator of vertex formats. + /// Iteration order determines the `shader_location` and `offset` of the VertexAttributes. + /// The first iterated item will have a `shader_location` and `offset` of zero. + /// The `array_stride` is the sum of the size of the iterated VertexFormats (in bytes). + pub fn from_vertex_formats>( + step_mode: VertexStepMode, + vertex_formats: T, + ) -> Self { + let mut array_stride = 0; + let mut attributes = Vec::new(); + for (shader_location, format) in vertex_formats.into_iter().enumerate() { + attributes.push(VertexAttribute { + format, + offset: array_stride, + shader_location: shader_location as u32, + }); + array_stride += format.size(); + } + + VertexBufferLayout { + array_stride, + step_mode, + attributes, + } + } +} + /// Describes the fragment process in a render pipeline. #[derive(Clone, Debug)] pub struct FragmentState { diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 77ed782eb61cb..603f62c2b4d56 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -13,7 +13,7 @@ use bevy_ecs::system::{Res, ResMut}; use bevy_utils::{tracing::error, Entry, HashMap, HashSet}; use std::{hash::Hash, ops::Deref, sync::Arc}; use thiserror::Error; -use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout}; +use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout as RawVertexBufferLayout}; use super::ProcessedShader; @@ -345,7 +345,7 @@ impl RenderPipelineCache { .vertex .buffers .iter() - .map(|layout| VertexBufferLayout { + .map(|layout| RawVertexBufferLayout { array_stride: layout.array_stride, attributes: &layout.attributes, step_mode: layout.step_mode, diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index a5cb472aa48f8..d187e199ccd29 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -1,5 +1,12 @@ -use crate::render_resource::{CachedPipelineId, RenderPipelineCache, RenderPipelineDescriptor}; -use bevy_utils::HashMap; +use crate::{ + mesh::{InnerMeshVertexBufferLayout, MeshVertexBufferLayout}, + render_resource::{ + CachedPipelineId, RenderPipelineCache, RenderPipelineDescriptor, VertexBufferLayout, + }, +}; +use bevy_utils::{ + hashbrown::hash_map::RawEntryMut, Entry, HashMap, Hashed, PreHashMap, PreHashMapExt, +}; use std::hash::Hash; pub struct SpecializedPipelines { @@ -32,3 +39,62 @@ pub trait SpecializedPipeline { type Key: Clone + Hash + PartialEq + Eq; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor; } + +pub struct SpecializedMeshPipelines { + mesh_layout_cache: PreHashMap>, + vertex_layout_cache: HashMap>, +} + +impl Default for SpecializedMeshPipelines { + fn default() -> Self { + Self { + mesh_layout_cache: Default::default(), + vertex_layout_cache: Default::default(), + } + } +} + +impl SpecializedMeshPipelines { + #[inline] + pub fn specialize( + &mut self, + cache: &mut RenderPipelineCache, + specialize_pipeline: &S, + key: S::Key, + layout: &MeshVertexBufferLayout, + ) -> CachedPipelineId { + let map = self + .mesh_layout_cache + .get_or_insert_with(layout, Default::default); + *map.entry(key.clone()).or_insert_with(|| { + let descriptor = specialize_pipeline.specialize(key.clone(), layout); + // Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout + // We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them + let layout_map = match self + .vertex_layout_cache + .raw_entry_mut() + .from_key(&descriptor.vertex.buffers[0]) + { + RawEntryMut::Occupied(entry) => entry.into_mut(), + RawEntryMut::Vacant(entry) => { + entry + .insert(descriptor.vertex.buffers[0].clone(), Default::default()) + .1 + } + }; + match layout_map.entry(key) { + Entry::Occupied(entry) => *entry.into_mut(), + Entry::Vacant(entry) => *entry.insert(cache.queue(descriptor)), + } + }) + } +} + +pub trait SpecializedMeshPipeline { + type Key: Clone + Hash + PartialEq + Eq; + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor; +} diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 0e3ceaae55cc0..c91bfe676d7d9 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -12,7 +12,7 @@ use bevy_ecs::{ world::FromWorld, }; use bevy_render::{ - mesh::Mesh, + mesh::{Mesh, MeshVertexBufferLayout}, render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}, render_component::ExtractComponentPlugin, render_phase::{ @@ -21,7 +21,7 @@ use bevy_render::{ }, render_resource::{ BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, - SpecializedPipeline, SpecializedPipelines, + SpecializedMeshPipeline, SpecializedMeshPipelines, }, renderer::RenderDevice, view::{ComputedVisibility, Msaa, Visibility, VisibleEntities}, @@ -78,7 +78,12 @@ impl SpecializedMaterial2d for M { fn key(_material: &::PreparedAsset) -> Self::Key {} #[inline] - fn specialize(_key: Self::Key, _descriptor: &mut RenderPipelineDescriptor) {} + fn specialize( + _key: Self::Key, + _descriptor: &mut RenderPipelineDescriptor, + _layout: &MeshVertexBufferLayout, + ) { + } #[inline] fn bind_group(material: &::PreparedAsset) -> &BindGroup { @@ -122,7 +127,11 @@ pub trait SpecializedMaterial2d: Asset + RenderAsset { fn key(material: &::PreparedAsset) -> Self::Key; /// Specializes the given `descriptor` according to the given `key`. - fn specialize(key: Self::Key, descriptor: &mut RenderPipelineDescriptor); + fn specialize( + key: Self::Key, + descriptor: &mut RenderPipelineDescriptor, + layout: &MeshVertexBufferLayout, + ); /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial2d::bind_group_layout`]. fn bind_group(material: &::PreparedAsset) -> &BindGroup; @@ -172,7 +181,7 @@ impl Plugin for Material2dPlugin { render_app .add_render_command::>() .init_resource::>() - .init_resource::>>() + .init_resource::>>() .add_system_to_stage(RenderStage::Queue, queue_material2d_meshes::); } } @@ -186,11 +195,15 @@ pub struct Material2dPipeline { marker: PhantomData, } -impl SpecializedPipeline for Material2dPipeline { +impl SpecializedMeshPipeline for Material2dPipeline { type Key = (Mesh2dPipelineKey, M::Key); - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh2d_pipeline.specialize(key.0); + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor { + let mut descriptor = self.mesh2d_pipeline.specialize(key.0, layout); if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -204,7 +217,7 @@ impl SpecializedPipeline for Material2dPipeline { self.mesh2d_pipeline.mesh_layout.clone(), ]); - M::specialize(key.1, &mut descriptor); + M::specialize(key.1, &mut descriptor, layout); descriptor } } @@ -259,7 +272,7 @@ impl EntityRenderCommand pub fn queue_material2d_meshes( transparent_draw_functions: Res>, material2d_pipeline: Res>, - mut pipelines: ResMut>>, + mut pipelines: ResMut>>, mut pipeline_cache: ResMut, msaa: Res, render_meshes: Res>, @@ -276,42 +289,39 @@ pub fn queue_material2d_meshes( .get_id::>() .unwrap(); - let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); + let msaa_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); for visible_entity in &visible_entities.entities { if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = material2d_meshes.get(*visible_entity) { if let Some(material2d) = render_materials.get(material2d_handle) { - let mut mesh2d_key = mesh_key; if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { - if mesh.has_tangents { - mesh2d_key |= Mesh2dPipelineKey::VERTEX_TANGENTS; - } - mesh2d_key |= - Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); + let mesh2d_key = msaa_key + | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); + + let specialized_key = M::key(material2d); + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &material2d_pipeline, + (mesh2d_key, specialized_key), + &mesh.layout, + ); + + let mesh_z = mesh2d_uniform.transform.w_axis.z; + transparent_phase.add(Transparent2d { + entity: *visible_entity, + draw_function: draw_transparent_pbr, + pipeline: pipeline_id, + // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the + // lowest sort key and getting closer should increase. As we have + // -z in front of the camera, the largest distance is -far with values increasing toward the + // camera. As such we can just use mesh_z as the distance + sort_key: FloatOrd(mesh_z), + // This material is not batched + batch_range: None, + }); } - - let specialized_key = M::key(material2d); - let pipeline_id = pipelines.specialize( - &mut pipeline_cache, - &material2d_pipeline, - (mesh2d_key, specialized_key), - ); - - let mesh_z = mesh2d_uniform.transform.w_axis.z; - transparent_phase.add(Transparent2d { - entity: *visible_entity, - draw_function: draw_transparent_pbr, - pipeline: pipeline_id, - // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the - // lowest sort key and getting closer should increase. As we have - // -z in front of the camera, the largest distance is -far with values increasing toward the - // camera. As such we can just use mesh_z as the distance - sort_key: FloatOrd(mesh_z), - // This material is not batched - batch_range: None, - }); } } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 7468990b47ea8..a87cd4f36570c 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -7,7 +7,7 @@ use bevy_ecs::{ use bevy_math::{Mat4, Size}; use bevy_reflect::TypeUuid; use bevy_render::{ - mesh::{GpuBufferInfo, Mesh}, + mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, @@ -64,7 +64,7 @@ impl Plugin for Mesh2dRenderPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_mesh2d) .add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group) .add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups); @@ -234,7 +234,6 @@ bitflags::bitflags! { // FIXME: make normals optional? pub struct Mesh2dPipelineKey: u32 { const NONE = 0; - const VERTEX_TANGENTS = (1 << 0); const MSAA_RESERVED_BITS = Mesh2dPipelineKey::MSAA_MASK_BITS << Mesh2dPipelineKey::MSAA_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Mesh2dPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << Mesh2dPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } @@ -276,84 +275,39 @@ impl Mesh2dPipelineKey { } } -impl SpecializedPipeline for Mesh2dPipeline { +impl SpecializedMeshPipeline for Mesh2dPipeline { type Key = Mesh2dPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let (vertex_array_stride, vertex_attributes) = - if key.contains(Mesh2dPipelineKey::VERTEX_TANGENTS) { - ( - 48, - vec![ - // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 12, - shader_location: 0, - }, - // Normal - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 1, - }, - // Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 40, - shader_location: 2, - }, - // Tangent - VertexAttribute { - format: VertexFormat::Float32x4, - offset: 24, - shader_location: 3, - }, - ], - ) - } else { - ( - 32, - vec![ - // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 12, - shader_location: 0, - }, - // Normal - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 1, - }, - // Uv - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 24, - shader_location: 2, - }, - ], - ) - }; + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor { + let mut vertex_attributes = vec![ + Mesh::ATTRIBUTE_POSITION.at_shader_location(0), + Mesh::ATTRIBUTE_NORMAL.at_shader_location(1), + Mesh::ATTRIBUTE_UV_0.at_shader_location(2), + ]; + let mut shader_defs = Vec::new(); - if key.contains(Mesh2dPipelineKey::VERTEX_TANGENTS) { + if layout.contains(Mesh::ATTRIBUTE_TANGENT) { shader_defs.push(String::from("VERTEX_TANGENTS")); + vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } #[cfg(feature = "webgl")] shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT")); + let vertex_buffer_layout = layout + .get_layout(&vertex_attributes) + .expect("Mesh is missing a vertex attribute"); + RenderPipelineDescriptor { vertex: VertexState { shader: MESH2D_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![VertexBufferLayout { - array_stride: vertex_array_stride, - step_mode: VertexStepMode::Vertex, - attributes: vertex_attributes, - }], + buffers: vec![vertex_buffer_layout], }, fragment: Some(FragmentState { shader: MESH2D_SHADER_HANDLE.typed::(), diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 4eceaf2bdfc87..e9f00bb710332 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -113,31 +113,24 @@ impl SpecializedPipeline for SpritePipeline { type Key = SpritePipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut vertex_buffer_layout = VertexBufferLayout { - array_stride: 20, - step_mode: VertexStepMode::Vertex, - attributes: vec![ - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 0, - }, - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 12, - shader_location: 1, - }, - ], - }; + let mut formats = vec![ + // position + VertexFormat::Float32x3, + // uv + VertexFormat::Float32x2, + ]; + + if key.contains(SpritePipelineKey::COLORED) { + // color + formats.push(VertexFormat::Uint32); + } + + let vertex_layout = + VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats); + let mut shader_defs = Vec::new(); if key.contains(SpritePipelineKey::COLORED) { shader_defs.push("COLORED".to_string()); - vertex_buffer_layout.attributes.push(VertexAttribute { - format: VertexFormat::Uint32, - offset: 20, - shader_location: 2, - }); - vertex_buffer_layout.array_stride += 4; } RenderPipelineDescriptor { @@ -145,7 +138,7 @@ impl SpecializedPipeline for SpritePipeline { shader: SPRITE_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![vertex_buffer_layout], + buffers: vec![vertex_layout.clone()], }, fragment: Some(FragmentState { shader: SPRITE_SHADER_HANDLE.typed::(), diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 4538dc922b131..082b88ed4370d 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -66,29 +66,17 @@ impl SpecializedPipeline for UiPipeline { type Key = UiPipelineKey; /// FIXME: there are no specialization for now, should this be removed? fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { - let vertex_buffer_layout = VertexBufferLayout { - array_stride: 24, - step_mode: VertexStepMode::Vertex, - attributes: vec![ - // Position - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 0, - }, - // UV - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 12, - shader_location: 1, - }, - VertexAttribute { - format: VertexFormat::Uint32, - offset: 20, - shader_location: 2, - }, + let vertex_layout = VertexBufferLayout::from_vertex_formats( + VertexStepMode::Vertex, + vec![ + // position + VertexFormat::Float32x3, + // uv + VertexFormat::Float32x2, + // color + VertexFormat::Uint32, ], - }; + ); let shader_defs = Vec::new(); RenderPipelineDescriptor { @@ -96,7 +84,7 @@ impl SpecializedPipeline for UiPipeline { shader: super::UI_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![vertex_buffer_layout], + buffers: vec![vertex_layout.clone()], }, fragment: Some(FragmentState { shader: super::UI_SHADER_HANDLE.typed::(), diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 371645839ac08..6fec365bb617f 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -68,11 +68,11 @@ fn star( v_pos.push([r * a.cos(), r * a.sin(), 0.0]); } // Set the position attribute - star.set_attribute(Mesh::ATTRIBUTE_POSITION, v_pos); + star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos); // And a RGB color attribute as well let mut v_color = vec![[0.0, 0.0, 0.0, 1.0]]; v_color.extend_from_slice(&[[1.0, 1.0, 0.0, 1.0]; 10]); - star.set_attribute(Mesh::ATTRIBUTE_COLOR, v_color); + star.insert_attribute(Mesh::ATTRIBUTE_COLOR, v_color); // Now, we specify the indices of the vertex that are going to compose the // triangles in our star. Vertices in triangles have to be specified in CCW diff --git a/examples/shader/animate_shader.rs b/examples/shader/animate_shader.rs index 6e8f4b00af0fc..0fc09c6c2fbf5 100644 --- a/examples/shader/animate_shader.rs +++ b/examples/shader/animate_shader.rs @@ -7,6 +7,8 @@ use bevy::{ }, prelude::*, render::{ + mesh::MeshVertexBufferLayout, + render_asset::RenderAssets, render_phase::{ AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, @@ -66,7 +68,7 @@ impl Plugin for CustomMaterialPlugin { bind_group: None, }) .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_time) .add_system_to_stage(RenderStage::Extract, extract_custom_material) .add_system_to_stage(RenderStage::Prepare, prepare_time) @@ -94,9 +96,10 @@ fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, msaa: Res, - mut pipelines: ResMut>, + mut pipelines: ResMut>, mut pipeline_cache: ResMut, - material_meshes: Query<(Entity, &MeshUniform), (With>, With)>, + render_meshes: Res>, + material_meshes: Query<(Entity, &MeshUniform, &Handle), With>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) { let draw_custom = transparent_3d_draw_functions @@ -106,18 +109,21 @@ fn queue_custom( let key = MeshPipelineKey::from_msaa_samples(msaa.samples) | MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); - let pipeline = pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key); for (view, mut transparent_phase) in views.iter_mut() { let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); - for (entity, mesh_uniform) in material_meshes.iter() { - transparent_phase.add(Transparent3d { - entity, - pipeline, - draw_function: draw_custom, - distance: view_row_2.dot(mesh_uniform.transform.col(3)), - }); + for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() { + if let Some(mesh) = render_meshes.get(mesh_handle) { + let pipeline = + pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout); + transparent_phase.add(Transparent3d { + entity, + pipeline, + draw_function: draw_custom, + distance: view_row_2.dot(mesh_uniform.transform.col(3)), + }); + } } } } @@ -207,11 +213,15 @@ impl FromWorld for CustomPipeline { } } -impl SpecializedPipeline for CustomPipeline { +impl SpecializedMeshPipeline for CustomPipeline { type Key = MeshPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key); + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(key, layout); descriptor.vertex.shader = self.shader.clone(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); descriptor.layout = Some(vec![ diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index bec75411f0a3e..e7325d7eef12f 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -6,12 +6,13 @@ use bevy::{ }, prelude::*, render::{ + mesh::MeshVertexBufferLayout, render_asset::RenderAssets, render_component::{ExtractComponent, ExtractComponentPlugin}, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ - RenderPipelineCache, RenderPipelineDescriptor, SpecializedPipeline, - SpecializedPipelines, + RenderPipelineCache, RenderPipelineDescriptor, SpecializedMeshPipeline, + SpecializedMeshPipelines, }, view::ExtractedView, RenderApp, RenderStage, @@ -26,7 +27,7 @@ impl Plugin for IsRedPlugin { app.sub_app_mut(RenderApp) .add_render_command::() .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Queue, queue_custom); } } @@ -98,15 +99,19 @@ impl FromWorld for IsRedPipeline { } } -impl SpecializedPipeline for IsRedPipeline { +impl SpecializedMeshPipeline for IsRedPipeline { type Key = (IsRed, MeshPipelineKey); - fn specialize(&self, (is_red, pbr_pipeline_key): Self::Key) -> RenderPipelineDescriptor { + fn specialize( + &self, + (is_red, pbr_pipeline_key): Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor { let mut shader_defs = Vec::new(); if is_red.0 { shader_defs.push("IS_RED".to_string()); } - let mut descriptor = self.mesh_pipeline.specialize(pbr_pipeline_key); + let mut descriptor = self.mesh_pipeline.specialize(pbr_pipeline_key, layout); descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.shader_defs = shader_defs.clone(); let fragment = descriptor.fragment.as_mut().unwrap(); @@ -133,7 +138,7 @@ fn queue_custom( render_meshes: Res>, custom_pipeline: Res, msaa: Res, - mut pipelines: ResMut>, + mut pipelines: ResMut>, mut pipeline_cache: ResMut, material_meshes: Query<(Entity, &Handle, &MeshUniform, &IsRed)>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, @@ -142,15 +147,20 @@ fn queue_custom( .read() .get_id::() .unwrap(); - let key = MeshPipelineKey::from_msaa_samples(msaa.samples); + let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); for (view, mut transparent_phase) in views.iter_mut() { let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); for (entity, mesh_handle, mesh_uniform, is_red) in material_meshes.iter() { if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline = - pipelines.specialize(&mut pipeline_cache, &custom_pipeline, (*is_red, key)); + let key = + msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline = pipelines.specialize( + &mut pipeline_cache, + &custom_pipeline, + (*is_red, key), + &mesh.layout, + ); transparent_phase.add(Transparent3d { entity, pipeline, diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 4c88cd17481e4..c620cb36fc857 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -5,7 +5,7 @@ use bevy::{ pbr::{MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup}, prelude::*, render::{ - mesh::GpuBufferInfo, + mesh::{GpuBufferInfo, MeshVertexBufferLayout}, render_asset::RenderAssets, render_component::{ExtractComponent, ExtractComponentPlugin}, render_phase::{ @@ -81,7 +81,7 @@ impl Plugin for CustomMaterialPlugin { app.sub_app_mut(RenderApp) .add_render_command::() .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Queue, queue_custom) .add_system_to_stage(RenderStage::Prepare, prepare_instance_buffers); } @@ -99,10 +99,11 @@ fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, msaa: Res, - mut pipelines: ResMut>, + mut pipelines: ResMut>, mut pipeline_cache: ResMut, + meshes: Res>, material_meshes: Query< - (Entity, &MeshUniform), + (Entity, &MeshUniform, &Handle), (With>, With), >, mut views: Query<(&ExtractedView, &mut RenderPhase)>, @@ -112,20 +113,24 @@ fn queue_custom( .get_id::() .unwrap(); - let key = MeshPipelineKey::from_msaa_samples(msaa.samples) - | MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); - let pipeline = pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key); + let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); for (view, mut transparent_phase) in views.iter_mut() { let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); - for (entity, mesh_uniform) in material_meshes.iter() { - transparent_phase.add(Transparent3d { - entity, - pipeline, - draw_function: draw_custom, - distance: view_row_2.dot(mesh_uniform.transform.col(3)), - }); + for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() { + if let Some(mesh) = meshes.get(mesh_handle) { + let key = + msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline = + pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout); + transparent_phase.add(Transparent3d { + entity, + pipeline, + draw_function: draw_custom, + distance: view_row_2.dot(mesh_uniform.transform.col(3)), + }); + } } } } @@ -175,11 +180,15 @@ impl FromWorld for CustomPipeline { } } -impl SpecializedPipeline for CustomPipeline { +impl SpecializedMeshPipeline for CustomPipeline { type Key = MeshPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key); + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(key, layout); descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.buffers.push(VertexBufferLayout { array_stride: std::mem::size_of::() as u64, diff --git a/examples/shader/shader_material_glsl.rs b/examples/shader/shader_material_glsl.rs index d1bb8b4b20818..984bdc75119b7 100644 --- a/examples/shader/shader_material_glsl.rs +++ b/examples/shader/shader_material_glsl.rs @@ -4,6 +4,7 @@ use bevy::{ prelude::*, reflect::TypeUuid, render::{ + mesh::MeshVertexBufferLayout, render_asset::{PrepareAssetError, RenderAsset}, render_resource::{ std140::{AsStd140, Std140}, @@ -95,7 +96,11 @@ impl SpecializedMaterial for CustomMaterial { fn key(_: &::PreparedAsset) -> Self::Key {} - fn specialize(_: Self::Key, descriptor: &mut RenderPipelineDescriptor) { + fn specialize( + descriptor: &mut RenderPipelineDescriptor, + _: Self::Key, + _layout: &MeshVertexBufferLayout, + ) { descriptor.vertex.entry_point = "main".into(); descriptor.fragment.as_mut().unwrap().entry_point = "main".into(); } From 4b289e6b5e280be85ed2d2bc58de63104fa4fe18 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 18 Feb 2022 16:28:29 -0800 Subject: [PATCH 02/12] Add error handling for missing vertex attributes --- crates/bevy_pbr/src/material.rs | 16 +++- crates/bevy_pbr/src/render/light.rs | 24 +++-- crates/bevy_pbr/src/render/mesh.rs | 10 +-- crates/bevy_pbr/src/wireframe.rs | 30 ++++--- crates/bevy_render/src/mesh/mesh/mod.rs | 25 ++++-- .../render_resource/pipeline_specializer.rs | 88 ++++++++++++------- crates/bevy_sprite/src/mesh2d/material.rs | 17 +++- crates/bevy_sprite/src/mesh2d/mesh.rs | 10 +-- 8 files changed, 144 insertions(+), 76 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index e63fdfd54698d..e672ac97578ea 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -24,12 +24,13 @@ use bevy_render::{ }, render_resource::{ BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, - SpecializedMeshPipeline, SpecializedMeshPipelines, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, view::{ExtractedView, Msaa, VisibleEntities}, RenderApp, RenderStage, }; +use bevy_utils::tracing::error; use std::hash::Hash; use std::marker::PhantomData; @@ -218,8 +219,8 @@ impl SpecializedMeshPipeline for MaterialPipeline { &self, key: Self::Key, layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key.0, layout); + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key.0, layout)?; if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -234,7 +235,7 @@ impl SpecializedMeshPipeline for MaterialPipeline { ]); M::specialize(&mut descriptor, key.1, layout); - descriptor + Ok(descriptor) } } @@ -344,6 +345,13 @@ pub fn queue_material_meshes( (mesh_key, specialized_key), &mesh.layout, ); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; // NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix // gives the z component of translation of the mesh in view space diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 7b52a61c82bfc..d230f92db028a 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -28,7 +28,10 @@ use bevy_render::{ view::{ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities}, }; use bevy_transform::components::GlobalTransform; -use bevy_utils::{tracing::warn, HashMap}; +use bevy_utils::{ + tracing::{error, warn}, + HashMap, +}; use std::num::NonZeroU32; #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] @@ -250,12 +253,11 @@ impl SpecializedMeshPipeline for ShadowPipeline { &self, key: Self::Key, layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor { - let vertex_buffer_layout = layout - .get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)]) - .expect("Mesh is missing a vertex attribute"); + ) -> Result { + let vertex_buffer_layout = + layout.get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)])?; - RenderPipelineDescriptor { + Ok(RenderPipelineDescriptor { vertex: VertexState { shader: SHADOW_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), @@ -291,7 +293,7 @@ impl SpecializedMeshPipeline for ShadowPipeline { }), multisample: MultisampleState::default(), label: Some("shadow_pipeline".into()), - } + }) } } @@ -1078,6 +1080,14 @@ pub fn queue_shadows( &mesh.layout, ); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + shadow_phase.add(Shadow { draw_function: draw_shadow_mesh, pipeline: pipeline_id, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 226f86407c132..dfceac7e8c693 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -417,7 +417,7 @@ impl SpecializedMeshPipeline for MeshPipeline { &self, key: Self::Key, layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor { + ) -> Result { let mut vertex_attributes = vec![ Mesh::ATTRIBUTE_POSITION.at_shader_location(0), Mesh::ATTRIBUTE_NORMAL.at_shader_location(1), @@ -430,9 +430,7 @@ impl SpecializedMeshPipeline for MeshPipeline { vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } - let vertex_buffer_layout = layout - .get_layout(&vertex_attributes) - .expect("Mesh is missing a vertex attribute"); + let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; let (label, blend, depth_write_enabled); if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) { @@ -453,7 +451,7 @@ impl SpecializedMeshPipeline for MeshPipeline { #[cfg(feature = "webgl")] shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT")); - RenderPipelineDescriptor { + Ok(RenderPipelineDescriptor { vertex: VertexState { shader: MESH_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), @@ -502,7 +500,7 @@ impl SpecializedMeshPipeline for MeshPipeline { alpha_to_coverage_enabled: false, }, label: Some(label), - } + }) } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index bda39c0b1b283..07e43a2d3cf99 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -7,7 +7,8 @@ use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::{Reflect, TypeUuid}; use bevy_render::mesh::MeshVertexBufferLayout; use bevy_render::render_resource::{ - PolygonMode, SpecializedMeshPipeline, SpecializedMeshPipelines, + PolygonMode, RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, + SpecializedMeshPipelines, }; use bevy_render::{ mesh::Mesh, @@ -17,6 +18,7 @@ use bevy_render::{ view::{ExtractedView, Msaa}, RenderApp, RenderStage, }; +use bevy_utils::tracing::error; pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766); @@ -89,13 +91,13 @@ impl SpecializedMeshPipeline for WireframePipeline { &self, key: Self::Key, layout: &MeshVertexBufferLayout, - ) -> bevy_render::render_resource::RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key, layout); + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; descriptor.vertex.shader = self.shader.clone_weak(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak(); descriptor.primitive.polygon_mode = PolygonMode::Line; descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; - descriptor + Ok(descriptor) } } @@ -129,14 +131,22 @@ fn queue_wireframes( if let Some(mesh) = render_meshes.get(mesh_handle) { let key = msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline_id = specialized_pipelines.specialize( + &mut pipeline_cache, + &wireframe_pipeline, + key, + &mesh.layout, + ); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + return; + } + }; transparent_phase.add(Opaque3d { entity, - pipeline: specialized_pipelines.specialize( - &mut pipeline_cache, - &wireframe_pipeline, - key, - &mesh.layout, - ), + pipeline: pipeline_id, draw_function: draw_custom, distance: view_row_2.dot(mesh_uniform.transform.col(3)), }); diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 1867577d48d43..68e6adf1ce870 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -12,6 +12,7 @@ use bevy_math::*; use bevy_reflect::TypeUuid; use bevy_utils::{EnumVariantMeta, Hashed}; use std::{collections::BTreeMap, hash::Hash}; +use thiserror::Error; use wgpu::{ util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexAttribute, VertexFormat, VertexStepMode, @@ -374,7 +375,7 @@ impl MeshVertexAttribute { } pub const fn at_shader_location(&self, shader_location: u32) -> VertexAttributeDescriptor { - VertexAttributeDescriptor::new(shader_location, self.id) + VertexAttributeDescriptor::new(shader_location, self.id, self.name) } } @@ -415,7 +416,7 @@ impl InnerMeshVertexBufferLayout { pub fn get_layout( &self, attribute_descriptors: &[VertexAttributeDescriptor], - ) -> Option { + ) -> Result { let mut attributes = Vec::with_capacity(attribute_descriptors.len()); for attribute_descriptor in attribute_descriptors.iter() { if let Some(index) = self @@ -430,11 +431,15 @@ impl InnerMeshVertexBufferLayout { shader_location: attribute_descriptor.shader_location, }) } else { - return None; + return Err(MissingVertexAttributeError { + id: attribute_descriptor.id, + name: attribute_descriptor.name, + pipeline_type: None, + }); } } - Some(VertexBufferLayout { + Ok(VertexBufferLayout { array_stride: self.layout.array_stride, step_mode: self.layout.step_mode, attributes, @@ -442,16 +447,26 @@ impl InnerMeshVertexBufferLayout { } } +#[derive(Error, Debug)] +#[error("Mesh is missing requested attribute: {name} ({id:?}, pipeline type: {pipeline_type:?})")] +pub struct MissingVertexAttributeError { + pub(crate) pipeline_type: Option<&'static str>, + id: MeshVertexAttributeId, + name: &'static str, +} + pub struct VertexAttributeDescriptor { pub shader_location: u32, pub id: MeshVertexAttributeId, + name: &'static str, } impl VertexAttributeDescriptor { - pub const fn new(shader_location: u32, id: MeshVertexAttributeId) -> Self { + pub const fn new(shader_location: u32, id: MeshVertexAttributeId, name: &'static str) -> Self { Self { shader_location, id, + name, } } } diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index d187e199ccd29..200bdbbea3aae 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -1,13 +1,20 @@ use crate::{ - mesh::{InnerMeshVertexBufferLayout, MeshVertexBufferLayout}, + mesh::{InnerMeshVertexBufferLayout, MeshVertexBufferLayout, MissingVertexAttributeError}, render_resource::{ CachedPipelineId, RenderPipelineCache, RenderPipelineDescriptor, VertexBufferLayout, }, }; use bevy_utils::{ - hashbrown::hash_map::RawEntryMut, Entry, HashMap, Hashed, PreHashMap, PreHashMapExt, + hashbrown::hash_map::RawEntryMut, tracing::error, Entry, HashMap, PreHashMap, PreHashMapExt, }; +use std::fmt::Debug; use std::hash::Hash; +use thiserror::Error; + +pub trait SpecializedPipeline { + type Key: Clone + Hash + PartialEq + Eq; + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor; +} pub struct SpecializedPipelines { cache: HashMap, @@ -35,9 +42,19 @@ impl SpecializedPipelines { } } -pub trait SpecializedPipeline { +#[derive(Error, Debug)] +pub enum SpecializedMeshPipelineError { + #[error(transparent)] + MissingVertexAttribute(#[from] MissingVertexAttributeError), +} + +pub trait SpecializedMeshPipeline { type Key: Clone + Hash + PartialEq + Eq; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor; + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result; } pub struct SpecializedMeshPipelines { @@ -62,39 +79,42 @@ impl SpecializedMeshPipelines { specialize_pipeline: &S, key: S::Key, layout: &MeshVertexBufferLayout, - ) -> CachedPipelineId { + ) -> Result { let map = self .mesh_layout_cache .get_or_insert_with(layout, Default::default); - *map.entry(key.clone()).or_insert_with(|| { - let descriptor = specialize_pipeline.specialize(key.clone(), layout); - // Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout - // We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them - let layout_map = match self - .vertex_layout_cache - .raw_entry_mut() - .from_key(&descriptor.vertex.buffers[0]) - { - RawEntryMut::Occupied(entry) => entry.into_mut(), - RawEntryMut::Vacant(entry) => { - entry - .insert(descriptor.vertex.buffers[0].clone(), Default::default()) - .1 - } - }; - match layout_map.entry(key) { - Entry::Occupied(entry) => *entry.into_mut(), - Entry::Vacant(entry) => *entry.insert(cache.queue(descriptor)), + match map.entry(key.clone()) { + Entry::Occupied(entry) => Ok(*entry.into_mut()), + Entry::Vacant(entry) => { + let descriptor = specialize_pipeline + .specialize(key.clone(), layout) + .map_err(|mut err| { + { + let SpecializedMeshPipelineError::MissingVertexAttribute(err) = + &mut err; + err.pipeline_type = Some(std::any::type_name::()); + } + err + })?; + // Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout + // We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them + let layout_map = match self + .vertex_layout_cache + .raw_entry_mut() + .from_key(&descriptor.vertex.buffers[0]) + { + RawEntryMut::Occupied(entry) => entry.into_mut(), + RawEntryMut::Vacant(entry) => { + entry + .insert(descriptor.vertex.buffers[0].clone(), Default::default()) + .1 + } + }; + Ok(*entry.insert(match layout_map.entry(key) { + Entry::Occupied(entry) => *entry.into_mut(), + Entry::Vacant(entry) => *entry.insert(cache.queue(descriptor)), + })) } - }) + } } } - -pub trait SpecializedMeshPipeline { - type Key: Clone + Hash + PartialEq + Eq; - fn specialize( - &self, - key: Self::Key, - layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor; -} diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index c91bfe676d7d9..2f3c9ff1211e8 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -11,6 +11,7 @@ use bevy_ecs::{ }, world::FromWorld, }; +use bevy_log::error; use bevy_render::{ mesh::{Mesh, MeshVertexBufferLayout}, render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}, @@ -21,7 +22,7 @@ use bevy_render::{ }, render_resource::{ BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, - SpecializedMeshPipeline, SpecializedMeshPipelines, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, view::{ComputedVisibility, Msaa, Visibility, VisibleEntities}, @@ -202,8 +203,8 @@ impl SpecializedMeshPipeline for Material2dPipeline &self, key: Self::Key, layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh2d_pipeline.specialize(key.0, layout); + ) -> Result { + let mut descriptor = self.mesh2d_pipeline.specialize(key.0, layout)?; if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -218,7 +219,7 @@ impl SpecializedMeshPipeline for Material2dPipeline ]); M::specialize(key.1, &mut descriptor, layout); - descriptor + Ok(descriptor) } } @@ -308,6 +309,14 @@ pub fn queue_material2d_meshes( &mesh.layout, ); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + let mesh_z = mesh2d_uniform.transform.w_axis.z; transparent_phase.add(Transparent2d { entity: *visible_entity, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index a87cd4f36570c..2d2a849917fcd 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -282,7 +282,7 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { &self, key: Self::Key, layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor { + ) -> Result { let mut vertex_attributes = vec![ Mesh::ATTRIBUTE_POSITION.at_shader_location(0), Mesh::ATTRIBUTE_NORMAL.at_shader_location(1), @@ -298,11 +298,9 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { #[cfg(feature = "webgl")] shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT")); - let vertex_buffer_layout = layout - .get_layout(&vertex_attributes) - .expect("Mesh is missing a vertex attribute"); + let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; - RenderPipelineDescriptor { + Ok(RenderPipelineDescriptor { vertex: VertexState { shader: MESH2D_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), @@ -336,7 +334,7 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { alpha_to_coverage_enabled: false, }, label: Some("transparent_mesh2d_pipeline".into()), - } + }) } } From 1a767529031f3c525418c94f14bcd50917e7a782 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 18 Feb 2022 16:38:26 -0800 Subject: [PATCH 03/12] use `offset` instead of `array_stride` --- crates/bevy_render/src/render_resource/pipeline.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index b2c37825ffddc..1d8cd2c253ab9 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -137,19 +137,19 @@ impl VertexBufferLayout { step_mode: VertexStepMode, vertex_formats: T, ) -> Self { - let mut array_stride = 0; + let mut offset = 0; let mut attributes = Vec::new(); for (shader_location, format) in vertex_formats.into_iter().enumerate() { attributes.push(VertexAttribute { format, - offset: array_stride, + offset, shader_location: shader_location as u32, }); - array_stride += format.size(); + offset += format.size(); } VertexBufferLayout { - array_stride, + array_stride: offset, step_mode, attributes, } From 03015aa619d5aa35ac6bb007ed9c545fd2b66778 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 22 Feb 2022 13:27:02 -0800 Subject: [PATCH 04/12] add doc links --- crates/bevy_render/src/render_resource/pipeline.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 1d8cd2c253ab9..03f2ff233420e 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -130,9 +130,9 @@ pub struct VertexBufferLayout { impl VertexBufferLayout { /// Creates a new densely packed [`VertexBufferLayout`] from an iterator of vertex formats. - /// Iteration order determines the `shader_location` and `offset` of the VertexAttributes. + /// Iteration order determines the `shader_location` and `offset` of the [`VertexAttributes`](VertexAttribute). /// The first iterated item will have a `shader_location` and `offset` of zero. - /// The `array_stride` is the sum of the size of the iterated VertexFormats (in bytes). + /// The `array_stride` is the sum of the size of the iterated [`VertexFormats`](VertexFormat) (in bytes). pub fn from_vertex_formats>( step_mode: VertexStepMode, vertex_formats: T, From b7160df71dbd241b54dcbb3b5df15b450b46c8c9 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 22 Feb 2022 13:38:21 -0800 Subject: [PATCH 05/12] fix examples --- examples/shader/animate_shader.rs | 11 ++++++----- examples/shader/shader_defs.rs | 22 ++++++++++++---------- examples/shader/shader_instancing.rs | 11 ++++++----- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/examples/shader/animate_shader.rs b/examples/shader/animate_shader.rs index 0fc09c6c2fbf5..46477b24bfd5d 100644 --- a/examples/shader/animate_shader.rs +++ b/examples/shader/animate_shader.rs @@ -115,8 +115,9 @@ fn queue_custom( let view_row_2 = view_matrix.row(2); for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() { if let Some(mesh) = render_meshes.get(mesh_handle) { - let pipeline = - pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout); + let pipeline = pipelines + .specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout) + .unwrap(); transparent_phase.add(Transparent3d { entity, pipeline, @@ -220,8 +221,8 @@ impl SpecializedMeshPipeline for CustomPipeline { &self, key: Self::Key, layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key, layout); + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; descriptor.vertex.shader = self.shader.clone(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); descriptor.layout = Some(vec![ @@ -229,7 +230,7 @@ impl SpecializedMeshPipeline for CustomPipeline { self.mesh_pipeline.mesh_layout.clone(), self.time_bind_group_layout.clone(), ]); - descriptor + Ok(descriptor) } } diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index e7325d7eef12f..187ec8e51d49d 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -12,7 +12,7 @@ use bevy::{ render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ RenderPipelineCache, RenderPipelineDescriptor, SpecializedMeshPipeline, - SpecializedMeshPipelines, + SpecializedMeshPipelineError, SpecializedMeshPipelines, }, view::ExtractedView, RenderApp, RenderStage, @@ -106,12 +106,12 @@ impl SpecializedMeshPipeline for IsRedPipeline { &self, (is_red, pbr_pipeline_key): Self::Key, layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor { + ) -> Result { let mut shader_defs = Vec::new(); if is_red.0 { shader_defs.push("IS_RED".to_string()); } - let mut descriptor = self.mesh_pipeline.specialize(pbr_pipeline_key, layout); + let mut descriptor = self.mesh_pipeline.specialize(pbr_pipeline_key, layout)?; descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.shader_defs = shader_defs.clone(); let fragment = descriptor.fragment.as_mut().unwrap(); @@ -121,7 +121,7 @@ impl SpecializedMeshPipeline for IsRedPipeline { self.mesh_pipeline.view_layout.clone(), self.mesh_pipeline.mesh_layout.clone(), ]); - descriptor + Ok(descriptor) } } @@ -155,12 +155,14 @@ fn queue_custom( if let Some(mesh) = render_meshes.get(mesh_handle) { let key = msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline = pipelines.specialize( - &mut pipeline_cache, - &custom_pipeline, - (*is_red, key), - &mesh.layout, - ); + let pipeline = pipelines + .specialize( + &mut pipeline_cache, + &custom_pipeline, + (*is_red, key), + &mesh.layout, + ) + .unwrap(); transparent_phase.add(Transparent3d { entity, pipeline, diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index c620cb36fc857..f56b1a70e0cba 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -122,8 +122,9 @@ fn queue_custom( if let Some(mesh) = meshes.get(mesh_handle) { let key = msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline = - pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout); + let pipeline = pipelines + .specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout) + .unwrap(); transparent_phase.add(Transparent3d { entity, pipeline, @@ -187,8 +188,8 @@ impl SpecializedMeshPipeline for CustomPipeline { &self, key: Self::Key, layout: &MeshVertexBufferLayout, - ) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key, layout); + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.buffers.push(VertexBufferLayout { array_stride: std::mem::size_of::() as u64, @@ -212,7 +213,7 @@ impl SpecializedMeshPipeline for CustomPipeline { self.mesh_pipeline.mesh_layout.clone(), ]); - descriptor + Ok(descriptor) } } From b566d5ee870a64e4f0c87e4b283d88a82375dc01 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 22 Feb 2022 13:41:56 -0800 Subject: [PATCH 06/12] clippy --- crates/bevy_pbr/src/render/light.rs | 2 +- crates/bevy_pbr/src/render/mesh.rs | 2 +- crates/bevy_sprite/src/render/mod.rs | 2 +- crates/bevy_ui/src/render/pipeline.rs | 2 +- examples/shader/animate_shader.rs | 1 + examples/shader/shader_instancing.rs | 1 + 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index d230f92db028a..562c9b6d2031c 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -262,7 +262,7 @@ impl SpecializedMeshPipeline for ShadowPipeline { shader: SHADOW_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: vec![], - buffers: vec![vertex_buffer_layout.clone()], + buffers: vec![vertex_buffer_layout], }, fragment: None, layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]), diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index dfceac7e8c693..a410e57ed5aa5 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -456,7 +456,7 @@ impl SpecializedMeshPipeline for MeshPipeline { shader: MESH_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![vertex_buffer_layout.clone()], + buffers: vec![vertex_buffer_layout], }, fragment: Some(FragmentState { shader: MESH_SHADER_HANDLE.typed::(), diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index e9f00bb710332..1243ded649a55 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -138,7 +138,7 @@ impl SpecializedPipeline for SpritePipeline { shader: SPRITE_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![vertex_layout.clone()], + buffers: vec![vertex_layout], }, fragment: Some(FragmentState { shader: SPRITE_SHADER_HANDLE.typed::(), diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 082b88ed4370d..dee26dfbc729a 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -84,7 +84,7 @@ impl SpecializedPipeline for UiPipeline { shader: super::UI_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![vertex_layout.clone()], + buffers: vec![vertex_layout], }, fragment: Some(FragmentState { shader: super::UI_SHADER_HANDLE.typed::(), diff --git a/examples/shader/animate_shader.rs b/examples/shader/animate_shader.rs index 46477b24bfd5d..b4266bcc8f00d 100644 --- a/examples/shader/animate_shader.rs +++ b/examples/shader/animate_shader.rs @@ -92,6 +92,7 @@ fn extract_custom_material( } // add each entity with a mesh and a `CustomMaterial` to every view's `Transparent3d` render phase using the `CustomPipeline` +#[allow(clippy::too_many_arguments)] fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index f56b1a70e0cba..f0544c0234c5c 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -95,6 +95,7 @@ struct InstanceData { color: [f32; 4], } +#[allow(clippy::too_many_arguments)] fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, From 82aaea7b817f1c58107b78bc80b73d7e30402b7f Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 22 Feb 2022 14:43:04 -0800 Subject: [PATCH 07/12] custom vertex attribute example --- Cargo.toml | 4 + assets/shaders/custom_vertex_attribute.wgsl | 40 ++++++ crates/bevy_pbr/src/material.rs | 17 ++- crates/bevy_pbr/src/pbr_material.rs | 3 +- crates/bevy_sprite/src/mesh2d/material.rs | 17 ++- examples/README.md | 1 + examples/shader/custom_vertex_attribute.rs | 150 ++++++++++++++++++++ examples/shader/shader_material_glsl.rs | 3 +- 8 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 assets/shaders/custom_vertex_attribute.wgsl create mode 100644 examples/shader/custom_vertex_attribute.rs diff --git a/Cargo.toml b/Cargo.toml index ef6cb40a7da7b..ad3e7a94a30e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -444,6 +444,10 @@ name = "scene" path = "examples/scene/scene.rs" # Shaders +[[example]] +name = "custom_vertex_attribute" +path = "examples/shader/custom_vertex_attribute.rs" + [[example]] name = "shader_defs" path = "examples/shader/shader_defs.rs" diff --git a/assets/shaders/custom_vertex_attribute.wgsl b/assets/shaders/custom_vertex_attribute.wgsl new file mode 100644 index 0000000000000..3e42b03d710ae --- /dev/null +++ b/assets/shaders/custom_vertex_attribute.wgsl @@ -0,0 +1,40 @@ +#import bevy_pbr::mesh_view_bind_group +#import bevy_pbr::mesh_struct + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] blend_color: vec4; +}; + +struct CustomMaterial { + color: vec4; +}; +[[group(1), binding(0)]] +var material: CustomMaterial; + +[[group(2), binding(0)]] +var mesh: Mesh; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] blend_color: vec4; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let world_position = mesh.model * vec4(vertex.position, 1.0); + + var out: VertexOutput; + out.clip_position = view.view_proj * world_position; + out.blend_color = vertex.blend_color; + return out; +} + +struct FragmentInput { + [[location(0)]] blend_color: vec4; +}; + +[[stage(fragment)]] +fn fragment(input: FragmentInput) -> [[location(0)]] vec4 { + return material.color * input.blend_color; +} diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index e672ac97578ea..a6aaf26c795a3 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -73,6 +73,16 @@ pub trait Material: Asset + RenderAsset { fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { &[] } + + /// Customizes the default [`RenderPipelineDescriptor`]. + #[allow(unused_variables)] + #[inline] + fn specialize( + descriptor: &mut RenderPipelineDescriptor, + layout: &MeshVertexBufferLayout, + ) -> Result<(), SpecializedMeshPipelineError> { + Ok(()) + } } impl SpecializedMaterial for M { @@ -86,7 +96,8 @@ impl SpecializedMaterial for M { _descriptor: &mut RenderPipelineDescriptor, _key: Self::Key, _layout: &MeshVertexBufferLayout, - ) { + ) -> Result<(), SpecializedMeshPipelineError> { + ::specialize(_descriptor, _layout) } #[inline] @@ -140,7 +151,7 @@ pub trait SpecializedMaterial: Asset + RenderAsset { descriptor: &mut RenderPipelineDescriptor, key: Self::Key, layout: &MeshVertexBufferLayout, - ); + ) -> Result<(), SpecializedMeshPipelineError>; /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial::bind_group_layout`]. fn bind_group(material: &::PreparedAsset) -> &BindGroup; @@ -234,7 +245,7 @@ impl SpecializedMeshPipeline for MaterialPipeline { self.mesh_pipeline.mesh_layout.clone(), ]); - M::specialize(&mut descriptor, key.1, layout); + M::specialize(&mut descriptor, key.1, layout)?; Ok(descriptor) } } diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 1e89180ec76f0..eaf45397a78a4 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -343,7 +343,7 @@ impl SpecializedMaterial for StandardMaterial { descriptor: &mut RenderPipelineDescriptor, key: Self::Key, _layout: &MeshVertexBufferLayout, - ) { + ) -> Result<(), SpecializedMeshPipelineError> { if key.normal_map { descriptor .fragment @@ -355,6 +355,7 @@ impl SpecializedMaterial for StandardMaterial { if let Some(label) = &mut descriptor.label { *label = format!("pbr_{}", *label).into(); } + Ok(()) } fn fragment_shader(_asset_server: &AssetServer) -> Option> { diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 2f3c9ff1211e8..619505bc18980 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -70,6 +70,16 @@ pub trait Material2d: Asset + RenderAsset { fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { &[] } + + /// Customizes the default [`RenderPipelineDescriptor`]. + #[allow(unused_variables)] + #[inline] + fn specialize( + descriptor: &mut RenderPipelineDescriptor, + layout: &MeshVertexBufferLayout, + ) -> Result<(), SpecializedMeshPipelineError> { + Ok(()) + } } impl SpecializedMaterial2d for M { @@ -83,7 +93,8 @@ impl SpecializedMaterial2d for M { _key: Self::Key, _descriptor: &mut RenderPipelineDescriptor, _layout: &MeshVertexBufferLayout, - ) { + ) -> Result<(), SpecializedMeshPipelineError> { + ::specialize(_descriptor, _layout) } #[inline] @@ -132,7 +143,7 @@ pub trait SpecializedMaterial2d: Asset + RenderAsset { key: Self::Key, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayout, - ); + ) -> Result<(), SpecializedMeshPipelineError>; /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial2d::bind_group_layout`]. fn bind_group(material: &::PreparedAsset) -> &BindGroup; @@ -218,7 +229,7 @@ impl SpecializedMeshPipeline for Material2dPipeline self.mesh2d_pipeline.mesh_layout.clone(), ]); - M::specialize(key.1, &mut descriptor, layout); + M::specialize(key.1, &mut descriptor, layout)?; Ok(descriptor) } } diff --git a/examples/README.md b/examples/README.md index aeaff8c7845f2..d147b0a79c773 100644 --- a/examples/README.md +++ b/examples/README.md @@ -222,6 +222,7 @@ Example | File | Description Example | File | Description --- | --- | --- +`custom_vertex_attribute` | [`shader/custom_vertex_attribute.rs`](./shader/custom_vertex_attribute.rs) | Illustrates creating a custom shader material that reads a mesh's custom vertex attribute. `shader_material` | [`shader/shader_material.rs`](./shader/shader_material.rs) | Illustrates creating a custom material and a shader that uses it `shader_material_glsl` | [`shader/shader_material_glsl.rs`](./shader/shader_material_glsl.rs) | A custom shader using the GLSL shading language. `shader_instancing` | [`shader/shader_instancing.rs`](./shader/shader_instancing.rs) | A custom shader showing off rendering a mesh multiple times in one draw call. diff --git a/examples/shader/custom_vertex_attribute.rs b/examples/shader/custom_vertex_attribute.rs new file mode 100644 index 0000000000000..af797e7fc6155 --- /dev/null +++ b/examples/shader/custom_vertex_attribute.rs @@ -0,0 +1,150 @@ +use bevy::{ + ecs::system::{lifetimeless::SRes, SystemParamItem}, + pbr::MaterialPipeline, + prelude::*, + reflect::TypeUuid, + render::{ + mesh::{MeshVertexAttribute, MeshVertexBufferLayout}, + render_asset::{PrepareAssetError, RenderAsset}, + render_resource::{ + std140::{AsStd140, Std140}, + BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer, + BufferBindingType, BufferInitDescriptor, BufferSize, BufferUsages, + RenderPipelineDescriptor, ShaderStages, SpecializedMeshPipelineError, VertexFormat, + }, + renderer::RenderDevice, + }, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(MaterialPlugin::::default()) + .add_startup_system(setup) + .run(); +} + +const ATTRIBUTE_BLEND_COLOR: MeshVertexAttribute = + MeshVertexAttribute::new("BlendColor", 988540917, VertexFormat::Float32x4); + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // A "high" random id should be used for custom attributes to ensure proper sorting and avoid collisions with other attributes. + // See the MeshVertexAttribute docs for more info. + let mut mesh = Mesh::from(shape::Cube { size: 1.0 }); + mesh.insert_attribute( + ATTRIBUTE_BLEND_COLOR, + // The cube mesh has 24 vertices (6 faces, 4 vertices per face), so we insert one BlendColor for each + vec![[1.0, 0.0, 0.0, 1.0]; 24], + ); + + // cube + commands.spawn().insert_bundle(MaterialMeshBundle { + mesh: meshes.add(mesh), + transform: Transform::from_xyz(0.0, 0.5, 0.0), + material: materials.add(CustomMaterial { + color: Color::WHITE, + }), + ..Default::default() + }); + + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..Default::default() + }); +} + +// This is the struct that will be passed to your shader +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] +pub struct CustomMaterial { + color: Color, +} + +#[derive(Clone)] +pub struct GpuCustomMaterial { + _buffer: Buffer, + bind_group: BindGroup, +} + +// The implementation of [`Material`] needs this impl to work properly. +impl RenderAsset for CustomMaterial { + type ExtractedAsset = CustomMaterial; + type PreparedAsset = GpuCustomMaterial; + type Param = (SRes, SRes>); + fn extract_asset(&self) -> Self::ExtractedAsset { + self.clone() + } + + fn prepare_asset( + extracted_asset: Self::ExtractedAsset, + (render_device, material_pipeline): &mut SystemParamItem, + ) -> Result> { + let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32()); + let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + contents: color.as_std140().as_bytes(), + label: None, + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, + }); + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + label: None, + layout: &material_pipeline.material_layout, + }); + + Ok(GpuCustomMaterial { + _buffer: buffer, + bind_group, + }) + } +} + +impl Material for CustomMaterial { + fn vertex_shader(asset_server: &AssetServer) -> Option> { + Some(asset_server.load("shaders/custom_vertex_attribute.wgsl")) + } + fn fragment_shader(asset_server: &AssetServer) -> Option> { + Some(asset_server.load("shaders/custom_vertex_attribute.wgsl")) + } + + fn bind_group(render_asset: &::PreparedAsset) -> &BindGroup { + &render_asset.bind_group + } + + fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: BufferSize::new(Vec4::std140_size_static() as u64), + }, + count: None, + }], + label: None, + }) + } + + fn specialize( + descriptor: &mut RenderPipelineDescriptor, + layout: &MeshVertexBufferLayout, + ) -> Result<(), SpecializedMeshPipelineError> { + let vertex_layout = layout.get_layout(&[ + Mesh::ATTRIBUTE_POSITION.at_shader_location(0), + ATTRIBUTE_BLEND_COLOR.at_shader_location(1), + ])?; + descriptor.vertex.buffers = vec![vertex_layout]; + Ok(()) + } +} diff --git a/examples/shader/shader_material_glsl.rs b/examples/shader/shader_material_glsl.rs index 984bdc75119b7..2f9dab493e0f7 100644 --- a/examples/shader/shader_material_glsl.rs +++ b/examples/shader/shader_material_glsl.rs @@ -100,9 +100,10 @@ impl SpecializedMaterial for CustomMaterial { descriptor: &mut RenderPipelineDescriptor, _: Self::Key, _layout: &MeshVertexBufferLayout, - ) { + ) -> Result<(), SpecializedMeshPipelineError> { descriptor.vertex.entry_point = "main".into(); descriptor.fragment.as_mut().unwrap().entry_point = "main".into(); + Ok(()) } fn vertex_shader(asset_server: &AssetServer) -> Option> { From 29afa5291f0743745363f5faf65a0771857b6fad Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 22 Feb 2022 14:55:04 -0800 Subject: [PATCH 08/12] fix set_attribute docs --- crates/bevy_gltf/src/loader.rs | 2 +- crates/bevy_render/src/mesh/mesh/mod.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 5b8cca3b9b7e1..a9d1b242f1540 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -158,7 +158,7 @@ async fn load_gltf<'a, 'b>( // .read_colors(0) // .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect())) // { - // mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute); + // mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute); // } if let Some(indices) = reader.read_indices() { diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 68e6adf1ce870..2d8ee3e158149 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -46,22 +46,22 @@ pub struct Mesh { /// # use bevy_render::render_resource::PrimitiveTopology; /// fn create_triangle() -> Mesh { /// let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); -/// mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]); +/// mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]); /// mesh.set_indices(Some(Indices::U32(vec![0,1,2]))); /// mesh /// } /// ``` impl Mesh { - /// Where the vertex is located in space. Use in conjunction with [`Mesh::set_attribute`] + /// Where the vertex is located in space. Use in conjunction with [`Mesh::insert_attribute`] pub const ATTRIBUTE_POSITION: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3); /// The direction the vertex normal is facing in. - /// Use in conjunction with [`Mesh::set_attribute`] + /// Use in conjunction with [`Mesh::insert_attribute`] pub const ATTRIBUTE_NORMAL: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Normal", 1, VertexFormat::Float32x3); - /// Texture coordinates for the vertex. Use in conjunction with [`Mesh::set_attribute`] + /// Texture coordinates for the vertex. Use in conjunction with [`Mesh::insert_attribute`] pub const ATTRIBUTE_UV_0: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Uv", 2, VertexFormat::Float32x2); @@ -69,14 +69,14 @@ impl Mesh { pub const ATTRIBUTE_TANGENT: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Tangent", 3, VertexFormat::Float32x4); - /// Per vertex coloring. Use in conjunction with [`Mesh::set_attribute`] + /// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`] pub const ATTRIBUTE_COLOR: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Color", 4, VertexFormat::Uint32); - /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::set_attribute`] + /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::insert_attribute`] pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_JointWeight", 5, VertexFormat::Float32x4); - /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::set_attribute`] + /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::insert_attribute`] pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_JointIndex", 6, VertexFormat::Uint32); From c1ad89c9c97cc9fd970237e3382f08bc6d9829f0 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 22 Feb 2022 15:19:03 -0800 Subject: [PATCH 09/12] tuple keys -> struct keys --- crates/bevy_pbr/src/material.rs | 19 ++++++++++++++----- crates/bevy_sprite/src/mesh2d/material.rs | 21 +++++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index a6aaf26c795a3..e2d2c78c5d98f 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -215,6 +215,12 @@ impl Plugin for MaterialPlugin { } } +#[derive(Eq, PartialEq, Clone, Hash)] +pub struct MaterialPipelineKey { + mesh_key: MeshPipelineKey, + material_key: T, +} + pub struct MaterialPipeline { pub mesh_pipeline: MeshPipeline, pub material_layout: BindGroupLayout, @@ -224,14 +230,14 @@ pub struct MaterialPipeline { } impl SpecializedMeshPipeline for MaterialPipeline { - type Key = (MeshPipelineKey, M::Key); + type Key = MaterialPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { - let mut descriptor = self.mesh_pipeline.specialize(key.0, layout)?; + let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?; if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -245,7 +251,7 @@ impl SpecializedMeshPipeline for MaterialPipeline { self.mesh_pipeline.mesh_layout.clone(), ]); - M::specialize(&mut descriptor, key.1, layout)?; + M::specialize(&mut descriptor, key.material_key, layout)?; Ok(descriptor) } } @@ -348,12 +354,15 @@ pub fn queue_material_meshes( mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS; } - let specialized_key = M::key(material); + let material_key = M::key(material); let pipeline_id = pipelines.specialize( &mut pipeline_cache, &material_pipeline, - (mesh_key, specialized_key), + MaterialPipelineKey { + mesh_key, + material_key, + }, &mesh.layout, ); let pipeline_id = match pipeline_id { diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 619505bc18980..36c8c0a6efeeb 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -207,15 +207,21 @@ pub struct Material2dPipeline { marker: PhantomData, } +#[derive(Eq, PartialEq, Clone, Hash)] +pub struct Material2dKey { + mesh_key: Mesh2dPipelineKey, + material_key: T, +} + impl SpecializedMeshPipeline for Material2dPipeline { - type Key = (Mesh2dPipelineKey, M::Key); + type Key = Material2dKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { - let mut descriptor = self.mesh2d_pipeline.specialize(key.0, layout)?; + let mut descriptor = self.mesh2d_pipeline.specialize(key.mesh_key, layout)?; if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -229,7 +235,7 @@ impl SpecializedMeshPipeline for Material2dPipeline self.mesh2d_pipeline.mesh_layout.clone(), ]); - M::specialize(key.1, &mut descriptor, layout)?; + M::specialize(key.material_key, &mut descriptor, layout)?; Ok(descriptor) } } @@ -309,14 +315,17 @@ pub fn queue_material2d_meshes( { if let Some(material2d) = render_materials.get(material2d_handle) { if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { - let mesh2d_key = msaa_key + let mesh_key = msaa_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); - let specialized_key = M::key(material2d); + let material_key = M::key(material2d); let pipeline_id = pipelines.specialize( &mut pipeline_cache, &material2d_pipeline, - (mesh2d_key, specialized_key), + Material2dKey { + mesh_key, + material_key, + }, &mesh.layout, ); From 4493f0f4912ef6eed1c7eb4bb3d2b31ec4d42871 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 23 Feb 2022 13:27:41 -0800 Subject: [PATCH 10/12] move comment --- examples/shader/custom_vertex_attribute.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/shader/custom_vertex_attribute.rs b/examples/shader/custom_vertex_attribute.rs index af797e7fc6155..deb56d3c524d3 100644 --- a/examples/shader/custom_vertex_attribute.rs +++ b/examples/shader/custom_vertex_attribute.rs @@ -25,6 +25,8 @@ fn main() { .run(); } +// A "high" random id should be used for custom attributes to ensure consistent sorting and avoid collisions with other attributes. +// See the MeshVertexAttribute docs for more info. const ATTRIBUTE_BLEND_COLOR: MeshVertexAttribute = MeshVertexAttribute::new("BlendColor", 988540917, VertexFormat::Float32x4); @@ -34,8 +36,6 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - // A "high" random id should be used for custom attributes to ensure proper sorting and avoid collisions with other attributes. - // See the MeshVertexAttribute docs for more info. let mut mesh = Mesh::from(shape::Cube { size: 1.0 }); mesh.insert_attribute( ATTRIBUTE_BLEND_COLOR, From 2bae92796fb70340a274a1de46eab78995f3aad9 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 23 Feb 2022 14:52:38 -0800 Subject: [PATCH 11/12] fix "unused but used" variable names --- crates/bevy_pbr/src/material.rs | 6 +++--- crates/bevy_sprite/src/mesh2d/material.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index e2d2c78c5d98f..d01e4f79a557c 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -93,11 +93,11 @@ impl SpecializedMaterial for M { #[inline] fn specialize( - _descriptor: &mut RenderPipelineDescriptor, + descriptor: &mut RenderPipelineDescriptor, _key: Self::Key, - _layout: &MeshVertexBufferLayout, + layout: &MeshVertexBufferLayout, ) -> Result<(), SpecializedMeshPipelineError> { - ::specialize(_descriptor, _layout) + ::specialize(descriptor, layout) } #[inline] diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 36c8c0a6efeeb..630af30155cb0 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -91,10 +91,10 @@ impl SpecializedMaterial2d for M { #[inline] fn specialize( _key: Self::Key, - _descriptor: &mut RenderPipelineDescriptor, - _layout: &MeshVertexBufferLayout, + descriptor: &mut RenderPipelineDescriptor, + layout: &MeshVertexBufferLayout, ) -> Result<(), SpecializedMeshPipelineError> { - ::specialize(_descriptor, _layout) + ::specialize(descriptor, layout) } #[inline] From 46f24a22394985b975b2d892c5dd65e2730af628 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 23 Feb 2022 15:16:49 -0800 Subject: [PATCH 12/12] Check for invalid pipeline cache entries --- crates/bevy_render/src/mesh/mesh/mod.rs | 1 - .../src/render_resource/bind_group_layout.rs | 6 ++++++ crates/bevy_render/src/render_resource/pipeline.rs | 6 +++--- .../src/render_resource/pipeline_cache.rs | 5 +++++ .../src/render_resource/pipeline_specializer.rs | 14 ++++++++++++-- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 2d8ee3e158149..9c7488bdbff5b 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -402,7 +402,6 @@ impl InnerMeshVertexBufferLayout { self.attribute_ids.contains(&attribute_id.into()) } - // TODO: maybe these should be hidden to ensure specializers can only use this type to generate VertexBufferLayouts? #[inline] pub fn attribute_ids(&self) -> &[MeshVertexAttributeId] { &self.attribute_ids diff --git a/crates/bevy_render/src/render_resource/bind_group_layout.rs b/crates/bevy_render/src/render_resource/bind_group_layout.rs index d17d639c3a77f..de0ce253f8a5f 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout.rs +++ b/crates/bevy_render/src/render_resource/bind_group_layout.rs @@ -10,6 +10,12 @@ pub struct BindGroupLayout { value: Arc, } +impl PartialEq for BindGroupLayout { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + impl BindGroupLayout { #[inline] pub fn id(&self) -> BindGroupLayoutId { diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 03f2ff233420e..caa2e41eb7b3c 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -87,7 +87,7 @@ impl Deref for ComputePipeline { } /// Describes a render (graphics) pipeline. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct RenderPipelineDescriptor { /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. pub label: Option>, @@ -105,7 +105,7 @@ pub struct RenderPipelineDescriptor { pub fragment: Option, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct VertexState { /// The compiled shader module for this stage. pub shader: Handle, @@ -157,7 +157,7 @@ impl VertexBufferLayout { } /// Describes the fragment process in a render pipeline. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct FragmentState { /// The compiled shader module for this stage. pub shader: Handle, diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 603f62c2b4d56..825a69afb0255 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -244,6 +244,11 @@ impl RenderPipelineCache { &self.pipelines[id.0].state } + #[inline] + pub fn get_descriptor(&self, id: CachedPipelineId) -> &RenderPipelineDescriptor { + &self.pipelines[id.0].descriptor + } + #[inline] pub fn get(&self, id: CachedPipelineId) -> Option<&RenderPipeline> { if let CachedPipelineState::Ok(pipeline) = &self.pipelines[id.0].state { diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index 200bdbbea3aae..517c9333dae30 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -111,8 +111,18 @@ impl SpecializedMeshPipelines { } }; Ok(*entry.insert(match layout_map.entry(key) { - Entry::Occupied(entry) => *entry.into_mut(), - Entry::Vacant(entry) => *entry.insert(cache.queue(descriptor)), + Entry::Occupied(entry) => { + if cfg!(debug_assertions) { + let stored_descriptor = cache.get_descriptor(*entry.get()); + if stored_descriptor != &descriptor { + error!("The cached pipeline descriptor for {} is not equal to the generated descriptor for the given key. This means the SpecializePipeline implementation uses 'unused' MeshVertexBufferLayout information to specialize the pipeline. This is not allowed because it would invalidate the pipeline cache.", std::any::type_name::()); + } + } + *entry.into_mut() + } + Entry::Vacant(entry) => { + *entry.insert(cache.queue(descriptor)) + } })) } }