Skip to content

Commit a861452

Browse files
Add user supplied mesh tag (#17648)
# Objective Because of mesh preprocessing, users cannot rely on `@builtin(instance_index)` in order to reference external data, as the instance index is not stable, either from frame to frame or relative to the total spawn order of mesh instances. ## Solution Add a user supplied mesh index that can be used for referencing external data when drawing instanced meshes. Closes #13373 ## Testing Benchmarked `many_cubes` showing no difference in total frame time. ## Showcase https://github.com/user-attachments/assets/80620147-aafc-4d9d-a8ee-e2149f7c8f3b --------- Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
1 parent 71b2239 commit a861452

File tree

15 files changed

+196
-68
lines changed

15 files changed

+196
-68
lines changed
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#import bevy_pbr::{
2+
mesh_functions,
3+
view_transformations::position_world_to_clip
4+
}
5+
6+
@group(2) @binding(0) var texture: texture_2d<f32>;
7+
@group(2) @binding(1) var texture_sampler: sampler;
8+
9+
struct Vertex {
10+
@builtin(instance_index) instance_index: u32,
11+
@location(0) position: vec3<f32>,
12+
};
13+
14+
struct VertexOutput {
15+
@builtin(position) clip_position: vec4<f32>,
16+
@location(0) world_position: vec4<f32>,
17+
@location(1) color: vec4<f32>,
18+
};
19+
20+
@vertex
21+
fn vertex(vertex: Vertex) -> VertexOutput {
22+
var out: VertexOutput;
23+
24+
// Lookup the tag for the given mesh
25+
let tag = mesh_functions::get_tag(vertex.instance_index);
26+
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
27+
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
28+
out.clip_position = position_world_to_clip(out.world_position.xyz);
29+
30+
let tex_dim = textureDimensions(texture);
31+
// Find the texel coordinate as derived from the tag
32+
let texel_coord = vec2<u32>(tag % tex_dim.x, tag / tex_dim.x);
33+
34+
out.color = textureLoad(texture, texel_coord, 0);
35+
return out;
36+
}
37+
38+
@fragment
39+
fn fragment(
40+
mesh: VertexOutput,
41+
) -> @location(0) vec4<f32> {
42+
return mesh.color;
43+
}

assets/shaders/storage_buffer.wgsl

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
}
55

66
@group(2) @binding(0) var<storage, read> colors: array<vec4<f32>, 5>;
7-
@group(2) @binding(1) var<uniform> color_id: u32;
87

