From a159f2d09c65e30545be0df64150fa688417e0d5 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Fri, 12 Nov 2021 21:20:13 -0800 Subject: [PATCH 1/2] Add dynamic vertex buffer layouts to resolve sorting issues - Vertex buffer layouts are set on `Mesh` for shader specialization. `Mesh` knows how to specialize its own vertex buffer because it owns the vertex data. - The `RenderAsset` implementation for `Mesh` caches the vertex buffer layout and adds a cache key to the `GpuMesh` render asset. The `MeshPipeline` can lookup the vertex buffer layout by composing `VertexLayoutKey` within `MeshPipelineKey`. - `SpritePipeline` and `UiPipeline` populates the vertex layout cache for themselves.. - `SpecializedPipeline::specialize` now takes a reference to the `RenderPipelineCache`, which is the best way I could find to get a reference to the cache for vertex layout lookups. - Discussion: https://discord.com/channels/691052431525675048/743663924229963868/908484759833960489 --- crates/bevy_gltf/src/loader.rs | 4 +- crates/bevy_pbr/src/material.rs | 34 ++-- crates/bevy_pbr/src/render/light.rs | 85 ++-------- crates/bevy_pbr/src/render/mesh.rs | 101 ++++-------- crates/bevy_pbr/src/wireframe.rs | 24 ++- crates/bevy_render/src/mesh/mesh/mod.rs | 146 +++++++++++++----- .../src/render_resource/pipeline.rs | 107 ++++++++++++- .../src/render_resource/pipeline_cache.rs | 32 +++- .../render_resource/pipeline_specializer.rs | 4 +- crates/bevy_sprite/src/render/mod.rs | 67 +++++--- crates/bevy_ui/src/render/pipeline.rs | 56 ++++--- examples/shader/shader_defs.rs | 27 ++-- 12 files changed, 409 insertions(+), 278 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 0669ca833a629..42508ea315fff 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -18,7 +18,7 @@ use bevy_render::{ mesh::{Indices, Mesh, VertexAttributeValues}, primitives::{Aabb, Frustum}, render_resource::{ - AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat, + AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat, VertexFormat, }, texture::{Image, ImageType, TextureError}, view::VisibleEntities, @@ -138,6 +138,8 @@ async fn load_gltf<'a, 'b>( .read_tangents() .map(|v| VertexAttributeValues::Float32x4(v.collect())) { + mesh.vertex_layout_mut() + .push(Mesh::ATTRIBUTE_TANGENT, VertexFormat::Float32x4); mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index d1c521397511e..8a6025cda2fe6 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,6 +1,6 @@ use crate::{ - AlphaMode, DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, - SetMeshViewBindGroup, + AlphaMode, DrawMesh, MeshPipeline, MeshPipelineFlags, MeshPipelineKey, MeshUniform, + SetMeshBindGroup, SetMeshViewBindGroup, }; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Asset, AssetServer, Handle}; @@ -205,8 +205,8 @@ pub struct MaterialPipeline { impl SpecializedPipeline for MaterialPipeline { type Key = (MeshPipelineKey, M::Key); - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key.0); + fn specialize(&self, cache: &RenderPipelineCache, key: Self::Key) -> RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(cache, key.0); if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } @@ -307,27 +307,33 @@ 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 mesh_flags = MeshPipelineFlags::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; - } - mesh_key |= - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + if let (Some(material), Some(mesh)) = ( + render_materials.get(material_handle), + render_meshes.get(mesh_handle), + ) { + let mut mesh_flags = mesh_flags; + if mesh.has_tangents { + mesh_flags |= MeshPipelineFlags::VERTEX_TANGENTS; } + mesh_flags |= + MeshPipelineFlags::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 + mesh_flags |= MeshPipelineFlags::TRANSPARENT_MAIN_PASS; } + let mesh_key = MeshPipelineKey { + vertex_layout_key: mesh.vertex_layout_key, + flags: mesh_flags, + }; let specialized_key = M::key(material); + let pipeline_id = pipelines.specialize( &mut pipeline_cache, &material_pipeline, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 0b7cbe8a12197..5819592c84654 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, VertexLayoutKey}, render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::{ @@ -209,84 +209,21 @@ impl FromWorld for ShadowPipeline { } } -bitflags::bitflags! { - #[repr(transparent)] - pub struct ShadowPipelineKey: u32 { - const NONE = 0; - const VERTEX_TANGENTS = (1 << 0); - } -} +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct ShadowPipelineKey(pub VertexLayoutKey); impl SpecializedPipeline for ShadowPipeline { type Key = ShadowPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let (vertex_array_stride, vertex_attributes) = - if key.contains(ShadowPipelineKey::VERTEX_TANGENTS) { - ( - 48, - vec![ - // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 12, - shader_location: 0, - }, - // Normal - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 1, - }, - // Uv (GOTCHA! uv is no longer third in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 40, - shader_location: 2, - }, - // Tangent - VertexAttribute { - format: VertexFormat::Float32x4, - offset: 24, - shader_location: 3, - }, - ], - ) - } else { - ( - 32, - vec![ - // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 12, - shader_location: 0, - }, - // Normal - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 1, - }, - // Uv - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 24, - shader_location: 2, - }, - ], - ) - }; + fn specialize(&self, cache: &RenderPipelineCache, key: Self::Key) -> RenderPipelineDescriptor { + let vertex_layout = cache.vertex_layout_cache.get(&key.0).unwrap(); + 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_layout.clone()], }, fragment: None, layout: Some(vec![self.view_layout.clone(), self.mesh_layout.clone()]), @@ -1087,13 +1024,9 @@ 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; - } - } + let mesh = render_meshes.get(mesh_handle).unwrap(); + let key = ShadowPipelineKey(mesh.vertex_layout_key); let pipeline_id = pipelines.specialize(&mut pipeline_cache, &shadow_pipeline, key); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index fdd3b9c585296..18fbbd57fb90b 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; use bevy_reflect::TypeUuid; use bevy_render::{ - mesh::{GpuBufferInfo, Mesh}, + mesh::{GpuBufferInfo, Mesh, VertexLayoutKey}, render_asset::RenderAssets, render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, @@ -356,20 +356,26 @@ impl MeshPipeline { } } +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct MeshPipelineKey { + pub vertex_layout_key: VertexLayoutKey, + pub flags: MeshPipelineFlags, +} + bitflags::bitflags! { #[repr(transparent)] // NOTE: Apparently quadro drivers support up to 64x MSAA. /// MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA. - pub struct MeshPipelineKey: u32 { + pub struct MeshPipelineFlags: u32 { const NONE = 0; const VERTEX_TANGENTS = (1 << 0); const TRANSPARENT_MAIN_PASS = (1 << 1); - 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; + const MSAA_RESERVED_BITS = MeshPipelineFlags::MSAA_MASK_BITS << MeshPipelineFlags::MSAA_SHIFT_BITS; + const PRIMITIVE_TOPOLOGY_RESERVED_BITS = MeshPipelineFlags::PRIMITIVE_TOPOLOGY_MASK_BITS << MeshPipelineFlags::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } } -impl MeshPipelineKey { +impl MeshPipelineFlags { const MSAA_MASK_BITS: u32 = 0b111111; const MSAA_SHIFT_BITS: u32 = 32 - 6; const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; @@ -377,7 +383,7 @@ impl MeshPipelineKey { pub fn from_msaa_samples(msaa_samples: u32) -> Self { let msaa_bits = ((msaa_samples - 1) & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; - MeshPipelineKey::from_bits(msaa_bits).unwrap() + MeshPipelineFlags::from_bits(msaa_bits).unwrap() } pub fn msaa_samples(&self) -> u32 { @@ -388,7 +394,7 @@ impl MeshPipelineKey { let primitive_topology_bits = ((primitive_topology as u32) & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; - MeshPipelineKey::from_bits(primitive_topology_bits).unwrap() + MeshPipelineFlags::from_bits(primitive_topology_bits).unwrap() } pub fn primitive_topology(&self) -> PrimitiveTopology { @@ -408,70 +414,19 @@ impl MeshPipelineKey { impl SpecializedPipeline 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, cache: &RenderPipelineCache, key: Self::Key) -> RenderPipelineDescriptor { + let vertex_layout = cache + .vertex_layout_cache + .get(&key.vertex_layout_key) + .unwrap(); + let mut shader_defs = Vec::new(); - if key.contains(MeshPipelineKey::VERTEX_TANGENTS) { + if key.flags.contains(MeshPipelineFlags::VERTEX_TANGENTS) { shader_defs.push(String::from("VERTEX_TANGENTS")); } let (label, blend, depth_write_enabled); - if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) { + if key.flags.contains(MeshPipelineFlags::TRANSPARENT_MAIN_PASS) { label = "transparent_mesh_pipeline".into(); blend = Some(BlendState::ALPHA_BLENDING); // For the transparent pass, fragments that are closer will be alpha blended @@ -494,11 +449,7 @@ impl SpecializedPipeline for MeshPipeline { shader: MESH_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![VertexBufferLayout { - array_stride: vertex_array_stride, - step_mode: VertexStepMode::Vertex, - attributes: vertex_attributes, - }], + buffers: vec![vertex_layout.clone()], }, fragment: Some(FragmentState { shader: MESH_SHADER_HANDLE.typed::(), @@ -517,7 +468,7 @@ impl SpecializedPipeline for MeshPipeline { unclipped_depth: false, polygon_mode: PolygonMode::Fill, conservative: false, - topology: key.primitive_topology(), + topology: key.flags.primitive_topology(), strip_index_format: None, }, depth_stencil: Some(DepthStencilState { @@ -537,7 +488,7 @@ impl SpecializedPipeline for MeshPipeline { }, }), multisample: MultisampleState { - count: key.msaa_samples(), + count: key.flags.msaa_samples(), mask: !0, alpha_to_coverage_enabled: false, }, @@ -737,11 +688,11 @@ impl EntityRenderCommand for DrawMesh { #[cfg(test)] mod tests { - use super::MeshPipelineKey; + use super::MeshPipelineFlags; #[test] fn mesh_key_msaa_samples() { for i in 1..=64 { - assert_eq!(MeshPipelineKey::from_msaa_samples(i).msaa_samples(), i); + assert_eq!(MeshPipelineFlags::from_msaa_samples(i).msaa_samples(), i); } } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 02b5d9dbee6eb..61847a798ce67 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,5 +1,8 @@ use crate::MeshPipeline; -use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup}; +use crate::{ + DrawMesh, MeshPipelineFlags, MeshPipelineKey, MeshUniform, SetMeshBindGroup, + SetMeshViewBindGroup, +}; use bevy_app::Plugin; use bevy_asset::{Assets, Handle, HandleUntyped}; use bevy_core_pipeline::Opaque3d; @@ -80,8 +83,12 @@ impl FromWorld for WireframePipeline { impl SpecializedPipeline 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, + cache: &RenderPipelineCache, + key: Self::Key, + ) -> bevy_render::render_resource::RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(cache, key); descriptor.vertex.shader = self.shader.clone_weak(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak(); descriptor.primitive.polygon_mode = PolygonMode::Line; @@ -91,6 +98,7 @@ impl SpecializedPipeline for WireframePipeline { } #[allow(clippy::too_many_arguments)] +#[allow(clippy::type_complexity)] fn queue_wireframes( opaque_3d_draw_functions: Res>, render_meshes: Res>, @@ -109,7 +117,7 @@ fn queue_wireframes( .read() .get_id::() .unwrap(); - let key = MeshPipelineKey::from_msaa_samples(msaa.samples); + let flags = MeshPipelineFlags::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); @@ -117,8 +125,12 @@ 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 flags = + flags | MeshPipelineFlags::from_primitive_topology(mesh.primitive_topology); + let key = MeshPipelineKey { + vertex_layout_key: mesh.vertex_layout_key, + flags, + }; transparent_phase.add(Opaque3d { entity, pipeline: specialized_pipelines.specialize( diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 0dc870f1bc1a6..261d154fd32fb 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -3,15 +3,22 @@ mod conversions; use crate::{ primitives::Aabb, render_asset::{PrepareAssetError, RenderAsset}, - render_resource::Buffer, + render_resource::{Buffer, RenderPipelineCache, VertexBufferLayout}, renderer::RenderDevice, }; use bevy_core::cast_slice; -use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; +use bevy_ecs::system::{ + lifetimeless::{SRes, SResMut}, + SystemParamItem, +}; use bevy_math::*; use bevy_reflect::TypeUuid; -use bevy_utils::EnumVariantMeta; -use std::{borrow::Cow, collections::BTreeMap}; +use bevy_utils::{AHasher, EnumVariantMeta}; +use std::{ + borrow::Cow, + collections::BTreeMap, + hash::{Hash, Hasher}, +}; use wgpu::{ util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexFormat, }; @@ -29,6 +36,7 @@ pub struct Mesh { /// 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>, + vertex_layout: VertexBufferLayout, indices: Option, } @@ -75,6 +83,7 @@ impl Mesh { Mesh { primitive_topology, attributes: Default::default(), + vertex_layout: Self::default_vertex_layout(), indices: None, } } @@ -86,13 +95,34 @@ 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`]. + /// + /// # Panics + /// + /// This method panics when the attribute descriptor does not have the given name + /// or has the wrong format. pub fn set_attribute( &mut self, name: impl Into>, values: impl Into, ) { + let name = name.into(); let values: VertexAttributeValues = values.into(); - self.attributes.insert(name.into(), values); + + let vertex_layout = self + .vertex_layout + .attribute_layout(&name) + .unwrap_or_else(|| { + panic!("VertexBufferLayout is missing an attribute named {}", name); + }); + + let vertex_format = VertexFormat::from(&values); + assert_eq!( + vertex_format, vertex_layout.format, + "Attribute {} has a different format than the VertexBufferLayout: {:?} != {:?}", + name, vertex_format, vertex_layout.format + ); + + self.attributes.insert(name, values); } /// Retrieves the data currently set to the vertex attribute with the specified `name`. @@ -108,6 +138,33 @@ impl Mesh { self.attributes.get_mut(&name.into()) } + /// Get a shared reference to the vertex buffer layout. + pub fn vertex_layout(&self) -> &VertexBufferLayout { + &self.vertex_layout + } + + /// Get a unique reference to the vertex buffer layout for updating. + pub fn vertex_layout_mut(&mut self) -> &mut VertexBufferLayout { + &mut self.vertex_layout + } + + /// Replace the existing vertex buffer layout. + pub fn replace_vertex_layout(&mut self, vertex_layout: VertexBufferLayout) { + self.vertex_layout = vertex_layout; + } + + /// Create a default vertex buffer layout matching the simplest PBR shader + /// (without specialization). + pub fn default_vertex_layout() -> VertexBufferLayout { + let mut vertex_layout = VertexBufferLayout::default(); + + vertex_layout.push(Self::ATTRIBUTE_POSITION, VertexFormat::Float32x3); + vertex_layout.push(Self::ATTRIBUTE_NORMAL, VertexFormat::Float32x3); + vertex_layout.push(Self::ATTRIBUTE_UV_0, VertexFormat::Float32x2); + + vertex_layout + } + /// 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. @@ -134,28 +191,6 @@ 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, - // } - // } - /// Counts all vertices of the mesh. /// /// # Panics @@ -181,17 +216,14 @@ impl Mesh { /// # Panics /// 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); - vertex_size += vertex_format.get_size() as usize; - } - let vertex_count = self.count_vertices(); + let vertex_size = self.vertex_layout.array_stride as usize; + 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() { + for (attribute_name, attribute_values) in self.attributes.iter() { + let vbd = self.vertex_layout.attribute_layout(attribute_name).unwrap(); + let attribute_offset = vbd.offset as usize; let vertex_format = VertexFormat::from(attribute_values); let attribute_size = vertex_format.get_size() as usize; let attributes_bytes = attribute_values.get_bytes(); @@ -202,8 +234,6 @@ impl Mesh { attributes_interleaved_buffer[offset..offset + attribute_size] .copy_from_slice(attribute_bytes); } - - attribute_offset += attribute_size; } attributes_interleaved_buffer @@ -585,6 +615,31 @@ impl From<&Indices> for IndexFormat { } } +bitflags::bitflags! { + #[repr(transparent)] + pub struct VertexLayoutSpriteKey: u32 { + const NONE = 0; + const COLORED = (1 << 0); + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum VertexLayoutKey { + Mesh(u64), + Sprite(VertexLayoutSpriteKey), + Ui, +} + +impl VertexLayoutKey { + pub fn sprite() -> Self { + Self::Sprite(VertexLayoutSpriteKey::NONE) + } + + pub fn colored_sprite() -> Self { + Self::Sprite(VertexLayoutSpriteKey::COLORED) + } +} + /// The GPU-representation of a [`Mesh`]. /// Consists of a vertex data buffer and an optional index data buffer. #[derive(Debug, Clone)] @@ -594,6 +649,7 @@ pub struct GpuMesh { pub buffer_info: GpuBufferInfo, pub has_tangents: bool, pub primitive_topology: PrimitiveTopology, + pub vertex_layout_key: VertexLayoutKey, } /// The index/vertex buffer info of a [`GpuMesh`]. @@ -613,7 +669,7 @@ pub enum GpuBufferInfo { impl RenderAsset for Mesh { type ExtractedAsset = Mesh; type PreparedAsset = GpuMesh; - type Param = SRes; + type Param = (SRes, SResMut); /// Clones the mesh. fn extract_asset(&self) -> Self::ExtractedAsset { @@ -623,7 +679,7 @@ impl RenderAsset for Mesh { /// Converts the extracted mesh a into [`GpuMesh`]. fn prepare_asset( mesh: Self::ExtractedAsset, - render_device: &mut SystemParamItem, + (render_device, pipeline_cache): &mut SystemParamItem, ) -> Result> { let vertex_buffer_data = mesh.get_vertex_buffer_data(); let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { @@ -647,11 +703,23 @@ impl RenderAsset for Mesh { }, ); + let vertex_layout = mesh.vertex_layout(); + + let mut hasher = AHasher::default(); + vertex_layout.hash(&mut hasher); + let vertex_layout_key = VertexLayoutKey::Mesh(hasher.finish()); + + // Cache the vertex layout for MeshPipeline + pipeline_cache + .vertex_layout_cache + .insert(vertex_layout_key, vertex_layout); + Ok(GpuMesh { vertex_buffer, buffer_info, has_tangents: mesh.attributes.contains_key(Mesh::ATTRIBUTE_TANGENT), primitive_topology: mesh.primitive_topology(), + vertex_layout_key, }) } } diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index f462c98c5352c..2f067d6bc9497 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -1,10 +1,16 @@ use crate::render_resource::{BindGroupLayout, Shader}; use bevy_asset::Handle; use bevy_reflect::Uuid; -use std::{borrow::Cow, ops::Deref, sync::Arc}; +use bevy_utils::HashMap; +use std::{ + borrow::Cow, + hash::{Hash, Hasher}, + ops::Deref, + sync::Arc, +}; use wgpu::{ BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, - VertexAttribute, VertexStepMode, + VertexAttribute, VertexFormat, VertexStepMode, }; /// A [`RenderPipeline`] identifier. @@ -118,14 +124,107 @@ pub struct VertexState { } /// Describes how the vertex buffer is interpreted. -#[derive(Clone, Debug, Hash, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq)] pub struct VertexBufferLayout { /// The stride, in bytes, between elements of this buffer. pub array_stride: BufferAddress, /// How often this vertex buffer is "stepped" forward. pub step_mode: VertexStepMode, /// The list of attributes which comprise a single vertex. - pub attributes: Vec, + attributes: Vec, + /// The list of attributes suitable for `wgpu`. + wgpu_attributes: Vec, + /// Attribute names for debugging and mapping types. + attribute_names: HashMap, +} + +impl Hash for VertexBufferLayout { + fn hash(&self, state: &mut H) { + self.array_stride.hash(state); + self.step_mode.hash(state); + self.attributes.hash(state); + } +} + +impl PartialEq for VertexBufferLayout { + fn eq(&self, other: &Self) -> bool { + self.array_stride == other.array_stride + && self.step_mode == other.step_mode + && self.attributes == other.attributes + } +} + +impl VertexBufferLayout { + /// Push a vertex attribute descriptor to the end of the list. + /// + /// The shader location is determined based on insertion order. + pub fn push(&mut self, name: &str, format: VertexFormat) { + let shader_location = if let Some(attribute) = self.attributes.last() { + attribute.shader_location + 1 + } else { + 0 + }; + + self.push_location(name, format, shader_location) + } + + /// Push a vertex attribute descriptor to the end of the list with an exact shader location. + pub fn push_location(&mut self, name: &str, format: VertexFormat, shader_location: u32) { + let offset = if let Some(attribute) = self.attributes.last() { + attribute.offset + attribute.format.size() + } else { + 0 + }; + + self.array_stride += format.size(); + self.attribute_names + .entry(name.to_string()) + .or_insert_with(|| self.attributes.len()); + self.attributes.push(VertexAttributeLayout { + name: name.to_string(), + format, + offset, + shader_location, + }); + self.wgpu_attributes.push(VertexAttribute { + format, + offset, + shader_location, + }) + } + + /// Get an attribute layout by name. + pub fn attribute_layout(&self, name: &str) -> Option<&VertexAttributeLayout> { + self.attribute_names.get(name).map(|i| &self.attributes[*i]) + } + + /// Get attributes suitable for `wgpu`. + pub fn attributes(&self) -> &[VertexAttribute] { + &self.wgpu_attributes + } +} + +/// Describes a vertex attribute's layout. +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub struct VertexAttributeLayout { + /// The attribute's name. + pub name: String, + /// Format of the attribute. + pub format: VertexFormat, + /// Byte offset of this attribute within the array element. + pub offset: u64, + /// Attribute location as referenced by the shader. + pub shader_location: u32, +} + +impl From<&VertexAttributeLayout> for VertexAttribute { + fn from(value: &VertexAttributeLayout) -> Self { + Self { + format: value.format, + offset: value.offset, + shader_location: value.shader_location, + } + } } /// Describes the fragment process in a render pipeline. diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index e5fcede3cbec9..a6a3933e026b5 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -1,8 +1,10 @@ use crate::{ + mesh::VertexLayoutKey, render_resource::{ AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ProcessShaderError, RawFragmentState, RawRenderPipelineDescriptor, RawVertexState, RenderPipeline, RenderPipelineDescriptor, Shader, ShaderImport, ShaderProcessor, ShaderReflectError, + VertexBufferLayout, }, renderer::RenderDevice, RenderWorld, @@ -13,7 +15,7 @@ use bevy_ecs::system::{Res, ResMut}; use bevy_utils::{tracing::error, HashMap, HashSet}; use std::{collections::hash_map::Entry, hash::Hash, ops::Deref, sync::Arc}; use thiserror::Error; -use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout}; +use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout as RawVertexBufferLayout}; use super::ProcessedShader; @@ -182,8 +184,31 @@ impl LayoutCache { } } +#[derive(Default)] +pub struct VertexLayoutCache { + layouts: HashMap, +} + +impl VertexLayoutCache { + pub fn insert(&mut self, key: VertexLayoutKey, vertex_layout: &VertexBufferLayout) { + let cached = self + .layouts + .entry(key) + .or_insert_with(|| vertex_layout.clone()); + debug_assert_eq!( + cached, vertex_layout, + "Mesh pipeline vertex layout cache is incorrectly keyed", + ); + } + + pub fn get(&self, key: &VertexLayoutKey) -> Option<&VertexBufferLayout> { + self.layouts.get(key) + } +} + pub struct RenderPipelineCache { layout_cache: LayoutCache, + pub vertex_layout_cache: VertexLayoutCache, shader_cache: ShaderCache, device: RenderDevice, pipelines: Vec, @@ -233,6 +258,7 @@ impl RenderPipelineCache { Self { device, layout_cache: Default::default(), + vertex_layout_cache: Default::default(), shader_cache: Default::default(), waiting_pipelines: Default::default(), pipelines: Default::default(), @@ -345,9 +371,9 @@ impl RenderPipelineCache { .vertex .buffers .iter() - .map(|layout| VertexBufferLayout { + .map(|layout| RawVertexBufferLayout { array_stride: layout.array_stride, - attributes: &layout.attributes, + attributes: layout.attributes(), step_mode: layout.step_mode, }) .collect::>(); diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index a5cb472aa48f8..7a82540b581d0 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -22,7 +22,7 @@ impl SpecializedPipelines { key: S::Key, ) -> CachedPipelineId { *self.cache.entry(key.clone()).or_insert_with(|| { - let descriptor = specialize_pipeline.specialize(key); + let descriptor = specialize_pipeline.specialize(cache, key); cache.queue(descriptor) }) } @@ -30,5 +30,5 @@ impl SpecializedPipelines { pub trait SpecializedPipeline { type Key: Clone + Hash + PartialEq + Eq; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor; + fn specialize(&self, cache: &RenderPipelineCache, key: Self::Key) -> RenderPipelineDescriptor; } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 3e5477f4b9f11..ff6dbbdb8bf19 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -14,6 +14,7 @@ use bevy_ecs::{ use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec4Swizzles}; use bevy_render::{ color::Color, + mesh::{VertexLayoutKey, VertexLayoutSpriteKey}, render_asset::RenderAssets, render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, render_resource::{std140::AsStd140, *}, @@ -31,6 +32,20 @@ pub struct SpritePipeline { material_layout: BindGroupLayout, } +impl SpritePipeline { + fn create_vertex_layout(colored: bool) -> VertexBufferLayout { + let mut vertex_layout = VertexBufferLayout::default(); + + vertex_layout.push("position", VertexFormat::Float32x3); + vertex_layout.push("uv", VertexFormat::Float32x2); + if colored { + vertex_layout.push("color", VertexFormat::Uint32); + } + + vertex_layout + } +} + impl FromWorld for SpritePipeline { fn from_world(world: &mut World) -> Self { let world = world.cell(); @@ -72,6 +87,17 @@ impl FromWorld for SpritePipeline { label: Some("sprite_material_layout"), }); + // Cache the vertex layouts + let mut pipeline_cache = world.get_resource_mut::().unwrap(); + pipeline_cache.vertex_layout_cache.insert( + VertexLayoutKey::sprite(), + &Self::create_vertex_layout(false), + ); + pipeline_cache.vertex_layout_cache.insert( + VertexLayoutKey::colored_sprite(), + &Self::create_vertex_layout(true), + ); + SpritePipeline { view_layout, material_layout, @@ -84,35 +110,28 @@ pub struct SpritePipelineKey { colored: bool, } +impl From for VertexLayoutKey { + fn from(value: SpritePipelineKey) -> Self { + if value.colored { + Self::Sprite(VertexLayoutSpriteKey::COLORED) + } else { + Self::Sprite(VertexLayoutSpriteKey::NONE) + } + } +} + impl SpecializedPipeline for SpritePipeline { type Key = SpritePipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut vertex_buffer_layout = VertexBufferLayout { - array_stride: 20, - step_mode: VertexStepMode::Vertex, - attributes: vec![ - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 0, - }, - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 12, - shader_location: 1, - }, - ], - }; + fn specialize(&self, cache: &RenderPipelineCache, key: Self::Key) -> RenderPipelineDescriptor { + let vertex_layout = cache + .vertex_layout_cache + .get(&VertexLayoutKey::from(key)) + .unwrap(); + let mut shader_defs = Vec::new(); if key.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 { @@ -120,7 +139,7 @@ impl SpecializedPipeline for SpritePipeline { shader: SPRITE_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![vertex_buffer_layout], + buffers: vec![vertex_layout.clone()], }, fragment: Some(FragmentState { shader: SPRITE_SHADER_HANDLE.typed::(), diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 4538dc922b131..af022885e09e6 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -1,5 +1,6 @@ use bevy_ecs::prelude::*; use bevy_render::{ + mesh::VertexLayoutKey, render_resource::{std140::AsStd140, *}, renderer::RenderDevice, texture::BevyDefault, @@ -11,6 +12,18 @@ pub struct UiPipeline { pub image_layout: BindGroupLayout, } +impl UiPipeline { + fn create_vertex_layout() -> VertexBufferLayout { + let mut vertex_layout = VertexBufferLayout::default(); + + vertex_layout.push("position", VertexFormat::Float32x3); + vertex_layout.push("uv", VertexFormat::Float32x2); + vertex_layout.push("color", VertexFormat::Uint32); + + vertex_layout + } +} + impl FromWorld for UiPipeline { fn from_world(world: &mut World) -> Self { let world = world.cell(); @@ -52,6 +65,12 @@ impl FromWorld for UiPipeline { label: Some("ui_image_layout"), }); + // Cache the vertex layouts + let mut pipeline_cache = world.get_resource_mut::().unwrap(); + pipeline_cache + .vertex_layout_cache + .insert(VertexLayoutKey::Ui, &Self::create_vertex_layout()); + UiPipeline { view_layout, image_layout, @@ -62,33 +81,20 @@ impl FromWorld for UiPipeline { #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct UiPipelineKey {} +impl From for VertexLayoutKey { + fn from(_value: UiPipelineKey) -> Self { + Self::Ui + } +} + impl SpecializedPipeline for UiPipeline { type Key = UiPipelineKey; /// FIXME: there are no specialization for now, should this be removed? - fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { - let vertex_buffer_layout = VertexBufferLayout { - array_stride: 24, - step_mode: VertexStepMode::Vertex, - attributes: vec![ - // Position - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 0, - }, - // UV - VertexAttribute { - format: VertexFormat::Float32x2, - offset: 12, - shader_location: 1, - }, - VertexAttribute { - format: VertexFormat::Uint32, - offset: 20, - shader_location: 2, - }, - ], - }; + fn specialize(&self, cache: &RenderPipelineCache, key: Self::Key) -> RenderPipelineDescriptor { + let vertex_layout = cache + .vertex_layout_cache + .get(&VertexLayoutKey::from(key)) + .unwrap(); let shader_defs = Vec::new(); RenderPipelineDescriptor { @@ -96,7 +102,7 @@ impl SpecializedPipeline for UiPipeline { shader: super::UI_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), - buffers: vec![vertex_buffer_layout], + buffers: vec![vertex_layout.clone()], }, fragment: Some(FragmentState { shader: super::UI_SHADER_HANDLE.typed::(), diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index 7cfd939f1ec2a..b9fff95d872e7 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -1,7 +1,7 @@ use bevy::{ core_pipeline::Transparent3d, pbr::{ - DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, + DrawMesh, MeshPipeline, MeshPipelineFlags, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup, }, prelude::*, @@ -82,7 +82,7 @@ fn setup(mut commands: Commands, mut meshes: ResMut>) { } struct IsRedPipeline { - mesh_pipline: MeshPipeline, + mesh_pipeline: MeshPipeline, shader: Handle, } @@ -92,7 +92,7 @@ impl FromWorld for IsRedPipeline { let mesh_pipeline = world.get_resource::().unwrap(); let shader = asset_server.load("shaders/shader_defs.wgsl"); IsRedPipeline { - mesh_pipline: mesh_pipeline.clone(), + mesh_pipeline: mesh_pipeline.clone(), shader, } } @@ -101,20 +101,24 @@ impl FromWorld for IsRedPipeline { impl SpecializedPipeline for IsRedPipeline { type Key = (IsRed, MeshPipelineKey); - fn specialize(&self, (is_red, pbr_pipeline_key): Self::Key) -> RenderPipelineDescriptor { + fn specialize( + &self, + cache: &RenderPipelineCache, + (is_red, pbr_pipeline_key): Self::Key, + ) -> RenderPipelineDescriptor { let mut shader_defs = Vec::new(); if is_red.0 { shader_defs.push("IS_RED".to_string()); } - let mut descriptor = self.mesh_pipline.specialize(pbr_pipeline_key); + let mut descriptor = self.mesh_pipeline.specialize(cache, pbr_pipeline_key); descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.shader_defs = shader_defs.clone(); let fragment = descriptor.fragment.as_mut().unwrap(); fragment.shader = self.shader.clone(); fragment.shader_defs = shader_defs; descriptor.layout = Some(vec![ - self.mesh_pipline.view_layout.clone(), - self.mesh_pipline.mesh_layout.clone(), + self.mesh_pipeline.view_layout.clone(), + self.mesh_pipeline.mesh_layout.clone(), ]); descriptor } @@ -142,13 +146,18 @@ fn queue_custom( .read() .get_id::() .unwrap(); - let key = MeshPipelineKey::from_msaa_samples(msaa.samples); + let flags = MeshPipelineFlags::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 flags = + flags | MeshPipelineFlags::from_primitive_topology(mesh.primitive_topology); + let key = MeshPipelineKey { + vertex_layout_key: mesh.vertex_layout_key, + flags, + }; let pipeline = pipelines.specialize(&mut pipeline_cache, &custom_pipeline, (*is_red, key)); transparent_phase.add(Transparent3d { From eb58387afc4ceb0d81676cf427721f4936eb9bdb Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Mon, 10 Jan 2022 02:51:56 -0800 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Nicola Papale --- crates/bevy_render/src/render_resource/pipeline.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 2f067d6bc9497..78b13791aa00f 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -159,22 +159,14 @@ impl VertexBufferLayout { /// /// The shader location is determined based on insertion order. pub fn push(&mut self, name: &str, format: VertexFormat) { - let shader_location = if let Some(attribute) = self.attributes.last() { - attribute.shader_location + 1 - } else { - 0 - }; + let shader_location = self.attributes.last().map_or(0, |attr| attr.shader_location + 1); self.push_location(name, format, shader_location) } /// Push a vertex attribute descriptor to the end of the list with an exact shader location. pub fn push_location(&mut self, name: &str, format: VertexFormat, shader_location: u32) { - let offset = if let Some(attribute) = self.attributes.last() { - attribute.offset + attribute.format.size() - } else { - 0 - }; + let offset = self.attributes.last().map_or(0, |attr| attr.offset + attr.format.size()); self.array_stride += format.size(); self.attribute_names