From e77e5aa2d0b660b841835427cd97260b3f156233 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 23 Feb 2022 23:21:13 +0000 Subject: [PATCH] Mesh vertex buffer layouts (#3959) This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes: * Reworks the `Mesh` type to use the newly added `VertexAttribute` internally * `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat` * `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting" * Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently. * Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key). * Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed` keys and `PreHashMap` (which uses `Hashed` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR. * Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key. For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool! To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now: ```rust impl SpecializedMeshPipeline for MeshPipeline { type Key = MeshPipelineKey; 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 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"); ``` Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`. This is still a draft because I still need to: * Add more docs * Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it. * Consider breaking out the PreHash / hashbrown changes into a separate PR. * Add an example illustrating this change * Verify that the "mesh-specialized pipeline de-duplication code" works properly Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap. Alternative to #3120 Fixes #3030 Co-authored-by: Carter Anderson --- Cargo.toml | 4 + assets/shaders/custom_vertex_attribute.wgsl | 40 ++ crates/bevy_gltf/src/loader.rs | 12 +- crates/bevy_pbr/src/lib.rs | 4 +- crates/bevy_pbr/src/material.rs | 182 +++++---- crates/bevy_pbr/src/pbr_material.rs | 8 +- crates/bevy_pbr/src/render/light.rs | 126 ++---- crates/bevy_pbr/src/render/mesh.rs | 90 +---- crates/bevy_pbr/src/wireframe.rs | 55 ++- crates/bevy_render/src/mesh/mesh/mod.rs | 289 +++++++++++--- 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/bind_group_layout.rs | 6 + .../src/render_resource/pipeline.rs | 38 +- .../src/render_resource/pipeline_cache.rs | 377 +++++++----------- .../render_resource/pipeline_specializer.rs | 115 ++++-- crates/bevy_sprite/src/mesh2d/material.rs | 123 ++++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 90 +---- crates/bevy_sprite/src/render/mod.rs | 39 +- crates/bevy_ui/src/render/pipeline.rs | 34 +- examples/2d/mesh2d_manual.rs | 4 +- examples/README.md | 1 + examples/shader/animate_shader.rs | 44 +- examples/shader/custom_vertex_attribute.rs | 150 +++++++ examples/shader/shader_defs.rs | 38 +- examples/shader/shader_instancing.rs | 49 ++- examples/shader/shader_material_glsl.rs | 8 +- 30 files changed, 1160 insertions(+), 808 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 eb7cab0be8825f..b04683e80bf438 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -447,6 +447,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 00000000000000..3e42b03d710aef --- /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_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index e10741d6e53949..17dd430e74a284 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -125,40 +125,40 @@ 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 // .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_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index f2b2954e73c6df..f264d829d3802c 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, SpecializedRenderPipelines}, + render_resource::{Shader, SpecializedMeshPipelines}, view::VisibilitySystems, RenderApp, RenderStage, }; @@ -176,7 +176,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 774cb0ea9241d9..d01e4f79a557ce 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::{ @@ -23,13 +23,14 @@ use bevy_render::{ SetItemPipeline, TrackedRenderPass, }, render_resource::{ - BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader, - SpecializedRenderPipeline, SpecializedRenderPipelines, + BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, view::{ExtractedView, Msaa, VisibleEntities}, RenderApp, RenderStage, }; +use bevy_utils::tracing::error; use std::hash::Hash; use std::marker::PhantomData; @@ -72,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 { @@ -81,7 +92,13 @@ 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, + ) -> Result<(), SpecializedMeshPipelineError> { + ::specialize(descriptor, layout) + } #[inline] fn bind_group(material: &::PreparedAsset) -> &BindGroup { @@ -130,7 +147,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, + ) -> Result<(), SpecializedMeshPipelineError>; /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial::bind_group_layout`]. fn bind_group(material: &::PreparedAsset) -> &BindGroup; @@ -188,12 +209,18 @@ 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::); } } } +#[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, @@ -202,11 +229,15 @@ pub struct MaterialPipeline { marker: PhantomData, } -impl SpecializedRenderPipeline for MaterialPipeline { - type Key = (MeshPipelineKey, M::Key); +impl SpecializedMeshPipeline for MaterialPipeline { + type Key = MaterialPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key.0); + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + 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(); } @@ -220,8 +251,8 @@ impl SpecializedRenderPipeline for MaterialPipeline { self.mesh_pipeline.mesh_layout.clone(), ]); - M::specialize(key.1, &mut descriptor); - descriptor + M::specialize(&mut descriptor, key.material_key, layout)?; + Ok(descriptor) } } @@ -275,8 +306,8 @@ pub fn queue_material_meshes( alpha_mask_draw_functions: Res>, transparent_draw_functions: Res>, material_pipeline: Res>, - mut pipelines: ResMut>>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>>, + mut pipeline_cache: ResMut, msaa: Res, render_meshes: Res>, render_materials: Res>, @@ -307,72 +338,81 @@ 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 material_key = M::key(material); + + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &material_pipeline, + MaterialPipelineKey { + mesh_key, + material_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 + 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 6be68dfdd3e646..eaf45397a78a42 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, + ) -> Result<(), SpecializedMeshPipelineError> { if key.normal_map { descriptor .fragment @@ -350,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_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index a6bd7937503b74..562c9b6d2031c0 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::{ @@ -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)] @@ -214,7 +217,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 +246,23 @@ impl ShadowPipelineKey { } } -impl SpecializedRenderPipeline 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, - }, - ], - ) - }; - RenderPipelineDescriptor { + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + let vertex_buffer_layout = + layout.get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)])?; + + Ok(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], }, fragment: None, layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]), @@ -344,7 +293,7 @@ impl SpecializedRenderPipeline for ShadowPipeline { }), multisample: MultisampleState::default(), label: Some("shadow_pipeline".into()), - } + }) } } @@ -1090,8 +1039,8 @@ pub fn queue_shadows( shadow_pipeline: Res, casting_meshes: Query<&Handle, Without>, render_meshes: Res>, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, view_lights: Query<&ViewLightEntities>, mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase)>, point_light_entities: Query<&CubemapVisibleEntities, With>, @@ -1120,23 +1069,32 @@ 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, + ); + + 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, + 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 7cb418ed9abcf3..4c34a462eb0f73 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}, @@ -366,8 +366,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; } @@ -409,71 +408,28 @@ impl MeshPipelineKey { } } -impl SpecializedRenderPipeline 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, + ) -> Result { + 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)?; + let (label, blend, depth_write_enabled); if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) { label = "transparent_mesh_pipeline".into(); @@ -493,16 +449,12 @@ impl SpecializedRenderPipeline 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(), 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: MESH_SHADER_HANDLE.typed::(), @@ -546,7 +498,7 @@ impl SpecializedRenderPipeline 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 74bbd3fa3530d3..7a65ae267ef99f 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -5,17 +5,20 @@ use bevy_asset::{load_internal_asset, 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, RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, + SpecializedMeshPipelines, +}; use bevy_render::{ mesh::Mesh, render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, - render_resource::{ - PipelineCache, Shader, SpecializedRenderPipeline, SpecializedRenderPipelines, - }, + render_resource::{RenderPipelineCache, Shader}, view::{ExtractedView, Msaa}, RenderApp, RenderStage, }; +use bevy_utils::tracing::error; pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766); @@ -38,7 +41,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); @@ -82,27 +85,32 @@ impl FromWorld for WireframePipeline { } } -impl SpecializedRenderPipeline 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, + ) -> 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) } } #[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 pipeline_cache: ResMut, + mut specialized_pipelines: ResMut>, msaa: Res, mut material_meshes: QuerySet<( QueryState<(Entity, &Handle, &MeshUniform)>, @@ -114,7 +122,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); @@ -122,15 +130,24 @@ 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); + 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, - ), + 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 317ce49a309022..9c7488bdbff5b0 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -3,17 +3,19 @@ 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 thiserror::Error; 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 +27,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, } @@ -44,29 +46,39 @@ 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 { - /// 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::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`] - pub const ATTRIBUTE_NORMAL: &'static str = "Vertex_Normal"; + /// 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::insert_attribute`] + 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: &'static str = "Vertex_Tangent"; + pub const ATTRIBUTE_TANGENT: MeshVertexAttribute = + MeshVertexAttribute::new("Vertex_Tangent", 3, VertexFormat::Float32x4); - /// 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"; + /// 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`] - pub const ATTRIBUTE_JOINT_WEIGHT: &'static str = "Vertex_JointWeight"; - /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_JOINT_INDEX: &'static str = "Vertex_JointIndex"; + /// 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::insert_attribute`] + 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 +98,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 +167,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 +200,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 +220,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 +229,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 +269,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 +322,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 +351,131 @@ 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, self.name) + } +} + +#[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()) + } + + #[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], + ) -> Result { + 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 Err(MissingVertexAttributeError { + id: attribute_descriptor.id, + name: attribute_descriptor.name, + pipeline_type: None, + }); + } + } + + Ok(VertexBufferLayout { + array_stride: self.layout.array_stride, + step_mode: self.layout.step_mode, + attributes, + }) + } +} + +#[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, name: &'static str) -> Self { + Self { + shader_location, + id, + name, + } + } +} + +#[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 +683,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 +751,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 +806,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 7b6d268a9a9a62..5459e8d47fe10e 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 ff2068469ec51d..7b0a31962205d5 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 9f128d2ac4c43b..31f9f8b5d4bd25 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 abe63e00c5c277..4fdc9b13181d53 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 571c3123d9aa19..ccce754e564f2c 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/bind_group_layout.rs b/crates/bevy_render/src/render_resource/bind_group_layout.rs index d17d639c3a77f6..de0ce253f8a5fc 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 ab57c62cd06896..04e2c772d01525 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. @@ -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, @@ -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,8 +128,36 @@ 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`](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`](VertexFormat) (in bytes). + pub fn from_vertex_formats>( + step_mode: VertexStepMode, + vertex_formats: T, + ) -> Self { + let mut offset = 0; + let mut attributes = Vec::new(); + for (shader_location, format) in vertex_formats.into_iter().enumerate() { + attributes.push(VertexAttribute { + format, + offset, + shader_location: shader_location as u32, + }); + offset += format.size(); + } + + VertexBufferLayout { + array_stride: offset, + step_mode, + attributes, + } + } +} + /// 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 618b36479158c9..825a69afb0255e 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -1,10 +1,8 @@ -use crate::render_resource::{ComputePipeline, RawComputePipelineDescriptor}; use crate::{ render_resource::{ - AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ComputePipelineDescriptor, - ProcessShaderError, ProcessedShader, RawFragmentState, RawRenderPipelineDescriptor, - RawVertexState, RenderPipeline, RenderPipelineDescriptor, Shader, ShaderImport, - ShaderProcessor, ShaderReflectError, + AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ProcessShaderError, + RawFragmentState, RawRenderPipelineDescriptor, RawVertexState, RenderPipeline, + RenderPipelineDescriptor, Shader, ShaderImport, ShaderProcessor, ShaderReflectError, }, renderer::RenderDevice, RenderWorld, @@ -15,17 +13,16 @@ 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}; -enum PipelineDescriptor { - RenderPipelineDescriptor(RenderPipelineDescriptor), - ComputePipelineDescriptor(ComputePipelineDescriptor), -} +use super::ProcessedShader; -#[derive(Debug)] -pub enum Pipeline { - RenderPipeline(RenderPipeline), - ComputePipeline(ComputePipeline), +#[derive(Default)] +pub struct ShaderData { + pipelines: HashSet, + processed_shaders: HashMap, Arc>, + resolved_imports: HashMap>, + dependents: HashSet>, } #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] @@ -35,38 +32,6 @@ impl CachedPipelineId { pub const INVALID: Self = CachedPipelineId(usize::MAX); } -struct CachedPipeline { - descriptor: PipelineDescriptor, - state: CachedPipelineState, -} - -#[derive(Debug)] -pub enum CachedPipelineState { - Queued, - Ok(Pipeline), - Err(PipelineCacheError), -} - -impl CachedPipelineState { - pub fn unwrap(&self) -> &Pipeline { - match self { - CachedPipelineState::Ok(pipeline) => pipeline, - CachedPipelineState::Queued => { - panic!("Pipeline has not been compiled yet. It is still in the 'Queued' state.") - } - CachedPipelineState::Err(err) => panic!("{}", err), - } - } -} - -#[derive(Default)] -pub struct ShaderData { - pipelines: HashSet, - processed_shaders: HashMap, Arc>, - resolved_imports: HashMap>, - dependents: HashSet>, -} - #[derive(Default)] struct ShaderCache { data: HashMap, ShaderData>, @@ -83,11 +48,11 @@ impl ShaderCache { pipeline: CachedPipelineId, handle: &Handle, shader_defs: &[String], - ) -> Result, PipelineCacheError> { + ) -> Result, RenderPipelineError> { let shader = self .shaders .get(handle) - .ok_or_else(|| PipelineCacheError::ShaderNotLoaded(handle.clone_weak()))?; + .ok_or_else(|| RenderPipelineError::ShaderNotLoaded(handle.clone_weak()))?; let data = self.data.entry(handle.clone_weak()).or_default(); let n_asset_imports = shader .imports() @@ -99,7 +64,7 @@ impl ShaderCache { .filter(|import| matches!(import, ShaderImport::AssetPath(_))) .count(); if n_asset_imports != n_resolved_asset_imports { - return Err(PipelineCacheError::ShaderImportNotYetAvailable); + return Err(RenderPipelineError::ShaderImportNotYetAvailable); } data.pipelines.insert(pipeline); @@ -117,7 +82,7 @@ impl ShaderCache { let module_descriptor = match processed.get_module_descriptor() { Ok(module_descriptor) => module_descriptor, Err(err) => { - return Err(PipelineCacheError::AsModuleDescriptorError(err, processed)); + return Err(RenderPipelineError::AsModuleDescriptorError(err, processed)); } }; entry.insert(Arc::new( @@ -217,7 +182,7 @@ impl LayoutCache { } } -pub struct PipelineCache { +pub struct RenderPipelineCache { layout_cache: LayoutCache, shader_cache: ShaderCache, device: RenderDevice, @@ -225,7 +190,45 @@ pub struct PipelineCache { waiting_pipelines: HashSet, } -impl PipelineCache { +struct CachedPipeline { + descriptor: RenderPipelineDescriptor, + state: CachedPipelineState, +} + +#[derive(Debug)] +pub enum CachedPipelineState { + Queued, + Ok(RenderPipeline), + Err(RenderPipelineError), +} + +impl CachedPipelineState { + pub fn unwrap(&self) -> &RenderPipeline { + match self { + CachedPipelineState::Ok(pipeline) => pipeline, + CachedPipelineState::Queued => { + panic!("Pipeline has not been compiled yet. It is still in the 'Queued' state.") + } + CachedPipelineState::Err(err) => panic!("{}", err), + } + } +} + +#[derive(Error, Debug)] +pub enum RenderPipelineError { + #[error( + "Pipeline cound not be compiled because the following shader is not loaded yet: {0:?}" + )] + ShaderNotLoaded(Handle), + #[error(transparent)] + ProcessShaderError(#[from] ProcessShaderError), + #[error("{0}")] + AsModuleDescriptorError(AsModuleDescriptorError, ProcessedShader), + #[error("Shader import not yet available.")] + ShaderImportNotYetAvailable, +} + +impl RenderPipelineCache { pub fn new(device: RenderDevice) -> Self { Self { device, @@ -242,47 +245,23 @@ impl PipelineCache { } #[inline] - pub fn get_render_pipeline(&self, id: CachedPipelineId) -> Option<&RenderPipeline> { - if let CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) = - &self.pipelines[id.0].state - { - Some(pipeline) - } else { - None - } + pub fn get_descriptor(&self, id: CachedPipelineId) -> &RenderPipelineDescriptor { + &self.pipelines[id.0].descriptor } #[inline] - pub fn get_compute_pipeline(&self, id: CachedPipelineId) -> Option<&ComputePipeline> { - if let CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) = - &self.pipelines[id.0].state - { + pub fn get(&self, id: CachedPipelineId) -> Option<&RenderPipeline> { + if let CachedPipelineState::Ok(pipeline) = &self.pipelines[id.0].state { Some(pipeline) } else { None } } - pub fn queue_render_pipeline( - &mut self, - descriptor: RenderPipelineDescriptor, - ) -> CachedPipelineId { + pub fn queue(&mut self, descriptor: RenderPipelineDescriptor) -> CachedPipelineId { let id = CachedPipelineId(self.pipelines.len()); self.pipelines.push(CachedPipeline { - descriptor: PipelineDescriptor::RenderPipelineDescriptor(descriptor), - state: CachedPipelineState::Queued, - }); - self.waiting_pipelines.insert(id); - id - } - - pub fn queue_compute_pipeline( - &mut self, - descriptor: ComputePipelineDescriptor, - ) -> CachedPipelineId { - let id = CachedPipelineId(self.pipelines.len()); - self.pipelines.push(CachedPipeline { - descriptor: PipelineDescriptor::ComputePipelineDescriptor(descriptor), + descriptor, state: CachedPipelineState::Queued, }); self.waiting_pipelines.insert(id); @@ -305,141 +284,23 @@ impl PipelineCache { } } - fn process_render_pipeline( - &mut self, - id: CachedPipelineId, - descriptor: &RenderPipelineDescriptor, - ) -> CachedPipelineState { - let vertex_module = match self.shader_cache.get( - &self.device, - id, - &descriptor.vertex.shader, - &descriptor.vertex.shader_defs, - ) { - Ok(module) => module, - Err(err) => { - return CachedPipelineState::Err(err); - } - }; - - let fragment_data = if let Some(fragment) = &descriptor.fragment { - let fragment_module = match self.shader_cache.get( - &self.device, - id, - &fragment.shader, - &fragment.shader_defs, - ) { - Ok(module) => module, - Err(err) => { - return CachedPipelineState::Err(err); - } - }; - Some(( - fragment_module, - fragment.entry_point.deref(), - &fragment.targets, - )) - } else { - None - }; - - let vertex_buffer_layouts = descriptor - .vertex - .buffers - .iter() - .map(|layout| VertexBufferLayout { - array_stride: layout.array_stride, - attributes: &layout.attributes, - step_mode: layout.step_mode, - }) - .collect::>(); - - let layout = if let Some(layout) = &descriptor.layout { - Some(self.layout_cache.get(&self.device, layout)) - } else { - None - }; - - let descriptor = RawRenderPipelineDescriptor { - multiview: None, - depth_stencil: descriptor.depth_stencil.clone(), - label: descriptor.label.as_deref(), - layout, - multisample: descriptor.multisample, - primitive: descriptor.primitive, - vertex: RawVertexState { - buffers: &vertex_buffer_layouts, - entry_point: descriptor.vertex.entry_point.deref(), - module: &vertex_module, - }, - fragment: fragment_data - .as_ref() - .map(|(module, entry_point, targets)| RawFragmentState { - entry_point, - module, - targets, - }), - }; - - let pipeline = self.device.create_render_pipeline(&descriptor); - - CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) - } - - fn process_compute_pipeline( - &mut self, - id: CachedPipelineId, - descriptor: &ComputePipelineDescriptor, - ) -> CachedPipelineState { - let compute_module = match self.shader_cache.get( - &self.device, - id, - &descriptor.shader, - &descriptor.shader_defs, - ) { - Ok(module) => module, - Err(err) => { - return CachedPipelineState::Err(err); - } - }; - - let layout = if let Some(layout) = &descriptor.layout { - Some(self.layout_cache.get(&self.device, layout)) - } else { - None - }; - - let descriptor = RawComputePipelineDescriptor { - label: descriptor.label.as_deref(), - layout, - module: &compute_module, - entry_point: descriptor.entry_point.as_ref(), - }; - - let pipeline = self.device.create_compute_pipeline(&descriptor); - - CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) - } - pub fn process_queue(&mut self) { - let waiting_pipelines = std::mem::take(&mut self.waiting_pipelines); - let mut pipelines = std::mem::take(&mut self.pipelines); - - for id in waiting_pipelines { - let pipeline = &mut pipelines[id.0]; - match &pipeline.state { + let pipelines = std::mem::take(&mut self.waiting_pipelines); + for id in pipelines { + let state = &mut self.pipelines[id.0]; + match &state.state { CachedPipelineState::Ok(_) => continue, CachedPipelineState::Queued => {} CachedPipelineState::Err(err) => { match err { - PipelineCacheError::ShaderNotLoaded(_) - | PipelineCacheError::ShaderImportNotYetAvailable => { /* retry */ } + RenderPipelineError::ShaderNotLoaded(_) + | RenderPipelineError::ShaderImportNotYetAvailable => { /* retry */ } // shader could not be processed ... retrying won't help - PipelineCacheError::ProcessShaderError(err) => { + RenderPipelineError::ProcessShaderError(err) => { error!("failed to process shader: {}", err); continue; } - PipelineCacheError::AsModuleDescriptorError(err, source) => { + RenderPipelineError::AsModuleDescriptorError(err, source) => { log_shader_error(source, err); continue; } @@ -447,21 +308,85 @@ impl PipelineCache { } } - pipeline.state = match &pipeline.descriptor { - PipelineDescriptor::RenderPipelineDescriptor(descriptor) => { - self.process_render_pipeline(id, descriptor) - } - PipelineDescriptor::ComputePipelineDescriptor(descriptor) => { - self.process_compute_pipeline(id, descriptor) + let descriptor = &state.descriptor; + let vertex_module = match self.shader_cache.get( + &self.device, + id, + &descriptor.vertex.shader, + &descriptor.vertex.shader_defs, + ) { + Ok(module) => module, + Err(err) => { + state.state = CachedPipelineState::Err(err); + self.waiting_pipelines.insert(id); + continue; } }; - if let CachedPipelineState::Err(_) = pipeline.state { - self.waiting_pipelines.insert(id); - } - } + let fragment_data = if let Some(fragment) = &descriptor.fragment { + let fragment_module = match self.shader_cache.get( + &self.device, + id, + &fragment.shader, + &fragment.shader_defs, + ) { + Ok(module) => module, + Err(err) => { + state.state = CachedPipelineState::Err(err); + self.waiting_pipelines.insert(id); + continue; + } + }; + Some(( + fragment_module, + fragment.entry_point.deref(), + &fragment.targets, + )) + } else { + None + }; + + let vertex_buffer_layouts = descriptor + .vertex + .buffers + .iter() + .map(|layout| RawVertexBufferLayout { + array_stride: layout.array_stride, + attributes: &layout.attributes, + step_mode: layout.step_mode, + }) + .collect::>(); - self.pipelines = pipelines; + let layout = if let Some(layout) = &descriptor.layout { + Some(self.layout_cache.get(&self.device, layout)) + } else { + None + }; + + let descriptor = RawRenderPipelineDescriptor { + multiview: None, + depth_stencil: descriptor.depth_stencil.clone(), + label: descriptor.label.as_deref(), + layout, + multisample: descriptor.multisample, + primitive: descriptor.primitive, + vertex: RawVertexState { + buffers: &vertex_buffer_layouts, + entry_point: descriptor.vertex.entry_point.deref(), + module: &vertex_module, + }, + fragment: fragment_data + .as_ref() + .map(|(module, entry_point, targets)| RawFragmentState { + entry_point, + module, + targets, + }), + }; + + let pipeline = self.device.create_render_pipeline(&descriptor); + state.state = CachedPipelineState::Ok(pipeline); + } } pub(crate) fn process_pipeline_queue_system(mut cache: ResMut) { @@ -578,20 +503,6 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) { } } -#[derive(Error, Debug)] -pub enum PipelineCacheError { - #[error( - "Pipeline cound not be compiled because the following shader is not loaded yet: {0:?}" - )] - ShaderNotLoaded(Handle), - #[error(transparent)] - ProcessShaderError(#[from] ProcessShaderError), - #[error("{0}")] - AsModuleDescriptorError(AsModuleDescriptorError, ProcessedShader), - #[error("Shader import not yet available.")] - ShaderImportNotYetAvailable, -} - struct ErrorSources<'a> { current: Option<&'a (dyn std::error::Error + 'static)>, } diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index e9c69d0255233d..517c9333dae309 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -1,14 +1,26 @@ -use crate::render_resource::{ - CachedPipelineId, ComputePipelineDescriptor, PipelineCache, RenderPipelineDescriptor, +use crate::{ + mesh::{InnerMeshVertexBufferLayout, MeshVertexBufferLayout, MissingVertexAttributeError}, + render_resource::{ + CachedPipelineId, RenderPipelineCache, RenderPipelineDescriptor, VertexBufferLayout, + }, }; -use bevy_utils::HashMap; +use bevy_utils::{ + 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 SpecializedRenderPipelines { +pub struct SpecializedPipelines { cache: HashMap, } -impl Default for SpecializedRenderPipelines { +impl Default for SpecializedPipelines { fn default() -> Self { Self { cache: Default::default(), @@ -16,52 +28,103 @@ impl Default for SpecializedRenderPipelines { } } -impl SpecializedRenderPipelines { +impl SpecializedPipelines { pub fn specialize( &mut self, - cache: &mut PipelineCache, + cache: &mut RenderPipelineCache, specialize_pipeline: &S, key: S::Key, ) -> CachedPipelineId { *self.cache.entry(key.clone()).or_insert_with(|| { let descriptor = specialize_pipeline.specialize(key); - cache.queue_render_pipeline(descriptor) + cache.queue(descriptor) }) } } -pub trait SpecializedRenderPipeline { +#[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 SpecializedComputePipelines { - cache: HashMap, +pub struct SpecializedMeshPipelines { + mesh_layout_cache: PreHashMap>, + vertex_layout_cache: HashMap>, } -impl Default for SpecializedComputePipelines { +impl Default for SpecializedMeshPipelines { fn default() -> Self { Self { - cache: Default::default(), + mesh_layout_cache: Default::default(), + vertex_layout_cache: Default::default(), } } } -impl SpecializedComputePipelines { +impl SpecializedMeshPipelines { + #[inline] pub fn specialize( &mut self, - cache: &mut PipelineCache, + cache: &mut RenderPipelineCache, specialize_pipeline: &S, key: S::Key, - ) -> CachedPipelineId { - *self.cache.entry(key.clone()).or_insert_with(|| { - let descriptor = specialize_pipeline.specialize(key); - cache.queue_compute_pipeline(descriptor) - }) + layout: &MeshVertexBufferLayout, + ) -> Result { + let map = self + .mesh_layout_cache + .get_or_insert_with(layout, Default::default); + 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) => { + 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)) + } + })) + } + } } } - -pub trait SpecializedComputePipeline { - type Key: Clone + Hash + PartialEq + Eq; - fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor; -} diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 89a9d028730a15..630af30155cb0e 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -11,8 +11,9 @@ use bevy_ecs::{ }, world::FromWorld, }; +use bevy_log::error; use bevy_render::{ - mesh::Mesh, + mesh::{Mesh, MeshVertexBufferLayout}, render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}, render_component::ExtractComponentPlugin, render_phase::{ @@ -20,8 +21,8 @@ use bevy_render::{ SetItemPipeline, TrackedRenderPass, }, render_resource::{ - BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader, - SpecializedRenderPipeline, SpecializedRenderPipelines, + BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, view::{ComputedVisibility, Msaa, Visibility, VisibleEntities}, @@ -69,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 { @@ -78,7 +89,13 @@ 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, + ) -> Result<(), SpecializedMeshPipelineError> { + ::specialize(descriptor, layout) + } #[inline] fn bind_group(material: &::PreparedAsset) -> &BindGroup { @@ -122,7 +139,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, + ) -> Result<(), SpecializedMeshPipelineError>; /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial2d::bind_group_layout`]. fn bind_group(material: &::PreparedAsset) -> &BindGroup; @@ -172,7 +193,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 +207,21 @@ pub struct Material2dPipeline { marker: PhantomData, } -impl SpecializedRenderPipeline for Material2dPipeline { - type Key = (Mesh2dPipelineKey, M::Key); +#[derive(Eq, PartialEq, Clone, Hash)] +pub struct Material2dKey { + mesh_key: Mesh2dPipelineKey, + material_key: T, +} + +impl SpecializedMeshPipeline for Material2dPipeline { + type Key = Material2dKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh2d_pipeline.specialize(key.0); + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + 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(); } @@ -204,8 +235,8 @@ impl SpecializedRenderPipeline for Material2dPipeline< self.mesh2d_pipeline.mesh_layout.clone(), ]); - M::specialize(key.1, &mut descriptor); - descriptor + M::specialize(key.material_key, &mut descriptor, layout)?; + Ok(descriptor) } } @@ -259,8 +290,8 @@ impl EntityRenderCommand pub fn queue_material2d_meshes( transparent_draw_functions: Res>, material2d_pipeline: Res>, - mut pipelines: ResMut>>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>>, + mut pipeline_cache: ResMut, msaa: Res, render_meshes: Res>, render_materials: Res>, @@ -276,42 +307,50 @@ 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 mesh_key = msaa_key + | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); + + let material_key = M::key(material2d); + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &material2d_pipeline, + Material2dKey { + mesh_key, + material_key, + }, + &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, + 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 32d4e650ccc9ed..5f9a54c5653258 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}, @@ -62,7 +62,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); @@ -232,7 +232,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; } @@ -274,84 +273,37 @@ impl Mesh2dPipelineKey { } } -impl SpecializedRenderPipeline 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, + ) -> Result { + 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")); - RenderPipelineDescriptor { + let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; + + Ok(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::(), @@ -380,7 +332,7 @@ impl SpecializedRenderPipeline for Mesh2dPipeline { alpha_to_coverage_enabled: false, }, label: Some("transparent_mesh2d_pipeline".into()), - } + }) } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index a7ac40288f5716..269c4be5ac45d9 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -113,31 +113,24 @@ impl SpecializedRenderPipeline 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 SpecializedRenderPipeline 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], }, 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 7fa13d2740fa46..f3c3dee5beec03 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -66,29 +66,17 @@ impl SpecializedRenderPipeline 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 SpecializedRenderPipeline 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], }, fragment: Some(FragmentState { shader: super::UI_SHADER_HANDLE.typed::(), diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 6d48ae4c86cf2b..7a85677818c99b 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -69,11 +69,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/README.md b/examples/README.md index aeaff8c7845f26..d147b0a79c773c 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/animate_shader.rs b/examples/shader/animate_shader.rs index a3942058ef7ef4..b4266bcc8f00da 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) @@ -90,13 +92,15 @@ 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, msaa: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, - material_meshes: Query<(Entity, &MeshUniform), (With>, With)>, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, + 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 +110,22 @@ 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) + .unwrap(); + transparent_phase.add(Transparent3d { + entity, + pipeline, + draw_function: draw_custom, + distance: view_row_2.dot(mesh_uniform.transform.col(3)), + }); + } } } } @@ -207,11 +215,15 @@ impl FromWorld for CustomPipeline { } } -impl SpecializedRenderPipeline 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, + ) -> 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![ @@ -219,7 +231,7 @@ impl SpecializedRenderPipeline for CustomPipeline { self.mesh_pipeline.mesh_layout.clone(), self.time_bind_group_layout.clone(), ]); - descriptor + Ok(descriptor) } } diff --git a/examples/shader/custom_vertex_attribute.rs b/examples/shader/custom_vertex_attribute.rs new file mode 100644 index 00000000000000..deb56d3c524d3d --- /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(); +} + +// 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); + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + 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_defs.rs b/examples/shader/shader_defs.rs index cbccd7e3c0f951..187ec8e51d49d1 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::{ - PipelineCache, RenderPipelineDescriptor, SpecializedRenderPipeline, - SpecializedRenderPipelines, + RenderPipelineCache, RenderPipelineDescriptor, SpecializedMeshPipeline, + SpecializedMeshPipelineError, 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 SpecializedRenderPipeline 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, + ) -> 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); + 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(); @@ -116,7 +121,7 @@ impl SpecializedRenderPipeline for IsRedPipeline { self.mesh_pipeline.view_layout.clone(), self.mesh_pipeline.mesh_layout.clone(), ]); - descriptor + Ok(descriptor) } } @@ -133,8 +138,8 @@ fn queue_custom( render_meshes: Res>, custom_pipeline: Res, msaa: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, material_meshes: Query<(Entity, &Handle, &MeshUniform, &IsRed)>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) { @@ -142,15 +147,22 @@ 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, + ) + .unwrap(); transparent_phase.add(Transparent3d { entity, pipeline, diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 7b8ccc7e71e979..f0544c0234c5c1 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); } @@ -95,14 +95,16 @@ struct InstanceData { color: [f32; 4], } +#[allow(clippy::too_many_arguments)] fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, msaa: Res, - mut pipelines: ResMut>, - mut pipeline_cache: 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 +114,25 @@ 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) + .unwrap(); + transparent_phase.add(Transparent3d { + entity, + pipeline, + draw_function: draw_custom, + distance: view_row_2.dot(mesh_uniform.transform.col(3)), + }); + } } } } @@ -175,11 +182,15 @@ impl FromWorld for CustomPipeline { } } -impl SpecializedRenderPipeline 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, + ) -> 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, @@ -203,7 +214,7 @@ impl SpecializedRenderPipeline for CustomPipeline { self.mesh_pipeline.mesh_layout.clone(), ]); - descriptor + Ok(descriptor) } } diff --git a/examples/shader/shader_material_glsl.rs b/examples/shader/shader_material_glsl.rs index d1bb8b4b208187..2f9dab493e0f7f 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,9 +96,14 @@ 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, + ) -> 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> {