98
struct Vertex {
109
@builtin(instance_index) instance_index: u32,
@@ -20,11 +19,12 @@ struct VertexOutput {
2019
@vertex
2120
fn vertex(vertex: Vertex) -> VertexOutput {
2221
var out: VertexOutput;
22+
let tag = mesh_functions::get_tag(vertex.instance_index);
2323
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
2424
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
2525
out.clip_position = position_world_to_clip(out.world_position.xyz);
2626

27-
out.color = colors[color_id];
27+
out.color = colors[tag];
2828
return out;
2929
}
3030

crates/bevy_pbr/src/meshlet/instance_manager.rs

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ impl InstanceManager {
125125
None,
126126
None,
127127
None,
128+
None,
128129
);
129130

130131
// Append instance data

crates/bevy_pbr/src/render/mesh.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,8 @@ pub struct MeshUniform {
485485
/// Low 16 bits: index of the material inside the bind group data.
486486
/// High 16 bits: index of the lightmap in the binding array.
487487
pub material_and_lightmap_bind_group_slot: u32,
488+
/// User supplied tag to identify this mesh instance.
489+
pub tag: u32,
488490
}
489491

490492
/// Information that has to be transferred from CPU to GPU in order to produce
@@ -541,10 +543,10 @@ pub struct MeshInputUniform {
541543
/// Low 16 bits: index of the material inside the bind group data.
542544
/// High 16 bits: index of the lightmap in the binding array.
543545
pub material_and_lightmap_bind_group_slot: u32,
546+
/// User supplied tag to identify this mesh instance.
547+
pub tag: u32,
544548
/// Padding.
545-
pub pad_a: u32,
546-
/// Padding.
547-
pub pad_b: u32,
549+
pub pad: u32,
548550
}
549551

550552
/// Information about each mesh instance needed to cull it on GPU.
@@ -578,6 +580,7 @@ impl MeshUniform {
578580
maybe_lightmap: Option<(LightmapSlotIndex, Rect)>,
579581
current_skin_index: Option<u32>,
580582
previous_skin_index: Option<u32>,
583+
tag: Option<u32>,
581584
) -> Self {
582585
let (local_from_world_transpose_a, local_from_world_transpose_b) =
583586
mesh_transforms.world_from_local.inverse_transpose_3x3();
@@ -598,6 +601,7 @@ impl MeshUniform {
598601
previous_skin_index: previous_skin_index.unwrap_or(u32::MAX),
599602
material_and_lightmap_bind_group_slot: u32::from(material_bind_group_slot)
600603
| ((lightmap_bind_group_slot as u32) << 16),
604+
tag: tag.unwrap_or(0),
601605
}
602606
}
603607
}
@@ -729,6 +733,8 @@ pub struct RenderMeshInstanceShared {
729733
/// Index of the slab that the lightmap resides in, if a lightmap is
730734
/// present.
731735
pub lightmap_slab_index: Option<LightmapSlabIndex>,
736+
/// User supplied tag to identify this mesh instance.
737+
pub tag: u32,
732738
}
733739

734740
/// Information that is gathered during the parallel portion of mesh extraction
@@ -808,6 +814,7 @@ impl RenderMeshInstanceShared {
808814
fn from_components(
809815
previous_transform: Option<&PreviousGlobalTransform>,
810816
mesh: &Mesh3d,
817+
tag: Option<&MeshTag>,
811818
not_shadow_caster: bool,
812819
no_automatic_batching: bool,
813820
) -> Self {
@@ -828,6 +835,7 @@ impl RenderMeshInstanceShared {
828835
// This gets filled in later, during `RenderMeshGpuBuilder::update`.
829836
material_bindings_index: default(),
830837
lightmap_slab_index: None,
838+
tag: tag.map_or(0, |i| **i),
831839
}
832840
}
833841

@@ -1160,8 +1168,8 @@ impl RenderMeshInstanceGpuBuilder {
11601168
material_and_lightmap_bind_group_slot: u32::from(
11611169
self.shared.material_bindings_index.slot,
11621170
) | ((lightmap_slot as u32) << 16),
1163-
pad_a: 0,
1164-
pad_b: 0,
1171+
tag: self.shared.tag,
1172+
pad: 0,
11651173
};
11661174

11671175
// Did the last frame contain this entity as well?
@@ -1296,6 +1304,7 @@ pub fn extract_meshes_for_cpu_building(
12961304
&GlobalTransform,
12971305
Option<&PreviousGlobalTransform>,
12981306
&Mesh3d,
1307+
Option<&MeshTag>,
12991308
Has<NoFrustumCulling>,
13001309
Has<NotShadowReceiver>,
13011310
Has<TransmittedShadowReceiver>,
@@ -1314,6 +1323,7 @@ pub fn extract_meshes_for_cpu_building(
13141323
transform,
13151324
previous_transform,
13161325
mesh,
1326+
tag,
13171327
no_frustum_culling,
13181328
not_shadow_receiver,
13191329
transmitted_receiver,
@@ -1341,6 +1351,7 @@ pub fn extract_meshes_for_cpu_building(
13411351
let shared = RenderMeshInstanceShared::from_components(
13421352
previous_transform,
13431353
mesh,
1354+
tag,
13441355
not_shadow_caster,
13451356
no_automatic_batching,
13461357
);
@@ -1402,6 +1413,7 @@ pub fn extract_meshes_for_gpu_building(
14021413
Option<&Lightmap>,
14031414
Option<&Aabb>,
14041415
&Mesh3d,
1416+
Option<&MeshTag>,
14051417
Has<NoFrustumCulling>,
14061418
Has<NotShadowReceiver>,
14071419
Has<TransmittedShadowReceiver>,
@@ -1459,6 +1471,7 @@ pub fn extract_meshes_for_gpu_building(
14591471
lightmap,
14601472
aabb,
14611473
mesh,
1474+
tag,
14621475
no_frustum_culling,
14631476
not_shadow_receiver,
14641477
transmitted_receiver,
@@ -1487,6 +1500,7 @@ pub fn extract_meshes_for_gpu_building(
14871500
let shared = RenderMeshInstanceShared::from_components(
14881501
previous_transform,
14891502
mesh,
1503+
tag,
14901504
not_shadow_caster,
14911505
no_automatic_batching,
14921506
);
@@ -1840,6 +1854,7 @@ impl GetBatchData for MeshPipeline {
18401854
maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)),
18411855
current_skin_index,
18421856
previous_skin_index,
1857+
Some(mesh_instance.tag),
18431858
),
18441859
mesh_instance.should_batch().then_some((
18451860
material_bind_group_index.group,
@@ -1907,6 +1922,7 @@ impl GetFullBatchData for MeshPipeline {
19071922
maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)),
19081923
current_skin_index,
19091924
previous_skin_index,
1925+
Some(mesh_instance.tag),
19101926
))
19111927
}
19121928

crates/bevy_pbr/src/render/mesh_functions.wgsl

+4
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,7 @@ fn get_visibility_range_dither_level(instance_index: u32, world_position: vec4<f
132132
return offset + clamp(level, 0, 16);
133133
}
134134
#endif
135+
136+
fn get_tag(instance_index: u32) -> u32 {
137+
return mesh[instance_index].tag;
138+
}

crates/bevy_pbr/src/render/mesh_preprocess.wgsl

+1
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,5 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3<u32>) {
345345
output[mesh_output_index].previous_skin_index = current_input[input_index].previous_skin_index;
346346
output[mesh_output_index].material_and_lightmap_bind_group_slot =
347347
current_input[input_index].material_and_lightmap_bind_group_slot;
348+
output[mesh_output_index].tag = current_input[input_index].tag;
348349
}

crates/bevy_pbr/src/render/mesh_types.wgsl

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ struct Mesh {
2222
// Low 16 bits: index of the material inside the bind group data.
2323
// High 16 bits: index of the lightmap in the binding array.
2424
material_and_lightmap_bind_group_slot: u32,
25+
// User supplied index to identify the mesh instance
26+
tag: u32,
2527
};
2628

2729
#ifdef SKINNED

crates/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ struct MeshInput {
1919
// Low 16 bits: index of the material inside the bind group data.
2020
// High 16 bits: index of the lightmap in the binding array.
2121
material_and_lightmap_bind_group_slot: u32,
22-
pad_a: u32,
23-
pad_b: u32,
22+
// User supplied index to identify the mesh instance
23+
tag: u32,
24+
pad: u32,
2425
}
2526

2627
// The `wgpu` indirect parameters structure. This is a union of two structures.

crates/bevy_render/src/mesh/components.rs

+5
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,8 @@ pub fn mark_3d_meshes_as_changed_if_their_assets_changed(
150150
}
151151
}
152152
}
153+
154+
/// A component that stores an arbitrary index used to identify the mesh instance when rendering.
155+
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)]
156+
#[reflect(Component, Default)]
157+
pub struct MeshTag(pub u32);

