From 382737d8bea193bae79b5533146a11186fb4954c Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sun, 23 Apr 2023 17:37:46 +0200 Subject: [PATCH 01/14] Use GpuArrayBuffer for MeshUniform --- assets/shaders/custom_vertex_attribute.wgsl | 5 +- assets/shaders/instancing.wgsl | 6 +- crates/bevy_pbr/src/prepass/mod.rs | 10 +++- crates/bevy_pbr/src/prepass/prepass.wgsl | 16 +++-- .../src/prepass/prepass_bindings.wgsl | 3 +- crates/bevy_pbr/src/render/mesh.rs | 59 ++++++++++++------- crates/bevy_pbr/src/render/mesh.wgsl | 18 +++++- crates/bevy_pbr/src/render/mesh_bindings.rs | 56 ++++++++++-------- crates/bevy_pbr/src/render/mesh_bindings.wgsl | 16 +++-- .../bevy_pbr/src/render/mesh_functions.wgsl | 16 ++--- .../src/render/mesh_vertex_output.wgsl | 5 +- crates/bevy_pbr/src/render/pbr.wgsl | 2 +- crates/bevy_pbr/src/render/pbr_functions.wgsl | 6 +- crates/bevy_pbr/src/render/wireframe.wgsl | 3 +- .../src/gpu_component_array_buffer.rs | 21 +++---- 15 files changed, 155 insertions(+), 87 deletions(-) diff --git a/assets/shaders/custom_vertex_attribute.wgsl b/assets/shaders/custom_vertex_attribute.wgsl index 2d1307bc7ec27..6ce1100df7e7e 100644 --- a/assets/shaders/custom_vertex_attribute.wgsl +++ b/assets/shaders/custom_vertex_attribute.wgsl @@ -8,6 +8,7 @@ struct CustomMaterial { var material: CustomMaterial; struct Vertex { + @builtin(instance_index) instance_index: u32, @location(0) position: vec3, @location(1) blend_color: vec4, }; @@ -21,8 +22,8 @@ struct VertexOutput { fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; out.clip_position = mesh_position_local_to_clip( - mesh.model, - vec4(vertex.position, 1.0) + mesh[vertex.instance_index].model, + vec4(vertex.position, 1.0), ); out.blend_color = vertex.blend_color; return out; diff --git a/assets/shaders/instancing.wgsl b/assets/shaders/instancing.wgsl index cf41bb01311fe..055c9dbf4156e 100644 --- a/assets/shaders/instancing.wgsl +++ b/assets/shaders/instancing.wgsl @@ -19,8 +19,12 @@ struct VertexOutput { fn vertex(vertex: Vertex) -> VertexOutput { let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz; var out: VertexOutput; + // NOTE: The 0 index into the Mesh array is a hack for this example as the + // instance_index builtin would map to the wrong index in the Mesh array. + // This index could be passed in via another uniform instead but it's + // unnecessary for the example. out.clip_position = mesh_position_local_to_clip( - mesh.model, + mesh[0].model, vec4(position, 1.0) ); out.color = vertex.i_color; diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index a0364acd7fd0e..d8201d89430bf 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -31,8 +31,8 @@ use bevy_render::{ BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache, - PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages, - ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, + PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderDefVal, ShaderRef, + ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType, TextureViewDimension, VertexState, }, @@ -226,6 +226,7 @@ pub struct PrepassPipeline { pub view_layout_motion_vectors: BindGroupLayout, pub view_layout_no_motion_vectors: BindGroupLayout, pub mesh_layouts: MeshLayouts, + pub mesh_buffer_batch_size: Option, pub material_layout: BindGroupLayout, pub material_vertex_shader: Option>, pub material_fragment_shader: Option>, @@ -313,6 +314,7 @@ impl FromWorld for PrepassPipeline { view_layout_motion_vectors, view_layout_no_motion_vectors, mesh_layouts: mesh_pipeline.mesh_layouts.clone(), + mesh_buffer_batch_size: mesh_pipeline.mesh_buffer_batch_size, material_vertex_shader: match M::prepass_vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), @@ -352,6 +354,10 @@ where let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); + if let Some(batch_size) = self.mesh_buffer_batch_size { + shader_defs.push(ShaderDefVal::UInt("MESH_BATCH_SIZE".into(), batch_size)); + } + // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. // The main limitation right now is that bind group order is hardcoded in shaders. bind_group_layouts.insert(1, self.material_layout.clone()); diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 50cbca994886e..6c98c55ee945f 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -7,6 +7,7 @@ // Most of these attributes are not used in the default prepass fragment shader, but they are still needed so we can // pass them to custom prepass shaders like pbr_prepass.wgsl. struct Vertex { + @builtin(instance_index) instance_index: u32, @location(0) position: vec3, #ifdef VERTEX_UVS @@ -88,7 +89,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef SKINNED var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // SKINNED - var model = mesh.model; + var model = mesh[vertex.instance_index].model; #endif // SKINNED out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); @@ -105,17 +106,24 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef SKINNED out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal); #else // SKINNED - out.world_normal = bevy_pbr::mesh_functions::mesh_normal_local_to_world(vertex.normal); + out.world_normal = bevy_pbr::mesh_functions::mesh_normal_local_to_world( + vertex.normal, + vertex.instance_index + ); #endif // SKINNED #ifdef VERTEX_TANGENTS - out.world_tangent = bevy_pbr::mesh_functions::mesh_tangent_local_to_world(model, vertex.tangent); + out.world_tangent = bevy_pbr::mesh_functions::mesh_tangent_local_to_world( + model, + vertex.tangent, + vertex.instance_index + ); #endif // VERTEX_TANGENTS #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); - out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh.previous_model, vec4(vertex.position, 1.0)); + out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh[vertex.instance_index].previous_model, vec4(vertex.position, 1.0)); #endif // MOTION_VECTOR_PREPASS return out; diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl index 825b0fa245181..c4d039d14d6d1 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl @@ -15,5 +15,4 @@ var previous_view_proj: mat4x4; // Material bindings will be in @group(1) -@group(2) @binding(0) -var mesh: bevy_pbr::mesh_types::Mesh; +#import bevy_pbr::mesh_bindings mesh diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index aa4ab31e5dc7d..f22b18fc7c680 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -21,8 +21,8 @@ use bevy_ecs::{ use bevy_math::{Mat3A, Mat4, Vec2}; use bevy_reflect::TypeUuid; use bevy_render::{ - extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, globals::{GlobalsBuffer, GlobalsUniform}, + gpu_component_array_buffer::GpuComponentArrayBufferPlugin, mesh::{ skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, GpuBufferInfo, InnerMeshVertexBufferLayout, Mesh, MeshVertexBufferLayout, @@ -121,7 +121,7 @@ impl Plugin for MeshRenderPlugin { load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl); load_internal_asset!(app, MORPH_HANDLE, "morph.wgsl", Shader::from_wgsl); - app.add_plugins(UniformComponentPlugin::::default()); + app.add_plugins(GpuComponentArrayBufferPlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -309,8 +309,8 @@ pub struct MeshPipeline { // This dummy white texture is to be used in place of optional StandardMaterial textures pub dummy_white_gpu_image: GpuImage, pub clustered_forward_buffer_binding_type: BufferBindingType, - pub mesh_layouts: MeshLayouts, + pub mesh_buffer_batch_size: Option, } impl FromWorld for MeshPipeline { @@ -550,6 +550,9 @@ impl FromWorld for MeshPipeline { clustered_forward_buffer_binding_type, dummy_white_gpu_image, mesh_layouts: MeshLayouts::new(&render_device), + mesh_buffer_batch_size: GpuArrayBuffer::::batch_size( + render_device.as_ref(), + ), } } } @@ -709,6 +712,11 @@ impl SpecializedMeshPipeline for MeshPipeline { let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); + shader_defs.push("INSTANCE_INDEX".into()); + if let Some(batch_size) = self.mesh_buffer_batch_size { + shader_defs.push(ShaderDefVal::UInt("MESH_BATCH_SIZE".into(), batch_size)); + } + if layout.contains(Mesh::ATTRIBUTE_POSITION) { shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); @@ -932,29 +940,29 @@ pub fn queue_mesh_bind_group( mut groups: ResMut, mesh_pipeline: Res, render_device: Res, - mesh_uniforms: Res>, + mesh_uniforms: Res>, skinned_mesh_uniform: Res, weights_uniform: Res, ) { groups.reset(); let layouts = &mesh_pipeline.mesh_layouts; - let Some(model) = mesh_uniforms.buffer() else { + let Some(model) = mesh_uniforms.binding() else { return; }; - groups.model_only = Some(layouts.model_only(&render_device, model)); + groups.model_only = Some(layouts.model_only(&render_device, &model)); let skin = skinned_mesh_uniform.buffer.buffer(); if let Some(skin) = skin { - groups.skinned = Some(layouts.skinned(&render_device, model, skin)); + groups.skinned = Some(layouts.skinned(&render_device, &model, skin)); } if let Some(weights) = weights_uniform.buffer.buffer() { for (id, gpu_mesh) in meshes.iter() { if let Some(targets) = gpu_mesh.morph_targets.as_ref() { let group = if let Some(skin) = skin.filter(|_| is_skinned(&gpu_mesh.layout)) { - layouts.morphed_skinned(&render_device, model, skin, weights, targets) + layouts.morphed_skinned(&render_device, &model, skin, weights, targets) } else { - layouts.morphed(&render_device, model, weights, targets) + layouts.morphed(&render_device, &model, weights, targets) }; groups.morph_targets.insert(id.id(), group); } @@ -1198,7 +1206,7 @@ impl RenderCommand

for SetMeshBindGroup { type ViewWorldQuery = (); type ItemWorldQuery = ( Read>, - Read>, + Read>, Option>, Option>, ); @@ -1207,7 +1215,7 @@ impl RenderCommand

for SetMeshBindGroup { fn render<'w>( _item: &P, _view: (), - (mesh, mesh_index, skin_index, morph_index): ROQueryItem, + (mesh, batch_indices, skin_index, morph_index): ROQueryItem, bind_groups: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { @@ -1223,13 +1231,19 @@ impl RenderCommand

for SetMeshBindGroup { ); return RenderCommandResult::Failure; }; + let mut set_bind_group = |indices: &[u32]| pass.set_bind_group(I, bind_group, indices); - let mesh_index = mesh_index.index(); - match (skin_index, morph_index) { - (None, None) => set_bind_group(&[mesh_index]), - (Some(skin), None) => set_bind_group(&[mesh_index, skin.index]), - (None, Some(morph)) => set_bind_group(&[mesh_index, morph.index]), - (Some(skin), Some(morph)) => set_bind_group(&[mesh_index, skin.index, morph.index]), + match (batch_indices.dynamic_offset, skin_index, morph_index) { + (None, None, None) => set_bind_group(&[]), + (None, Some(skin), None) => set_bind_group(&[skin.index]), + (None, None, Some(morph)) => set_bind_group(&[morph.index]), + (None, Some(skin), Some(morph)) => set_bind_group(&[skin.index, morph.index]), + (Some(mesh_index), None, None) => set_bind_group(&[mesh_index]), + (Some(mesh_index), Some(skin), None) => set_bind_group(&[mesh_index, skin.index]), + (Some(mesh_index), None, Some(morph)) => set_bind_group(&[mesh_index, morph.index]), + (Some(mesh_index), Some(skin), Some(morph)) => { + set_bind_group(&[mesh_index, skin.index, morph.index]); + } }; RenderCommandResult::Success } @@ -1239,12 +1253,12 @@ pub struct DrawMesh; impl RenderCommand

for DrawMesh { type Param = SRes>; type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ItemWorldQuery = (Read>, Read>); #[inline] fn render<'w>( _item: &P, _view: (), - mesh_handle: ROQueryItem<'_, Self::ItemWorldQuery>, + (batch_indices, mesh_handle): ROQueryItem<'_, Self::ItemWorldQuery>, meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { @@ -1257,10 +1271,13 @@ impl RenderCommand

for DrawMesh { count, } => { pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, 0..1); + pass.draw_indexed(0..*count, 0, batch_indices.index..batch_indices.index + 1); } GpuBufferInfo::NonIndexed => { - pass.draw(0..gpu_mesh.vertex_count, 0..1); + pass.draw( + 0..gpu_mesh.vertex_count, + batch_indices.index..batch_indices.index + 1, + ); } } RenderCommandResult::Success diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index a99017ce628dd..0dbfcc9758ec6 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -5,6 +5,7 @@ #import bevy_pbr::mesh_vertex_output MeshVertexOutput struct Vertex { + @builtin(instance_index) instance_index: u32, #ifdef VERTEX_POSITIONS @location(0) position: vec3, #endif @@ -63,14 +64,17 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #ifdef SKINNED var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else - var model = mesh.model; + var model = mesh[vertex.instance_index].model; #endif #ifdef VERTEX_NORMALS #ifdef SKINNED out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal); #else - out.world_normal = mesh_functions::mesh_normal_local_to_world(vertex.normal); + out.world_normal = mesh_functions::mesh_normal_local_to_world( + vertex.normal, + vertex.instance_index + ); #endif #endif @@ -84,13 +88,21 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #endif #ifdef VERTEX_TANGENTS - out.world_tangent = mesh_functions::mesh_tangent_local_to_world(model, vertex.tangent); + out.world_tangent = mesh_functions::mesh_tangent_local_to_world( + model, + vertex.tangent, + vertex.instance_index + ); #endif #ifdef VERTEX_COLORS out.color = vertex.color; #endif +#ifdef INSTANCE_INDEX + out.instance_index = vertex.instance_index; +#endif + return out; } diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 3b600a7e29fe8..e46af242fdf85 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -3,8 +3,8 @@ use bevy_render::{ mesh::morph::MAX_MORPH_WEIGHTS, render_resource::{ - BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor, Buffer, - TextureView, + BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor, + BindingResource, Buffer, TextureView, }, renderer::RenderDevice, }; @@ -17,9 +17,12 @@ mod layout_entry { use super::MORPH_BUFFER_SIZE; use crate::render::mesh::JOINT_BUFFER_SIZE; use crate::MeshUniform; - use bevy_render::render_resource::{ - BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, ShaderStages, ShaderType, - TextureSampleType, TextureViewDimension, + use bevy_render::{ + render_resource::{ + BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, GpuArrayBuffer, + ShaderStages, TextureSampleType, TextureViewDimension, + }, + renderer::RenderDevice, }; fn buffer(binding: u32, size: u64, visibility: ShaderStages) -> BindGroupLayoutEntry { @@ -34,9 +37,12 @@ mod layout_entry { }, } } - pub(super) fn model(binding: u32) -> BindGroupLayoutEntry { - let size = MeshUniform::min_size().get(); - buffer(binding, size, ShaderStages::VERTEX | ShaderStages::FRAGMENT) + pub(super) fn model(render_device: &RenderDevice, binding: u32) -> BindGroupLayoutEntry { + GpuArrayBuffer::::binding_layout( + binding, + ShaderStages::VERTEX_FRAGMENT, + render_device, + ) } pub(super) fn skinning(binding: u32) -> BindGroupLayoutEntry { buffer(binding, JOINT_BUFFER_SIZE as u64, ShaderStages::VERTEX) @@ -62,9 +68,8 @@ mod layout_entry { mod entry { use super::MORPH_BUFFER_SIZE; use crate::render::mesh::JOINT_BUFFER_SIZE; - use crate::MeshUniform; use bevy_render::render_resource::{ - BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, ShaderType, TextureView, + BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, TextureView, }; fn entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry { @@ -77,8 +82,8 @@ mod entry { }), } } - pub(super) fn model(binding: u32, buffer: &Buffer) -> BindGroupEntry { - entry(binding, MeshUniform::min_size().get(), buffer) + pub(super) fn model(binding: u32, resource: BindingResource) -> BindGroupEntry { + BindGroupEntry { binding, resource } } pub(super) fn skinning(binding: u32, buffer: &Buffer) -> BindGroupEntry { entry(binding, JOINT_BUFFER_SIZE as u64, buffer) @@ -132,20 +137,23 @@ impl MeshLayouts { fn model_only_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[layout_entry::model(0)], + entries: &[layout_entry::model(render_device, 0)], label: Some("mesh_layout"), }) } fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[layout_entry::model(0), layout_entry::skinning(1)], + entries: &[ + layout_entry::model(render_device, 0), + layout_entry::skinning(1), + ], label: Some("skinned_mesh_layout"), }) } fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ - layout_entry::model(0), + layout_entry::model(render_device, 0), layout_entry::weights(2), layout_entry::targets(3), ], @@ -155,7 +163,7 @@ impl MeshLayouts { fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ - layout_entry::model(0), + layout_entry::model(render_device, 0), layout_entry::skinning(1), layout_entry::weights(2), layout_entry::targets(3), @@ -166,9 +174,9 @@ impl MeshLayouts { // ---------- BindGroup methods ---------- - pub fn model_only(&self, render_device: &RenderDevice, model: &Buffer) -> BindGroup { + pub fn model_only(&self, render_device: &RenderDevice, model: &BindingResource) -> BindGroup { render_device.create_bind_group(&BindGroupDescriptor { - entries: &[entry::model(0, model)], + entries: &[entry::model(0, model.clone())], layout: &self.model_only, label: Some("model_only_mesh_bind_group"), }) @@ -176,11 +184,11 @@ impl MeshLayouts { pub fn skinned( &self, render_device: &RenderDevice, - model: &Buffer, + model: &BindingResource, skin: &Buffer, ) -> BindGroup { render_device.create_bind_group(&BindGroupDescriptor { - entries: &[entry::model(0, model), entry::skinning(1, skin)], + entries: &[entry::model(0, model.clone()), entry::skinning(1, skin)], layout: &self.skinned, label: Some("skinned_mesh_bind_group"), }) @@ -188,13 +196,13 @@ impl MeshLayouts { pub fn morphed( &self, render_device: &RenderDevice, - model: &Buffer, + model: &BindingResource, weights: &Buffer, targets: &TextureView, ) -> BindGroup { render_device.create_bind_group(&BindGroupDescriptor { entries: &[ - entry::model(0, model), + entry::model(0, model.clone()), entry::weights(2, weights), entry::targets(3, targets), ], @@ -205,14 +213,14 @@ impl MeshLayouts { pub fn morphed_skinned( &self, render_device: &RenderDevice, - model: &Buffer, + model: &BindingResource, skin: &Buffer, weights: &Buffer, targets: &TextureView, ) -> BindGroup { render_device.create_bind_group(&BindGroupDescriptor { entries: &[ - entry::model(0, model), + entry::model(0, model.clone()), entry::skinning(1, skin), entry::weights(2, weights), entry::targets(3, targets), diff --git a/crates/bevy_pbr/src/render/mesh_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_bindings.wgsl index 2b6d8826811b1..b99bcb1dd48be 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_bindings.wgsl @@ -5,11 +5,19 @@ #ifdef MESH_BINDGROUP_1 @group(1) @binding(0) -var mesh: Mesh; - +#ifdef MESH_BATCH_SIZE +var mesh: array; #else +var mesh: array; +#endif // MESH_BATCH_SIZE + +#else // MESH_BINDGROUP_1 @group(2) @binding(0) -var mesh: Mesh; +#ifdef MESH_BATCH_SIZE +var mesh: array; +#else +var mesh: array; +#endif // MESH_BATCH_SIZE -#endif +#endif // MESH_BINDGROUP_1 diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index 4a55cce62e031..4b5d1da7bdd26 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -20,7 +20,7 @@ fn mesh_position_local_to_clip(model: mat4x4, vertex_position: vec4) - return mesh_position_world_to_clip(world_position); } -fn mesh_normal_local_to_world(vertex_normal: vec3) -> vec3 { +fn mesh_normal_local_to_world(vertex_normal: vec3, instance_index: u32) -> vec3 { // NOTE: The mikktspace method of normal mapping requires that the world normal is // re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents // and normal maps so that the exact inverse process is applied when shading. Blender, Unity, @@ -29,23 +29,23 @@ fn mesh_normal_local_to_world(vertex_normal: vec3) -> vec3 { // http://www.mikktspace.com/ return normalize( mat3x3( - mesh.inverse_transpose_model[0].xyz, - mesh.inverse_transpose_model[1].xyz, - mesh.inverse_transpose_model[2].xyz + mesh[instance_index].inverse_transpose_model[0].xyz, + mesh[instance_index].inverse_transpose_model[1].xyz, + mesh[instance_index].inverse_transpose_model[2].xyz ) * vertex_normal ); } // Calculates the sign of the determinant of the 3x3 model matrix based on a // mesh flag -fn sign_determinant_model_3x3m() -> f32 { +fn sign_determinant_model_3x3m(instance_index: u32) -> f32 { // bool(u32) is false if 0u else true // f32(bool) is 1.0 if true else 0.0 // * 2.0 - 1.0 remaps 0.0 or 1.0 to -1.0 or 1.0 respectively - return f32(bool(mesh.flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0; + return f32(bool(mesh[instance_index].flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0; } -fn mesh_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4) -> vec4 { +fn mesh_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4, instance_index: u32) -> vec4 { // NOTE: The mikktspace method of normal mapping requires that the world tangent is // re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents // and normal maps so that the exact inverse process is applied when shading. Blender, Unity, @@ -62,6 +62,6 @@ fn mesh_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4) -> ), // NOTE: Multiplying by the sign of the determinant of the 3x3 model matrix accounts for // situations such as negative scaling. - vertex_tangent.w * sign_determinant_model_3x3m() + vertex_tangent.w * sign_determinant_model_3x3m(instance_index) ); } diff --git a/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl b/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl index bc3b7c6f58797..4286f60349029 100644 --- a/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl +++ b/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl @@ -1,7 +1,7 @@ #define_import_path bevy_pbr::mesh_vertex_output struct MeshVertexOutput { - // this is `clip position` when the struct is used as a vertex stage output + // this is `clip position` when the struct is used as a vertex stage output // and `frag coord` when used as a fragment stage input @builtin(position) position: vec4, @location(0) world_position: vec4, @@ -15,4 +15,7 @@ struct MeshVertexOutput { #ifdef VERTEX_COLORS @location(4) color: vec4, #endif + #ifdef INSTANCE_INDEX + @location(5) instance_index: u32, + #endif } diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 0a9bd3959b8ba..545b40c5b3a24 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -138,7 +138,7 @@ fn fragment( pbr_input.V = V; pbr_input.occlusion = occlusion; - pbr_input.flags = mesh.flags; + pbr_input.flags = mesh[in.instance_index].flags; output_color = pbr_functions::pbr(pbr_input); } else { diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 4e9fce0242e40..6677eb70e5906 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -223,7 +223,7 @@ fn pbr( for (var i: u32 = offset_and_counts[0]; i < offset_and_counts[0] + offset_and_counts[1]; i = i + 1u) { let light_id = clustering::get_light_id(i); var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal); } @@ -236,7 +236,7 @@ fn pbr( let light_id = clustering::get_light_id(i); var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = shadows::fetch_spot_shadow(light_id, in.world_position, in.world_normal); } @@ -248,7 +248,7 @@ fn pbr( let n_directional_lights = view_bindings::lights.n_directional_lights; for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) { var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u + if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = shadows::fetch_directional_shadow(i, in.world_position, in.world_normal, view_z); } diff --git a/crates/bevy_pbr/src/render/wireframe.wgsl b/crates/bevy_pbr/src/render/wireframe.wgsl index a37a17015e882..2d6a81f821d87 100644 --- a/crates/bevy_pbr/src/render/wireframe.wgsl +++ b/crates/bevy_pbr/src/render/wireframe.wgsl @@ -6,6 +6,7 @@ #endif struct Vertex { + @builtin(instance_index) instance_index: u32, @location(0) position: vec3, #ifdef SKINNED @location(4) joint_indexes: vec4, @@ -22,7 +23,7 @@ fn vertex(vertex: Vertex) -> VertexOutput { #ifdef SKINNED let model = bevy_pbr::skinning::skin_model(vertex.joint_indexes, vertex.joint_weights); #else - let model = mesh.model; + let model = mesh[vertex.instance_index].model; #endif var out: VertexOutput; diff --git a/crates/bevy_render/src/gpu_component_array_buffer.rs b/crates/bevy_render/src/gpu_component_array_buffer.rs index 6076049c7fd2c..d866d31b70da0 100644 --- a/crates/bevy_render/src/gpu_component_array_buffer.rs +++ b/crates/bevy_render/src/gpu_component_array_buffer.rs @@ -17,16 +17,17 @@ pub struct GpuComponentArrayBufferPlugin(Phan impl Plugin for GpuComponentArrayBufferPlugin { fn build(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .insert_resource(GpuArrayBuffer::::new( - render_app.world.resource::(), - )) - .add_systems( - Render, - prepare_gpu_component_array_buffers::.in_set(RenderSet::Prepare), - ); - } + app.sub_app_mut(RenderApp).add_systems( + Render, + prepare_gpu_component_array_buffers::.in_set(RenderSet::Prepare), + ); + } + + fn finish(&self, app: &mut App) { + let render_app = app.sub_app_mut(RenderApp); + render_app.insert_resource(GpuArrayBuffer::::new( + render_app.world.resource::(), + )); } } From 478ee2c745e391d37b1a25baee7816228206e62f Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 24 Jul 2023 11:37:58 +0200 Subject: [PATCH 02/14] Sort passes using a depth buffer first by the per-object binding dynamic offset When using a uniform buffer with batches of per-object data, this provides a ~18% frame time reduction on the many_cubes -- sphere example in the Opaque3d phase and should benefit alpha mask, prepass, and shadow phases in a similar way. --- crates/bevy_core_pipeline/src/core_3d/mod.rs | 26 +++++-- crates/bevy_core_pipeline/src/prepass/mod.rs | 28 ++++++-- crates/bevy_pbr/src/material.rs | 21 ++++-- crates/bevy_pbr/src/prepass/mod.rs | 21 ++++-- crates/bevy_pbr/src/render/light.rs | 25 ++++--- crates/bevy_pbr/src/wireframe.rs | 73 ++++++++++++-------- 6 files changed, 135 insertions(+), 59 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 29e7157165d13..29f72f8122657 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -136,14 +136,16 @@ impl Plugin for Core3dPlugin { pub struct Opaque3d { pub distance: f32, + pub per_object_binding_dynamic_offset: u32, pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, } impl PhaseItem for Opaque3d { + // NOTE: (dynamic offset, -distance) // NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort. - type SortKey = Reverse; + type SortKey = (u32, Reverse); #[inline] fn entity(&self) -> Entity { @@ -152,7 +154,10 @@ impl PhaseItem for Opaque3d { #[inline] fn sort_key(&self) -> Self::SortKey { - Reverse(FloatOrd(self.distance)) + ( + self.per_object_binding_dynamic_offset, + Reverse(FloatOrd(self.distance)), + ) } #[inline] @@ -163,7 +168,9 @@ impl PhaseItem for Opaque3d { #[inline] fn sort(items: &mut [Self]) { // Key negated to match reversed SortKey ordering - radsort::sort_by_key(items, |item| -item.distance); + radsort::sort_by_key(items, |item| { + (item.per_object_binding_dynamic_offset, -item.distance) + }); } } @@ -176,14 +183,16 @@ impl CachedRenderPipelinePhaseItem for Opaque3d { pub struct AlphaMask3d { pub distance: f32, + pub per_object_binding_dynamic_offset: u32, pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, } impl PhaseItem for AlphaMask3d { + // NOTE: (dynamic offset, -distance) // NOTE: Values increase towards the camera. Front-to-back ordering for alpha mask means we need a descending sort. - type SortKey = Reverse; + type SortKey = (u32, Reverse); #[inline] fn entity(&self) -> Entity { @@ -192,7 +201,10 @@ impl PhaseItem for AlphaMask3d { #[inline] fn sort_key(&self) -> Self::SortKey { - Reverse(FloatOrd(self.distance)) + ( + self.per_object_binding_dynamic_offset, + Reverse(FloatOrd(self.distance)), + ) } #[inline] @@ -203,7 +215,9 @@ impl PhaseItem for AlphaMask3d { #[inline] fn sort(items: &mut [Self]) { // Key negated to match reversed SortKey ordering - radsort::sort_by_key(items, |item| -item.distance); + radsort::sort_by_key(items, |item| { + (item.per_object_binding_dynamic_offset, -item.distance) + }); } } diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 93bcebe0318c3..4c6ecad940b11 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -80,14 +80,16 @@ pub struct ViewPrepassTextures { /// Used to render all 3D meshes with materials that have no transparency. pub struct Opaque3dPrepass { pub distance: f32, + pub per_object_binding_dynamic_offset: u32, pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, pub draw_function: DrawFunctionId, } impl PhaseItem for Opaque3dPrepass { + // NOTE: (dynamic offset, -distance) // NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort. - type SortKey = Reverse; + type SortKey = (u32, Reverse); #[inline] fn entity(&self) -> Entity { @@ -96,7 +98,10 @@ impl PhaseItem for Opaque3dPrepass { #[inline] fn sort_key(&self) -> Self::SortKey { - Reverse(FloatOrd(self.distance)) + ( + self.per_object_binding_dynamic_offset, + Reverse(FloatOrd(self.distance)), + ) } #[inline] @@ -107,7 +112,9 @@ impl PhaseItem for Opaque3dPrepass { #[inline] fn sort(items: &mut [Self]) { // Key negated to match reversed SortKey ordering - radsort::sort_by_key(items, |item| -item.distance); + radsort::sort_by_key(items, |item| { + (item.per_object_binding_dynamic_offset, -item.distance) + }); } } @@ -125,14 +132,16 @@ impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { /// Used to render all meshes with a material with an alpha mask. pub struct AlphaMask3dPrepass { pub distance: f32, + pub per_object_binding_dynamic_offset: u32, pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, pub draw_function: DrawFunctionId, } impl PhaseItem for AlphaMask3dPrepass { - // NOTE: Values increase towards the camera. Front-to-back ordering for alpha mask means we need a descending sort. - type SortKey = Reverse; + // NOTE: (dynamic offset, -distance) + // NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort. + type SortKey = (u32, Reverse); #[inline] fn entity(&self) -> Entity { @@ -141,7 +150,10 @@ impl PhaseItem for AlphaMask3dPrepass { #[inline] fn sort_key(&self) -> Self::SortKey { - Reverse(FloatOrd(self.distance)) + ( + self.per_object_binding_dynamic_offset, + Reverse(FloatOrd(self.distance)), + ) } #[inline] @@ -152,7 +164,9 @@ impl PhaseItem for AlphaMask3dPrepass { #[inline] fn sort(items: &mut [Self]) { // Key negated to match reversed SortKey ordering - radsort::sort_by_key(items, |item| -item.distance); + radsort::sort_by_key(items, |item| { + (item.per_object_binding_dynamic_offset, -item.distance) + }); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index ec9e4364b9521..e14c9f9e9ded9 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -30,9 +30,9 @@ use bevy_render::{ RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ - AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource, - PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline, - SpecializedMeshPipelineError, SpecializedMeshPipelines, + AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, GpuArrayBufferIndex, + OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, texture::FallbackImage, @@ -379,7 +379,12 @@ pub fn queue_material_meshes( msaa: Res, render_meshes: Res>, render_materials: Res>, - material_meshes: Query<(&Handle, &Handle, &MeshUniform)>, + material_meshes: Query<( + &Handle, + &Handle, + &MeshUniform, + &GpuArrayBufferIndex, + )>, images: Res>, mut views: Query<( &ExtractedView, @@ -463,7 +468,7 @@ pub fn queue_material_meshes( let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { - if let Ok((material_handle, mesh_handle, mesh_uniform)) = + if let Ok((material_handle, mesh_handle, mesh_uniform, batch_indices)) = material_meshes.get(*visible_entity) { if let (Some(mesh), Some(material)) = ( @@ -520,6 +525,9 @@ pub fn queue_material_meshes( draw_function: draw_opaque_pbr, pipeline: pipeline_id, distance, + per_object_binding_dynamic_offset: batch_indices + .dynamic_offset + .unwrap_or_default(), }); } AlphaMode::Mask(_) => { @@ -528,6 +536,9 @@ pub fn queue_material_meshes( draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, distance, + per_object_binding_dynamic_offset: batch_indices + .dynamic_offset + .unwrap_or_default(), }); } AlphaMode::Blend diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index d8201d89430bf..20a5c96458ae0 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -30,9 +30,9 @@ use bevy_render::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, - DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache, - PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderDefVal, ShaderRef, - ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, + DynamicUniformBuffer, FragmentState, FrontFace, GpuArrayBufferIndex, MultisampleState, + PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderDefVal, + ShaderRef, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType, TextureViewDimension, VertexState, }, @@ -757,7 +757,12 @@ pub fn queue_prepass_material_meshes( msaa: Res, render_meshes: Res>, render_materials: Res>, - material_meshes: Query<(&Handle, &Handle, &MeshUniform)>, + material_meshes: Query<( + &Handle, + &Handle, + &MeshUniform, + &GpuArrayBufferIndex, + )>, mut views: Query<( &ExtractedView, &VisibleEntities, @@ -802,7 +807,7 @@ pub fn queue_prepass_material_meshes( let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { - let Ok((material_handle, mesh_handle, mesh_uniform)) = material_meshes.get(*visible_entity) else { + let Ok((material_handle, mesh_handle, mesh_uniform, batch_indices)) = material_meshes.get(*visible_entity) else { continue; }; @@ -854,6 +859,9 @@ pub fn queue_prepass_material_meshes( draw_function: opaque_draw_prepass, pipeline_id, distance, + per_object_binding_dynamic_offset: batch_indices + .dynamic_offset + .unwrap_or_default(), }); } AlphaMode::Mask(_) => { @@ -862,6 +870,9 @@ pub fn queue_prepass_material_meshes( draw_function: alpha_mask_draw_prepass, pipeline_id, distance, + per_object_binding_dynamic_offset: batch_indices + .dynamic_offset + .unwrap_or_default(), }); } AlphaMode::Blend diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 00beb05b5e91f..23706fb1ca8f3 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -3,8 +3,8 @@ use crate::{ CascadeShadowConfig, Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities, DirectionalLight, DirectionalLightShadowMap, DrawPrepass, EnvironmentMapLight, GlobalVisiblePointLights, Material, MaterialPipelineKey, MeshPipeline, MeshPipelineKey, - NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterials, SpotLight, - VisiblePointLights, + MeshUniform, NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline, + RenderMaterials, SpotLight, VisiblePointLights, }; use bevy_asset::Handle; use bevy_core_pipeline::core_3d::Transparent3d; @@ -1556,7 +1556,10 @@ pub fn prepare_clusters( pub fn queue_shadows( shadow_draw_functions: Res>, prepass_pipeline: Res>, - casting_meshes: Query<(&Handle, &Handle), Without>, + casting_meshes: Query< + (&GpuArrayBufferIndex, &Handle, &Handle), + Without, + >, render_meshes: Res>, render_materials: Res>, mut pipelines: ResMut>>, @@ -1601,7 +1604,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() { - if let Ok((mesh_handle, material_handle)) = casting_meshes.get(entity) { + if let Ok((batch_indices, mesh_handle, material_handle)) = + casting_meshes.get(entity) + { if let (Some(mesh), Some(material)) = ( render_meshes.get(mesh_handle), render_materials.get(material_handle), @@ -1647,7 +1652,10 @@ pub fn queue_shadows( draw_function: draw_shadow_mesh, pipeline: pipeline_id, entity, - distance: 0.0, // TODO: sort back-to-front + distance: 0.0, // TODO: sort front-to-back + per_object_binding_dynamic_offset: batch_indices + .dynamic_offset + .unwrap_or_default(), }); } } @@ -1658,13 +1666,14 @@ pub fn queue_shadows( pub struct Shadow { pub distance: f32, + pub per_object_binding_dynamic_offset: u32, pub entity: Entity, pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, } impl PhaseItem for Shadow { - type SortKey = usize; + type SortKey = (usize, u32); #[inline] fn entity(&self) -> Entity { @@ -1673,7 +1682,7 @@ impl PhaseItem for Shadow { #[inline] fn sort_key(&self) -> Self::SortKey { - self.pipeline.id() + (self.pipeline.id(), self.per_object_binding_dynamic_offset) } #[inline] @@ -1686,7 +1695,7 @@ impl PhaseItem for Shadow { // The shadow phase is sorted by pipeline id for performance reasons. // Grouping all draw commands using the same pipeline together performs // better than rebinding everything at a high rate. - radsort::sort_by_key(items, |item| item.pipeline.id()); + radsort::sort_by_key(items, |item| item.sort_key()); } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 07898068fcd24..1a6757abfe83e 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -7,6 +7,7 @@ use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{Reflect, TypeUuid}; use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; +use bevy_render::render_resource::GpuArrayBufferIndex; use bevy_render::Render; use bevy_render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, @@ -117,8 +118,21 @@ fn queue_wireframes( pipeline_cache: Res, msaa: Res, mut material_meshes: ParamSet<( - Query<(Entity, &Handle, &MeshUniform)>, - Query<(Entity, &Handle, &MeshUniform), With>, + Query<( + Entity, + &Handle, + &MeshUniform, + &GpuArrayBufferIndex, + )>, + Query< + ( + Entity, + &Handle, + &MeshUniform, + &GpuArrayBufferIndex, + ), + With, + >, )>, mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase)>, ) { @@ -128,32 +142,35 @@ fn queue_wireframes( let rangefinder = view.rangefinder3d(); let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); - let add_render_phase = - |(entity, mesh_handle, mesh_uniform): (Entity, &Handle, &MeshUniform)| { - if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = view_key - | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &wireframe_pipeline, - key, - &mesh.layout, - ); - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - return; - } - }; - opaque_phase.add(Opaque3d { - entity, - pipeline: pipeline_id, - draw_function: draw_custom, - distance: rangefinder.distance(&mesh_uniform.transform), - }); - } - }; + let add_render_phase = |(entity, mesh_handle, mesh_uniform, batch_indices): ( + Entity, + &Handle, + &MeshUniform, + &GpuArrayBufferIndex, + )| { + if let Some(mesh) = render_meshes.get(mesh_handle) { + let key = + view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline_id = + pipelines.specialize(&pipeline_cache, &wireframe_pipeline, key, &mesh.layout); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + return; + } + }; + opaque_phase.add(Opaque3d { + entity, + pipeline: pipeline_id, + draw_function: draw_custom, + distance: rangefinder.distance(&mesh_uniform.transform), + per_object_binding_dynamic_offset: batch_indices + .dynamic_offset + .unwrap_or_default(), + }); + } + }; if wireframe_config.global { let query = material_meshes.p0(); From b0698be0e76dd9c04f68151fada6b403e6e3a836 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 24 Jul 2023 11:44:59 +0200 Subject: [PATCH 03/14] Put group and binding directly above binding declarations --- crates/bevy_pbr/src/render/mesh_bindings.wgsl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_bindings.wgsl index b99bcb1dd48be..f3c85c4ea4cce 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_bindings.wgsl @@ -4,19 +4,21 @@ #ifdef MESH_BINDGROUP_1 -@group(1) @binding(0) #ifdef MESH_BATCH_SIZE +@group(1) @binding(0) var mesh: array; #else +@group(1) @binding(0) var mesh: array; #endif // MESH_BATCH_SIZE #else // MESH_BINDGROUP_1 -@group(2) @binding(0) #ifdef MESH_BATCH_SIZE +@group(2) @binding(0) var mesh: array; #else +@group(2) @binding(0) var mesh: array; #endif // MESH_BATCH_SIZE From 40112bfc60b6f226075fb49a4eaeceb2925bd92c Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 24 Jul 2023 11:51:08 +0200 Subject: [PATCH 04/14] Document MeshPipeline per_object_buffer_batch_size --- crates/bevy_pbr/src/prepass/mod.rs | 7 +++++-- crates/bevy_pbr/src/render/mesh.rs | 15 +++++++++++---- crates/bevy_pbr/src/render/mesh_bindings.wgsl | 12 ++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 20a5c96458ae0..7fe1a06a5572e 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -314,7 +314,7 @@ impl FromWorld for PrepassPipeline { view_layout_motion_vectors, view_layout_no_motion_vectors, mesh_layouts: mesh_pipeline.mesh_layouts.clone(), - mesh_buffer_batch_size: mesh_pipeline.mesh_buffer_batch_size, + mesh_buffer_batch_size: mesh_pipeline.per_object_buffer_batch_size, material_vertex_shader: match M::prepass_vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), @@ -355,7 +355,10 @@ where let mut vertex_attributes = Vec::new(); if let Some(batch_size) = self.mesh_buffer_batch_size { - shader_defs.push(ShaderDefVal::UInt("MESH_BATCH_SIZE".into(), batch_size)); + shader_defs.push(ShaderDefVal::UInt( + "PER_OBJECT_BUFFER_BATCH_SIZE".into(), + batch_size, + )); } // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index f22b18fc7c680..f9a725a9fc187 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -310,7 +310,11 @@ pub struct MeshPipeline { pub dummy_white_gpu_image: GpuImage, pub clustered_forward_buffer_binding_type: BufferBindingType, pub mesh_layouts: MeshLayouts, - pub mesh_buffer_batch_size: Option, + // This defines the batch size of the per-object buffer, for example when on WebGL2 and a + // uniform buffer is used within `GpuArrayBuffer` with a fixed-size array of `MeshUniform` + // in batches. Use `ShaderDefVal::UInt("PER_OBJECT_BUFFER_BATCH_SIZE", )` to configure + // this in shaders. + pub per_object_buffer_batch_size: Option, } impl FromWorld for MeshPipeline { @@ -550,7 +554,7 @@ impl FromWorld for MeshPipeline { clustered_forward_buffer_binding_type, dummy_white_gpu_image, mesh_layouts: MeshLayouts::new(&render_device), - mesh_buffer_batch_size: GpuArrayBuffer::::batch_size( + per_object_buffer_batch_size: GpuArrayBuffer::::batch_size( render_device.as_ref(), ), } @@ -713,8 +717,11 @@ impl SpecializedMeshPipeline for MeshPipeline { let mut vertex_attributes = Vec::new(); shader_defs.push("INSTANCE_INDEX".into()); - if let Some(batch_size) = self.mesh_buffer_batch_size { - shader_defs.push(ShaderDefVal::UInt("MESH_BATCH_SIZE".into(), batch_size)); + if let Some(batch_size) = self.per_object_buffer_batch_size { + shader_defs.push(ShaderDefVal::UInt( + "PER_OBJECT_BUFFER_BATCH_SIZE".into(), + batch_size, + )); } if layout.contains(Mesh::ATTRIBUTE_POSITION) { diff --git a/crates/bevy_pbr/src/render/mesh_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_bindings.wgsl index f3c85c4ea4cce..ed638bfbda60a 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_bindings.wgsl @@ -4,22 +4,22 @@ #ifdef MESH_BINDGROUP_1 -#ifdef MESH_BATCH_SIZE +#ifdef PER_OBJECT_BUFFER_BATCH_SIZE @group(1) @binding(0) -var mesh: array; +var mesh: array; #else @group(1) @binding(0) var mesh: array; -#endif // MESH_BATCH_SIZE +#endif // PER_OBJECT_BUFFER_BATCH_SIZE #else // MESH_BINDGROUP_1 -#ifdef MESH_BATCH_SIZE +#ifdef PER_OBJECT_BUFFER_BATCH_SIZE @group(2) @binding(0) -var mesh: array; +var mesh: array; #else @group(2) @binding(0) var mesh: array; -#endif // MESH_BATCH_SIZE +#endif // PER_OBJECT_BUFFER_BATCH_SIZE #endif // MESH_BINDGROUP_1 From 1f2f75eb8ff55d9818c0066f7dce5d22d214eb42 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 24 Jul 2023 11:53:38 +0200 Subject: [PATCH 05/14] Rename INSTANCE_INDEX to VERTEX_OUTPUT_INSTANCE_INDEX for clarity --- crates/bevy_pbr/src/render/mesh.rs | 2 +- crates/bevy_pbr/src/render/mesh.wgsl | 2 +- crates/bevy_pbr/src/render/mesh_vertex_output.wgsl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index f9a725a9fc187..8cce7bbfdcb4f 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -716,7 +716,7 @@ impl SpecializedMeshPipeline for MeshPipeline { let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); - shader_defs.push("INSTANCE_INDEX".into()); + shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); if let Some(batch_size) = self.per_object_buffer_batch_size { shader_defs.push(ShaderDefVal::UInt( "PER_OBJECT_BUFFER_BATCH_SIZE".into(), diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 0dbfcc9758ec6..28a07ebbd2924 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -99,7 +99,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { out.color = vertex.color; #endif -#ifdef INSTANCE_INDEX +#ifdef VERTEX_OUTPUT_INSTANCE_INDEX out.instance_index = vertex.instance_index; #endif diff --git a/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl b/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl index 4286f60349029..32418f8043a7f 100644 --- a/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl +++ b/crates/bevy_pbr/src/render/mesh_vertex_output.wgsl @@ -15,7 +15,7 @@ struct MeshVertexOutput { #ifdef VERTEX_COLORS @location(4) color: vec4, #endif - #ifdef INSTANCE_INDEX + #ifdef VERTEX_OUTPUT_INSTANCE_INDEX @location(5) instance_index: u32, #endif } From 23b47933b81ad8dd6c7773868993077859c514cd Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 24 Jul 2023 12:12:20 +0200 Subject: [PATCH 06/14] Fix no_renderer The RenderApp sub app does not exist when the renderer is disabled so assuming it does is not a good idea. --- .../src/gpu_component_array_buffer.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/bevy_render/src/gpu_component_array_buffer.rs b/crates/bevy_render/src/gpu_component_array_buffer.rs index d866d31b70da0..d9b9c619eac51 100644 --- a/crates/bevy_render/src/gpu_component_array_buffer.rs +++ b/crates/bevy_render/src/gpu_component_array_buffer.rs @@ -17,17 +17,20 @@ pub struct GpuComponentArrayBufferPlugin(Phan impl Plugin for GpuComponentArrayBufferPlugin { fn build(&self, app: &mut App) { - app.sub_app_mut(RenderApp).add_systems( - Render, - prepare_gpu_component_array_buffers::.in_set(RenderSet::Prepare), - ); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_systems( + Render, + prepare_gpu_component_array_buffers::.in_set(RenderSet::Prepare), + ); + } } fn finish(&self, app: &mut App) { - let render_app = app.sub_app_mut(RenderApp); - render_app.insert_resource(GpuArrayBuffer::::new( - render_app.world.resource::(), - )); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.insert_resource(GpuArrayBuffer::::new( + render_app.world.resource::(), + )); + } } } From 505af3e982cab181c9f6362d28a44716ab598d15 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Tue, 25 Jul 2023 19:12:31 +0200 Subject: [PATCH 07/14] Use SmallVec for flexible dynamic offset gathering in SetMeshBindGroup --- crates/bevy_pbr/Cargo.toml | 3 ++- crates/bevy_pbr/src/render/mesh.rs | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 2f3bc6617cc59..8366556275585 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -29,5 +29,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.12.0-dev" } bitflags = "2.3" # direct dependency required for derive macro bytemuck = { version = "1", features = ["derive"] } -radsort = "0.1" naga_oil = "0.8" +radsort = "0.1" +smallvec = "1.6" diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 8cce7bbfdcb4f..6d201fb4671b9 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -42,6 +42,7 @@ use bevy_render::{ }; use bevy_transform::components::GlobalTransform; use bevy_utils::{tracing::error, HashMap, Hashed}; +use smallvec::SmallVec; use crate::render::{ morph::{extract_morphs, prepare_morphs, MorphIndex, MorphUniform}, @@ -1239,19 +1240,18 @@ impl RenderCommand

for SetMeshBindGroup { return RenderCommandResult::Failure; }; - let mut set_bind_group = |indices: &[u32]| pass.set_bind_group(I, bind_group, indices); - match (batch_indices.dynamic_offset, skin_index, morph_index) { - (None, None, None) => set_bind_group(&[]), - (None, Some(skin), None) => set_bind_group(&[skin.index]), - (None, None, Some(morph)) => set_bind_group(&[morph.index]), - (None, Some(skin), Some(morph)) => set_bind_group(&[skin.index, morph.index]), - (Some(mesh_index), None, None) => set_bind_group(&[mesh_index]), - (Some(mesh_index), Some(skin), None) => set_bind_group(&[mesh_index, skin.index]), - (Some(mesh_index), None, Some(morph)) => set_bind_group(&[mesh_index, morph.index]), - (Some(mesh_index), Some(skin), Some(morph)) => { - set_bind_group(&[mesh_index, skin.index, morph.index]); - } - }; + let mut dynamic_offsets: SmallVec<[u32; 3]> = SmallVec::with_capacity(3); + if let Some(mesh_index) = batch_indices.dynamic_offset { + dynamic_offsets.push(mesh_index); + } + if let Some(skin_index) = skin_index { + dynamic_offsets.push(skin_index.index); + } + if let Some(morph_index) = morph_index { + dynamic_offsets.push(morph_index.index); + } + pass.set_bind_group(I, bind_group, &dynamic_offsets); + RenderCommandResult::Success } } From 8c50fdb9e83083ddd9587f4882eb707c0b736c39 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Tue, 25 Jul 2023 19:32:52 +0200 Subject: [PATCH 08/14] Clarify per-object binding dynamic offset's purpose in phase items --- crates/bevy_core_pipeline/src/core_3d/mod.rs | 4 ++++ crates/bevy_core_pipeline/src/prepass/mod.rs | 4 ++++ crates/bevy_pbr/src/render/light.rs | 2 ++ 3 files changed, 10 insertions(+) diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 29f72f8122657..5ea5e9781260f 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -136,6 +136,8 @@ impl Plugin for Core3dPlugin { pub struct Opaque3d { pub distance: f32, + // Per-object data may be bound at different dynamic offsets within a buffer. If it is, then + // each batch of per-object data starts at the same dynamic offset. pub per_object_binding_dynamic_offset: u32, pub pipeline: CachedRenderPipelineId, pub entity: Entity, @@ -183,6 +185,8 @@ impl CachedRenderPipelinePhaseItem for Opaque3d { pub struct AlphaMask3d { pub distance: f32, + // Per-object data may be bound at different dynamic offsets within a buffer. If it is, then + // each batch of per-object data starts at the same dynamic offset. pub per_object_binding_dynamic_offset: u32, pub pipeline: CachedRenderPipelineId, pub entity: Entity, diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 4c6ecad940b11..8edae904f67a7 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -80,6 +80,8 @@ pub struct ViewPrepassTextures { /// Used to render all 3D meshes with materials that have no transparency. pub struct Opaque3dPrepass { pub distance: f32, + // Per-object data may be bound at different dynamic offsets within a buffer. If it is, then + // each batch of per-object data starts at the same dynamic offset. pub per_object_binding_dynamic_offset: u32, pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, @@ -132,6 +134,8 @@ impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { /// Used to render all meshes with a material with an alpha mask. pub struct AlphaMask3dPrepass { pub distance: f32, + // Per-object data may be bound at different dynamic offsets within a buffer. If it is, then + // each batch of per-object data starts at the same dynamic offset. pub per_object_binding_dynamic_offset: u32, pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 23706fb1ca8f3..265a27e026f2d 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1666,6 +1666,8 @@ pub fn queue_shadows( pub struct Shadow { pub distance: f32, + // Per-object data may be bound at different dynamic offsets within a buffer. If it is, then + // each batch of per-object data starts at the same dynamic offset. pub per_object_binding_dynamic_offset: u32, pub entity: Entity, pub pipeline: CachedRenderPipelineId, From dae52b74974b991866a5a0617b5faee82d5df7c2 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Tue, 25 Jul 2023 19:59:32 +0200 Subject: [PATCH 09/14] Use a module-associated shader def for PER_OBJECT_BUFFER_BATCH_SIZE --- crates/bevy_pbr/src/prepass/mod.rs | 13 ++-------- crates/bevy_pbr/src/render/mesh.rs | 41 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 7fe1a06a5572e..a838247791e1d 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -31,8 +31,8 @@ use bevy_render::{ BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, DynamicUniformBuffer, FragmentState, FrontFace, GpuArrayBufferIndex, MultisampleState, - PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderDefVal, - ShaderRef, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, + PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef, + ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType, TextureViewDimension, VertexState, }, @@ -226,7 +226,6 @@ pub struct PrepassPipeline { pub view_layout_motion_vectors: BindGroupLayout, pub view_layout_no_motion_vectors: BindGroupLayout, pub mesh_layouts: MeshLayouts, - pub mesh_buffer_batch_size: Option, pub material_layout: BindGroupLayout, pub material_vertex_shader: Option>, pub material_fragment_shader: Option>, @@ -314,7 +313,6 @@ impl FromWorld for PrepassPipeline { view_layout_motion_vectors, view_layout_no_motion_vectors, mesh_layouts: mesh_pipeline.mesh_layouts.clone(), - mesh_buffer_batch_size: mesh_pipeline.per_object_buffer_batch_size, material_vertex_shader: match M::prepass_vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), @@ -354,13 +352,6 @@ where let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); - if let Some(batch_size) = self.mesh_buffer_batch_size { - shader_defs.push(ShaderDefVal::UInt( - "PER_OBJECT_BUFFER_BATCH_SIZE".into(), - batch_size, - )); - } - // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. // The main limitation right now is that bind group order is hardcoded in shaders. bind_group_layouts.insert(1, self.material_layout.clone()); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 6d201fb4671b9..8813daffe0156 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -106,12 +106,6 @@ impl Plugin for MeshRenderPlugin { Shader::from_wgsl ); load_internal_asset!(app, MESH_TYPES_HANDLE, "mesh_types.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - MESH_BINDINGS_HANDLE, - "mesh_bindings.wgsl", - Shader::from_wgsl - ); load_internal_asset!( app, MESH_FUNCTIONS_HANDLE, @@ -146,9 +140,30 @@ impl Plugin for MeshRenderPlugin { } fn finish(&self, app: &mut bevy_app::App) { + let mut mesh_bindings_shader_defs = Vec::with_capacity(1); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + if let Some(per_object_buffer_batch_size) = GpuArrayBuffer::::batch_size( + render_app.world.resource::(), + ) { + mesh_bindings_shader_defs.push(ShaderDefVal::UInt( + "PER_OBJECT_BUFFER_BATCH_SIZE".into(), + per_object_buffer_batch_size, + )); + } + render_app.init_resource::(); } + + // Load the mesh_bindings shader module here as it depends on runtime information about + // whether storage buffers are supported, or the maximum uniform buffer binding size. + load_internal_asset!( + app, + MESH_BINDINGS_HANDLE, + "mesh_bindings.wgsl", + Shader::from_wgsl_with_defs, + mesh_bindings_shader_defs + ); } } @@ -311,11 +326,6 @@ pub struct MeshPipeline { pub dummy_white_gpu_image: GpuImage, pub clustered_forward_buffer_binding_type: BufferBindingType, pub mesh_layouts: MeshLayouts, - // This defines the batch size of the per-object buffer, for example when on WebGL2 and a - // uniform buffer is used within `GpuArrayBuffer` with a fixed-size array of `MeshUniform` - // in batches. Use `ShaderDefVal::UInt("PER_OBJECT_BUFFER_BATCH_SIZE", )` to configure - // this in shaders. - pub per_object_buffer_batch_size: Option, } impl FromWorld for MeshPipeline { @@ -555,9 +565,6 @@ impl FromWorld for MeshPipeline { clustered_forward_buffer_binding_type, dummy_white_gpu_image, mesh_layouts: MeshLayouts::new(&render_device), - per_object_buffer_batch_size: GpuArrayBuffer::::batch_size( - render_device.as_ref(), - ), } } } @@ -718,12 +725,6 @@ impl SpecializedMeshPipeline for MeshPipeline { let mut vertex_attributes = Vec::new(); shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); - if let Some(batch_size) = self.per_object_buffer_batch_size { - shader_defs.push(ShaderDefVal::UInt( - "PER_OBJECT_BUFFER_BATCH_SIZE".into(), - batch_size, - )); - } if layout.contains(Mesh::ATTRIBUTE_POSITION) { shader_defs.push("VERTEX_POSITIONS".into()); From 331e4fc6cb42a351a6290216c56645d8a3024c5d Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Wed, 26 Jul 2023 22:00:56 +0200 Subject: [PATCH 10/14] Replace SmallVec with slice for performance --- crates/bevy_pbr/src/render/mesh.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 8813daffe0156..97a45e4151c8e 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -42,7 +42,6 @@ use bevy_render::{ }; use bevy_transform::components::GlobalTransform; use bevy_utils::{tracing::error, HashMap, Hashed}; -use smallvec::SmallVec; use crate::render::{ morph::{extract_morphs, prepare_morphs, MorphIndex, MorphUniform}, @@ -1241,17 +1240,21 @@ impl RenderCommand

for SetMeshBindGroup { return RenderCommandResult::Failure; }; - let mut dynamic_offsets: SmallVec<[u32; 3]> = SmallVec::with_capacity(3); + let mut dynamic_offsets: [u32; 3] = Default::default(); + let mut index_count = 0; if let Some(mesh_index) = batch_indices.dynamic_offset { - dynamic_offsets.push(mesh_index); + dynamic_offsets[index_count] = mesh_index; + index_count += 1; } if let Some(skin_index) = skin_index { - dynamic_offsets.push(skin_index.index); + dynamic_offsets[index_count] = skin_index.index; + index_count += 1; } if let Some(morph_index) = morph_index { - dynamic_offsets.push(morph_index.index); + dynamic_offsets[index_count] = morph_index.index; + index_count += 1; } - pass.set_bind_group(I, bind_group, &dynamic_offsets); + pass.set_bind_group(I, bind_group, &dynamic_offsets[0..index_count]); RenderCommandResult::Success } From ba8cf71d313afcaf4d6faa1fd74f3e536f20468f Mon Sep 17 00:00:00 2001 From: Elabajaba Date: Sat, 29 Jul 2023 00:11:37 -0400 Subject: [PATCH 11/14] fix dx12 --- crates/bevy_pbr/src/prepass/prepass.wgsl | 8 ++++---- crates/bevy_pbr/src/render/mesh.wgsl | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 6c98c55ee945f..2b8fd438f9993 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -89,7 +89,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef SKINNED var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // SKINNED - var model = mesh[vertex.instance_index].model; + var model = mesh[vertex_no_morph.instance_index].model; #endif // SKINNED out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); @@ -108,7 +108,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #else // SKINNED out.world_normal = bevy_pbr::mesh_functions::mesh_normal_local_to_world( vertex.normal, - vertex.instance_index + vertex_no_morph.instance_index ); #endif // SKINNED @@ -116,14 +116,14 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.world_tangent = bevy_pbr::mesh_functions::mesh_tangent_local_to_world( model, vertex.tangent, - vertex.instance_index + vertex_no_morph.instance_index ); #endif // VERTEX_TANGENTS #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); - out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh[vertex.instance_index].previous_model, vec4(vertex.position, 1.0)); + out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh[vertex_no_morph.instance_index].previous_model, vec4(vertex.position, 1.0)); #endif // MOTION_VECTOR_PREPASS return out; diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 28a07ebbd2924..5e1ef9c07a19d 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -64,7 +64,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #ifdef SKINNED var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else - var model = mesh[vertex.instance_index].model; + var model = mesh[vertex_no_morph.instance_index].model; #endif #ifdef VERTEX_NORMALS @@ -73,7 +73,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #else out.world_normal = mesh_functions::mesh_normal_local_to_world( vertex.normal, - vertex.instance_index + vertex_no_morph.instance_index ); #endif #endif @@ -91,7 +91,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { out.world_tangent = mesh_functions::mesh_tangent_local_to_world( model, vertex.tangent, - vertex.instance_index + vertex_no_morph.instance_index ); #endif @@ -100,7 +100,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #endif #ifdef VERTEX_OUTPUT_INSTANCE_INDEX - out.instance_index = vertex.instance_index; + out.instance_index = vertex_no_morph.instance_index; #endif return out; From f1ece4ad2982507ad51c7025602ed2d97cab49d6 Mon Sep 17 00:00:00 2001 From: Elabajaba Date: Sat, 29 Jul 2023 00:24:06 -0400 Subject: [PATCH 12/14] add comments on why we're using vertex_no_morph.instance index instead of vertex.instance_index --- crates/bevy_pbr/src/prepass/prepass.wgsl | 4 ++++ crates/bevy_pbr/src/render/mesh.wgsl | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 2b8fd438f9993..b585bc4a59ca1 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -89,6 +89,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef SKINNED var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // SKINNED + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. var model = mesh[vertex_no_morph.instance_index].model; #endif // SKINNED @@ -108,6 +109,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #else // SKINNED out.world_normal = bevy_pbr::mesh_functions::mesh_normal_local_to_world( vertex.normal, + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. vertex_no_morph.instance_index ); #endif // SKINNED @@ -116,6 +118,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.world_tangent = bevy_pbr::mesh_functions::mesh_tangent_local_to_world( model, vertex.tangent, + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. vertex_no_morph.instance_index ); #endif // VERTEX_TANGENTS @@ -123,6 +126,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef MOTION_VECTOR_PREPASS out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh[vertex_no_morph.instance_index].previous_model, vec4(vertex.position, 1.0)); #endif // MOTION_VECTOR_PREPASS diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 5e1ef9c07a19d..61a54358dc3b6 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -64,6 +64,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #ifdef SKINNED var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. var model = mesh[vertex_no_morph.instance_index].model; #endif @@ -73,6 +74,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #else out.world_normal = mesh_functions::mesh_normal_local_to_world( vertex.normal, + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. vertex_no_morph.instance_index ); #endif @@ -91,6 +93,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { out.world_tangent = mesh_functions::mesh_tangent_local_to_world( model, vertex.tangent, + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. vertex_no_morph.instance_index ); #endif @@ -100,6 +103,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #endif #ifdef VERTEX_OUTPUT_INSTANCE_INDEX + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. out.instance_index = vertex_no_morph.instance_index; #endif From f41899ae765a148f28156dbfada819acbdee45a1 Mon Sep 17 00:00:00 2001 From: Elabajaba Date: Sat, 29 Jul 2023 16:43:11 -0400 Subject: [PATCH 13/14] add links to naga issue --- crates/bevy_pbr/src/prepass/prepass.wgsl | 4 ++++ crates/bevy_pbr/src/render/mesh.wgsl | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index b585bc4a59ca1..73cc46a219df0 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -90,6 +90,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // SKINNED // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 var model = mesh[vertex_no_morph.instance_index].model; #endif // SKINNED @@ -110,6 +111,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.world_normal = bevy_pbr::mesh_functions::mesh_normal_local_to_world( vertex.normal, // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 vertex_no_morph.instance_index ); #endif // SKINNED @@ -119,6 +121,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { model, vertex.tangent, // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 vertex_no_morph.instance_index ); #endif // VERTEX_TANGENTS @@ -127,6 +130,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef MOTION_VECTOR_PREPASS out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh[vertex_no_morph.instance_index].previous_model, vec4(vertex.position, 1.0)); #endif // MOTION_VECTOR_PREPASS diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 61a54358dc3b6..c5f3d327b6301 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -65,6 +65,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { var model = bevy_pbr::skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 var model = mesh[vertex_no_morph.instance_index].model; #endif @@ -75,6 +76,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { out.world_normal = mesh_functions::mesh_normal_local_to_world( vertex.normal, // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 vertex_no_morph.instance_index ); #endif @@ -94,6 +96,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { model, vertex.tangent, // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 vertex_no_morph.instance_index ); #endif @@ -104,6 +107,7 @@ fn vertex(vertex_no_morph: Vertex) -> MeshVertexOutput { #ifdef VERTEX_OUTPUT_INSTANCE_INDEX // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 out.instance_index = vertex_no_morph.instance_index; #endif From 3525d82bce5305653197b3805c8e9e873275b7da Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sun, 30 Jul 2023 14:52:55 +0200 Subject: [PATCH 14/14] Break long line --- crates/bevy_pbr/src/prepass/prepass.wgsl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 73cc46a219df0..088cb645e981e 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -131,7 +131,10 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 - out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh[vertex_no_morph.instance_index].previous_model, vec4(vertex.position, 1.0)); + out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world( + mesh[vertex_no_morph.instance_index].previous_model, + vec4(vertex.position, 1.0) + ); #endif // MOTION_VECTOR_PREPASS return out;