diff --git a/assets/shaders/custom_vertex_attribute.wgsl b/assets/shaders/custom_vertex_attribute.wgsl index 2d1307bc7e..6ce1100df7 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 cf41bb0131..055c9dbf41 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_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 29e7157165..5ea5e97812 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -136,14 +136,18 @@ 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, 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 +156,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 +170,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 +185,18 @@ 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, 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 +205,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 +219,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 93bcebe031..8edae904f6 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -80,14 +80,18 @@ 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, 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 +100,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 +114,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 +134,18 @@ 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, 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 +154,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 +168,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/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 2f3bc6617c..8366556275 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/material.rs b/crates/bevy_pbr/src/material.rs index ec9e4364b9..e14c9f9e9d 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 a0364acd7f..a838247791 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, ShaderRef, ShaderStages, - ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, + DynamicUniformBuffer, FragmentState, FrontFace, GpuArrayBufferIndex, MultisampleState, + PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef, + ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType, TextureViewDimension, VertexState, }, @@ -751,7 +751,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, @@ -796,7 +801,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; }; @@ -848,6 +853,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(_) => { @@ -856,6 +864,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/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 50cbca9948..088cb645e9 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,9 @@ 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; + // 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 out.clip_position = bevy_pbr::mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); @@ -105,17 +108,33 @@ 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, + // 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 #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, + // 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 #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)); + // 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 return out; diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl index 825b0fa245..c4d039d14d 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/light.rs b/crates/bevy_pbr/src/render/light.rs index 00beb05b5e..265a27e026 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,16 @@ 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, pub draw_function: DrawFunctionId, } impl PhaseItem for Shadow { - type SortKey = usize; + type SortKey = (usize, u32); #[inline] fn entity(&self) -> Entity { @@ -1673,7 +1684,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 +1697,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/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index aa4ab31e5d..97a45e4151 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, @@ -105,12 +105,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, @@ -121,7 +115,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 @@ -145,9 +139,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 + ); } } @@ -309,7 +324,6 @@ 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, } @@ -709,6 +723,8 @@ impl SpecializedMeshPipeline for MeshPipeline { let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); + shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); + if layout.contains(Mesh::ATTRIBUTE_POSITION) { shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); @@ -932,29 +948,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 +1214,7 @@ impl RenderCommand

for SetMeshBindGroup { type ViewWorldQuery = (); type ItemWorldQuery = ( Read>, - Read>, + Read>, Option>, Option>, ); @@ -1207,7 +1223,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,14 +1239,23 @@ 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]), - }; + + let mut dynamic_offsets: [u32; 3] = Default::default(); + let mut index_count = 0; + if let Some(mesh_index) = batch_indices.dynamic_offset { + dynamic_offsets[index_count] = mesh_index; + index_count += 1; + } + if let Some(skin_index) = skin_index { + dynamic_offsets[index_count] = skin_index.index; + index_count += 1; + } + if let Some(morph_index) = morph_index { + dynamic_offsets[index_count] = morph_index.index; + index_count += 1; + } + pass.set_bind_group(I, bind_group, &dynamic_offsets[0..index_count]); + RenderCommandResult::Success } } @@ -1239,12 +1264,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 +1282,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 a99017ce62..c5f3d327b6 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,21 @@ 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; + // 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 #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, + // 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 #endif @@ -84,13 +92,25 @@ 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, + // 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 #ifdef VERTEX_COLORS out.color = vertex.color; #endif +#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 + return out; } diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 3b600a7e29..e46af242fd 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 2b6d882681..ed638bfbda 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_bindings.wgsl @@ -4,12 +4,22 @@ #ifdef MESH_BINDGROUP_1 +#ifdef PER_OBJECT_BUFFER_BATCH_SIZE @group(1) @binding(0) -var mesh: Mesh; - +var mesh: array; #else +@group(1) @binding(0) +var mesh: array; +#endif // PER_OBJECT_BUFFER_BATCH_SIZE +#else // MESH_BINDGROUP_1 + +#ifdef PER_OBJECT_BUFFER_BATCH_SIZE @group(2) @binding(0) -var mesh: Mesh; +var mesh: array; +#else +@group(2) @binding(0) +var mesh: array; +#endif // PER_OBJECT_BUFFER_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 4a55cce62e..4b5d1da7bd 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 bc3b7c6f58..32418f8043 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 VERTEX_OUTPUT_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 0a9bd3959b..545b40c5b3 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 4e9fce0242..6677eb70e5 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 a37a17015e..2d6a81f821 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_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 07898068fc..1a6757abfe 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(); diff --git a/crates/bevy_render/src/gpu_component_array_buffer.rs b/crates/bevy_render/src/gpu_component_array_buffer.rs index 6076049c7f..d9b9c619ea 100644 --- a/crates/bevy_render/src/gpu_component_array_buffer.rs +++ b/crates/bevy_render/src/gpu_component_array_buffer.rs @@ -18,14 +18,18 @@ 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), - ); + render_app.add_systems( + Render, + prepare_gpu_component_array_buffers::.in_set(RenderSet::Prepare), + ); + } + } + + fn finish(&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::(), + )); } } }