crates/bevy_render/src/mesh/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use bevy_ecs::{
2121
SystemParamItem,
2222
},
2323
};
24-
pub use components::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh2d, Mesh3d};
24+
pub use components::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh2d, Mesh3d, MeshTag};
2525
use wgpu::IndexFormat;
2626

2727
/// Registers all [`MeshBuilder`] types.

crates/bevy_sprite/src/mesh2d/mesh.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use bevy_ecs::{
1919
};
2020
use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo};
2121
use bevy_math::{Affine3, Vec4};
22+
use bevy_render::mesh::MeshTag;
2223
use bevy_render::prelude::Msaa;
2324
use bevy_render::RenderSet::PrepareAssets;
2425
use bevy_render::{
@@ -230,17 +231,19 @@ pub struct Mesh2dUniform {
230231
pub local_from_world_transpose_a: [Vec4; 2],
231232
pub local_from_world_transpose_b: f32,
232233
pub flags: u32,
234+
pub tag: u32,
233235
}
234236

235-
impl From<&Mesh2dTransforms> for Mesh2dUniform {
236-
fn from(mesh_transforms: &Mesh2dTransforms) -> Self {
237+
impl Mesh2dUniform {
238+
fn from_components(mesh_transforms: &Mesh2dTransforms, tag: u32) -> Self {
237239
let (local_from_world_transpose_a, local_from_world_transpose_b) =
238240
mesh_transforms.world_from_local.inverse_transpose_3x3();
239241
Self {
240242
world_from_local: mesh_transforms.world_from_local.to_transpose(),
241243
local_from_world_transpose_a,
242244
local_from_world_transpose_b,
243245
flags: mesh_transforms.flags,
246+
tag,
244247
}
245248
}
246249
}
@@ -259,6 +262,7 @@ pub struct RenderMesh2dInstance {
259262
pub mesh_asset_id: AssetId<Mesh>,
260263
pub material_bind_group_id: Material2dBindGroupId,
261264
pub automatic_batching: bool,
265+
pub tag: u32,
262266
}
263267

264268
#[derive(Default, Resource, Deref, DerefMut)]
@@ -275,13 +279,14 @@ pub fn extract_mesh2d(
275279
&ViewVisibility,
276280
&GlobalTransform,
277281
&Mesh2d,
282+
Option<&MeshTag>,
278283
Has<NoAutomaticBatching>,
279284
)>,
280285
>,
281286
) {
282287
render_mesh_instances.clear();
283288

284-
for (entity, view_visibility, transform, handle, no_automatic_batching) in &query {
289+
for (entity, view_visibility, transform, handle, tag, no_automatic_batching) in &query {
285290
if !view_visibility.get() {
286291
continue;
287292
}
@@ -295,6 +300,7 @@ pub fn extract_mesh2d(
295300
mesh_asset_id: handle.0.id(),
296301
material_bind_group_id: Material2dBindGroupId::default(),
297302
automatic_batching: !no_automatic_batching,
303+
tag: tag.map_or(0, |i| **i),
298304
},
299305
);
300306
}
@@ -422,7 +428,7 @@ impl GetBatchData for Mesh2dPipeline {
422428
) -> Option<(Self::BufferData, Option<Self::CompareData>)> {
423429
let mesh_instance = mesh_instances.get(&main_entity)?;
424430
Some((
425-
(&mesh_instance.transforms).into(),
431+
Mesh2dUniform::from_components(&mesh_instance.transforms, mesh_instance.tag),
426432
mesh_instance.automatic_batching.then_some((
427433
mesh_instance.material_bind_group_id,
428434
mesh_instance.mesh_asset_id,
@@ -439,7 +445,10 @@ impl GetFullBatchData for Mesh2dPipeline {
439445
main_entity: MainEntity,
440446
) -> Option<Self::BufferData> {
441447
let mesh_instance = mesh_instances.get(&main_entity)?;
442-
Some((&mesh_instance.transforms).into())
448+
Some(Mesh2dUniform::from_components(
449+
&mesh_instance.transforms,
450+
mesh_instance.tag,
451+
))
443452
}
444453

445454
fn get_index_and_compare_data(

examples/2d/mesh2d_manual.rs

+1
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ pub fn extract_colored_mesh2d(
352352
transforms,
353353
material_bind_group_id: Material2dBindGroupId::default(),
354354
automatic_batching: false,
355+
tag: 0,
355356
},
356357
);
357358
}

0 commit comments

Comments
 (0)