From 3a01381f3db1b875199046e685dab0104de280c3 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Fri, 12 Nov 2021 21:20:13 -0800 Subject: [PATCH] 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 converting the specialization key to a vertex layout key.. - `SpritePipeline` populates the vertex layout cache for itself. - `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 --- examples/shader/custom_shader_pipelined.rs | 4 +- examples/shader/shader_defs_pipelined.rs | 17 +- pipelined/bevy_gltf2/src/loader.rs | 3 + pipelined/bevy_pbr2/src/render/light.rs | 116 ++++++------- pipelined/bevy_pbr2/src/render/mesh.rs | 109 +++++------- pipelined/bevy_pbr2/src/render/mod.rs | 11 +- pipelined/bevy_render2/src/mesh/mesh/mod.rs | 155 +++++++++++++----- .../src/render_resource/pipeline.rs | 107 +++++++++++- .../src/render_resource/pipeline_cache.rs | 33 +++- .../render_resource/pipeline_specializer.rs | 4 +- pipelined/bevy_sprite2/src/render/mod.rs | 70 +++++--- 11 files changed, 406 insertions(+), 223 deletions(-) diff --git a/examples/shader/custom_shader_pipelined.rs b/examples/shader/custom_shader_pipelined.rs index dab60f7e6df961..3d3d0c55a8fad9 100644 --- a/examples/shader/custom_shader_pipelined.rs +++ b/examples/shader/custom_shader_pipelined.rs @@ -135,8 +135,8 @@ pub struct CustomPipeline { impl SpecializedPipeline for CustomPipeline { type Key = MeshPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key); + fn specialize(&self, cache: &RenderPipelineCache, key: Self::Key) -> RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(cache, key); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); descriptor.layout = Some(vec![ self.mesh_pipeline.view_layout.clone(), diff --git a/examples/shader/shader_defs_pipelined.rs b/examples/shader/shader_defs_pipelined.rs index 0856261d604ea3..7db95990a3eacf 100644 --- a/examples/shader/shader_defs_pipelined.rs +++ b/examples/shader/shader_defs_pipelined.rs @@ -86,7 +86,7 @@ fn setup(mut commands: Commands, mut meshes: ResMut>) { } struct IsRedPipeline { - mesh_pipline: MeshPipeline, + mesh_pipeline: MeshPipeline, shader: Handle, } @@ -96,7 +96,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, } } @@ -105,20 +105,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 } @@ -131,6 +135,7 @@ type DrawIsRed = ( DrawMesh, ); +#[allow(clippy::too_many_arguments)] fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, diff --git a/pipelined/bevy_gltf2/src/loader.rs b/pipelined/bevy_gltf2/src/loader.rs index 0058dd1bf7453a..7c52b93a53b11a 100644 --- a/pipelined/bevy_gltf2/src/loader.rs +++ b/pipelined/bevy_gltf2/src/loader.rs @@ -14,6 +14,7 @@ use bevy_render2::{ color::Color, mesh::{Indices, Mesh, VertexAttributeValues}, primitives::Aabb, + render_resource::VertexFormat, texture::{Image, ImageType, TextureError}, }; use bevy_scene::Scene; @@ -132,6 +133,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/pipelined/bevy_pbr2/src/render/light.rs b/pipelined/bevy_pbr2/src/render/light.rs index a1a5f2673b7fb2..5957f5eb35d8dd 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/pipelined/bevy_pbr2/src/render/light.rs @@ -14,7 +14,7 @@ use bevy_math::{const_vec3, Mat4, Vec3, Vec4}; use bevy_render2::{ camera::CameraProjection, color::Color, - mesh::Mesh, + mesh::{Mesh, VertexLayoutKey, VertexLayoutMeshKey}, render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::{ @@ -201,79 +201,60 @@ bitflags::bitflags! { pub struct ShadowPipelineKey: u32 { const NONE = 0; const VERTEX_TANGENTS = (1 << 0); + const VERTEX_JOINTS = (1 << 1); + } +} + +impl From for VertexLayoutKey { + fn from(value: ShadowPipelineKey) -> Self { + let mut key = VertexLayoutMeshKey::empty(); + + if value.contains(ShadowPipelineKey::VERTEX_TANGENTS) { + key |= VertexLayoutMeshKey::TANGENTS; + } + if value.contains(ShadowPipelineKey::VERTEX_JOINTS) { + key |= VertexLayoutMeshKey::JOINTS; + } + + Self::Mesh(key) + } +} + +impl From for ShadowPipelineKey { + fn from(value: VertexLayoutKey) -> Self { + match value { + VertexLayoutKey::Mesh(mesh_key) => { + let mut key = Self::empty(); + + if mesh_key.contains(VertexLayoutMeshKey::TANGENTS) { + key |= Self::VERTEX_TANGENTS; + } + if mesh_key.contains(VertexLayoutMeshKey::JOINTS) { + key |= Self::VERTEX_JOINTS; + } + + key + } + _ => panic!("Invalid vertex layout key"), + } } } 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(&VertexLayoutKey::from(key)) + .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()]), @@ -815,13 +796,10 @@ pub fn queue_shadows( // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued for VisibleEntity { entity, .. } in visible_entities.iter() { - 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::from(mesh.vertex_layout_key); + let pipeline_id = pipelines.specialize(&mut pipeline_cache, &shadow_pipeline, key); diff --git a/pipelined/bevy_pbr2/src/render/mesh.rs b/pipelined/bevy_pbr2/src/render/mesh.rs index 567745f919e0f2..36e034d8014dfa 100644 --- a/pipelined/bevy_pbr2/src/render/mesh.rs +++ b/pipelined/bevy_pbr2/src/render/mesh.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ use bevy_math::Mat4; use bevy_reflect::TypeUuid; use bevy_render2::{ - mesh::Mesh, + mesh::{Mesh, VertexLayoutKey, VertexLayoutMeshKey}, render_asset::RenderAssets, render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, @@ -328,11 +328,47 @@ bitflags::bitflags! { pub struct MeshPipelineKey: u32 { const NONE = 0; const VERTEX_TANGENTS = (1 << 0); - const TRANSPARENT_MAIN_PASS = (1 << 1); + const VERTEX_JOINTS = (1 << 1); + const TRANSPARENT_MAIN_PASS = (1 << 2); const MSAA_RESERVED_BITS = MeshPipelineKey::MSAA_MASK_BITS << MeshPipelineKey::MSAA_SHIFT_BITS; } } +impl From for VertexLayoutKey { + fn from(value: MeshPipelineKey) -> Self { + let mut key = VertexLayoutMeshKey::empty(); + + if value.contains(MeshPipelineKey::VERTEX_TANGENTS) { + key |= VertexLayoutMeshKey::TANGENTS; + } + if value.contains(MeshPipelineKey::VERTEX_JOINTS) { + key |= VertexLayoutMeshKey::JOINTS; + } + + Self::Mesh(key) + } +} + +impl From for MeshPipelineKey { + fn from(value: VertexLayoutKey) -> Self { + match value { + VertexLayoutKey::Mesh(mesh_key) => { + let mut key = Self::empty(); + + if mesh_key.contains(VertexLayoutMeshKey::TANGENTS) { + key |= Self::VERTEX_TANGENTS; + } + if mesh_key.contains(VertexLayoutMeshKey::JOINTS) { + key |= Self::VERTEX_JOINTS; + } + + key + } + _ => panic!("Invalid vertex layout key"), + } + } +} + impl MeshPipelineKey { const MSAA_MASK_BITS: u32 = 0b111111; const MSAA_SHIFT_BITS: u32 = 32 - 6; @@ -350,63 +386,12 @@ 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(&VertexLayoutKey::from(key)) + .unwrap(); + let mut shader_defs = Vec::new(); if key.contains(MeshPipelineKey::VERTEX_TANGENTS) { shader_defs.push(String::from("VERTEX_TANGENTS")); @@ -433,11 +418,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::(), diff --git a/pipelined/bevy_pbr2/src/render/mod.rs b/pipelined/bevy_pbr2/src/render/mod.rs index 348f7db9552803..f93c072d8ff952 100644 --- a/pipelined/bevy_pbr2/src/render/mod.rs +++ b/pipelined/bevy_pbr2/src/render/mod.rs @@ -191,8 +191,8 @@ pub struct PbrPipelineKey { impl SpecializedPipeline for PbrPipeline { type Key = PbrPipelineKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key); + fn specialize(&self, cache: &RenderPipelineCache, key: Self::Key) -> RenderPipelineDescriptor { + let mut descriptor = self.mesh_pipeline.specialize(cache, key.mesh_key); descriptor.fragment.as_mut().unwrap().shader = PBR_SHADER_HANDLE.typed::(); descriptor.layout = Some(vec![ self.mesh_pipeline.view_layout.clone(), @@ -256,11 +256,8 @@ pub fn queue_meshes( mesh_key, normal_map: material.has_normal_map, }; - if let Some(mesh) = render_meshes.get(mesh_handle) { - if mesh.has_tangents { - pbr_key.mesh_key |= MeshPipelineKey::VERTEX_TANGENTS; - } - } + let mesh = render_meshes.get(mesh_handle).unwrap(); + pbr_key.mesh_key |= MeshPipelineKey::from(mesh.vertex_layout_key); if let AlphaMode::Blend = material.alpha_mode { pbr_key.mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS diff --git a/pipelined/bevy_render2/src/mesh/mesh/mod.rs b/pipelined/bevy_render2/src/mesh/mesh/mod.rs index 57614f378e34a8..a64349f4514d9e 100644 --- a/pipelined/bevy_render2/src/mesh/mesh/mod.rs +++ b/pipelined/bevy_render2/src/mesh/mesh/mod.rs @@ -3,15 +3,18 @@ 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 std::{borrow::Cow, collections::BTreeMap, hash::Hash}; use wgpu::{ util::BufferInitDescriptor, BufferUsages, IndexFormat, PrimitiveTopology, VertexFormat, }; @@ -29,6 +32,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 +79,7 @@ impl Mesh { Mesh { primitive_topology, attributes: Default::default(), + vertex_layout: Self::default_vertex_layout(), indices: None, } } @@ -86,13 +91,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 +134,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 +187,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 +212,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 +230,6 @@ impl Mesh { attributes_interleaved_buffer[offset..offset + attribute_size] .copy_from_slice(attribute_bytes); } - - attribute_offset += attribute_size; } attributes_interleaved_buffer @@ -585,14 +611,47 @@ impl From<&Indices> for IndexFormat { } } +bitflags::bitflags! { + #[repr(transparent)] + pub struct VertexLayoutMeshKey: u32 { + const NONE = 0; + const TANGENTS = (1 << 0); + const JOINTS = (1 << 1); + } +} + +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(VertexLayoutMeshKey), + Sprite(VertexLayoutSpriteKey), +} + +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)] pub struct GpuMesh { /// Contains all attribute data for each vertex. pub vertex_buffer: Buffer, + pub vertex_layout_key: VertexLayoutKey, pub index_info: Option, - pub has_tangents: bool, } /// The index info of a [`GpuMesh`]. @@ -607,7 +666,7 @@ pub struct GpuIndexInfo { 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 { @@ -617,7 +676,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 { @@ -636,10 +695,26 @@ impl RenderAsset for Mesh { index_format: mesh.indices().unwrap().into(), }); + let mut mesh_key = VertexLayoutMeshKey::empty(); + if mesh.attributes.contains_key(Mesh::ATTRIBUTE_TANGENT) { + mesh_key |= VertexLayoutMeshKey::TANGENTS; + } + if mesh.attributes.contains_key(Mesh::ATTRIBUTE_JOINT_INDEX) + || mesh.attributes.contains_key(Mesh::ATTRIBUTE_JOINT_WEIGHT) + { + mesh_key |= VertexLayoutMeshKey::JOINTS; + } + let vertex_layout_key = VertexLayoutKey::Mesh(mesh_key); + + // Cache the vertex layout for MeshPipeline + pipeline_cache + .vertex_layout_cache + .insert(vertex_layout_key, mesh.vertex_layout()); + Ok(GpuMesh { vertex_buffer, + vertex_layout_key, index_info, - has_tangents: mesh.attributes.contains_key(Mesh::ATTRIBUTE_TANGENT), }) } } diff --git a/pipelined/bevy_render2/src/render_resource/pipeline.rs b/pipelined/bevy_render2/src/render_resource/pipeline.rs index f462c98c5352ca..2f067d6bc94973 100644 --- a/pipelined/bevy_render2/src/render_resource/pipeline.rs +++ b/pipelined/bevy_render2/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/pipelined/bevy_render2/src/render_resource/pipeline_cache.rs b/pipelined/bevy_render2/src/render_resource/pipeline_cache.rs index 1dff4fe617ec03..6780be99e0894e 100644 --- a/pipelined/bevy_render2/src/render_resource/pipeline_cache.rs +++ b/pipelined/bevy_render2/src/render_resource/pipeline_cache.rs @@ -1,8 +1,9 @@ use crate::{ + mesh::VertexLayoutKey, render_resource::{ AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ProcessShaderError, RawFragmentState, RawRenderPipelineDescriptor, RawVertexState, RenderPipeline, - RenderPipelineDescriptor, Shader, ShaderImport, ShaderProcessor, + RenderPipelineDescriptor, Shader, ShaderImport, ShaderProcessor, VertexBufferLayout, }, renderer::RenderDevice, RenderWorld, @@ -13,7 +14,7 @@ use bevy_ecs::system::{Res, ResMut}; use bevy_utils::{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}; #[derive(Default)] pub struct ShaderData { @@ -166,8 +167,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, @@ -217,6 +241,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(), @@ -325,9 +350,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/pipelined/bevy_render2/src/render_resource/pipeline_specializer.rs b/pipelined/bevy_render2/src/render_resource/pipeline_specializer.rs index a5cb472aa48f8b..7a82540b581d0e 100644 --- a/pipelined/bevy_render2/src/render_resource/pipeline_specializer.rs +++ b/pipelined/bevy_render2/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/pipelined/bevy_sprite2/src/render/mod.rs b/pipelined/bevy_sprite2/src/render/mod.rs index eb330900034afa..68604f013d5527 100644 --- a/pipelined/bevy_sprite2/src/render/mod.rs +++ b/pipelined/bevy_sprite2/src/render/mod.rs @@ -14,6 +14,7 @@ use bevy_ecs::{ use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec4Swizzles}; use bevy_render2::{ color::Color, + mesh::{VertexLayoutKey, VertexLayoutSpriteKey}, render_asset::RenderAssets, render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, render_resource::*, @@ -32,6 +33,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(); @@ -76,6 +91,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, @@ -83,40 +109,33 @@ impl FromWorld for SpritePipeline { } } -#[derive(Clone, Copy, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 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 { @@ -124,7 +143,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::(), @@ -474,6 +493,7 @@ pub fn queue_sprites( &sprite_pipeline, SpritePipelineKey { colored: true }, ); + for mut transparent_phase in views.iter_mut() { for (entity, batch) in sprite_batches.iter_mut() { image_bind_groups