Use GpuArrayBuffer for MeshUniform (#9254)

# Objective

- Reduce the number of rebindings to enable batching of draw commands

## Solution

- Use the new `GpuArrayBuffer` for `MeshUniform` data to store all
`MeshUniform` data in arrays within fewer bindings
- Sort opaque/alpha mask prepass, opaque/alpha mask main, and shadow
phases also by the batch per-object data binding dynamic offset to
improve performance on WebGL2.

---

## Changelog

- Changed: Per-object `MeshUniform` data is now managed by
`GpuArrayBuffer` as arrays in buffers that need to be indexed into.

## Migration Guide

Accessing the `model` member of an individual mesh object's shader
`Mesh` struct the old way where each `MeshUniform` was stored at its own
dynamic offset:
```rust
struct Vertex {
    @location(0) position: vec3<f32>,
};

fn vertex(vertex: Vertex) -> VertexOutput {
    var out: VertexOutput;
    out.clip_position = mesh_position_local_to_clip(
        mesh.model,
        vec4<f32>(vertex.position, 1.0)
    );
    return out;
}
```

The new way where one needs to index into the array of `Mesh`es for the
batch:
```rust
struct Vertex {
    @builtin(instance_index) instance_index: u32,
    @location(0) position: vec3<f32>,
};

fn vertex(vertex: Vertex) -> VertexOutput {
    var out: VertexOutput;
    out.clip_position = mesh_position_local_to_clip(
        mesh[vertex.instance_index].model,
        vec4<f32>(vertex.position, 1.0)
    );
    return out;
}
```
Note that using the instance_index is the default way to pass the
per-object index into the shader, but if you wish to do custom rendering
approaches you can pass it in however you like.

---------

Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com>
Co-authored-by: Elabajaba <Elabajaba@users.noreply.github.com>
This commit is contained in:
Robert Swain 2023-07-30 15:17:08 +02:00 committed by GitHub
parent fb9c5a6cbb
commit e6405bb7b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 335 additions and 151 deletions

View file

@ -8,6 +8,7 @@ struct CustomMaterial {
var<uniform> material: CustomMaterial;
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
@location(1) blend_color: vec4<f32>,
};
@ -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<f32>(vertex.position, 1.0)
mesh[vertex.instance_index].model,
vec4<f32>(vertex.position, 1.0),
);
out.blend_color = vertex.blend_color;
return out;

View file

@ -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<f32>(position, 1.0)
);
out.color = vertex.i_color;

View file

@ -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<FloatOrd>;
type SortKey = (u32, Reverse<FloatOrd>);
#[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<FloatOrd>;
type SortKey = (u32, Reverse<FloatOrd>);
#[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)
});
}
}

View file

@ -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<FloatOrd>;
type SortKey = (u32, Reverse<FloatOrd>);
#[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<FloatOrd>;
// 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<FloatOrd>);
#[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)
});
}
}

View file

@ -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"

View file

@ -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<M: Material>(
msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderMaterials<M>>,
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshUniform)>,
material_meshes: Query<(
&Handle<M>,
&Handle<Mesh>,
&MeshUniform,
&GpuArrayBufferIndex<MeshUniform>,
)>,
images: Res<RenderAssets<Image>>,
mut views: Query<(
&ExtractedView,
@ -463,7 +468,7 @@ pub fn queue_material_meshes<M: Material>(
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<M: Material>(
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<M: Material>(
draw_function: draw_alpha_mask_pbr,
pipeline: pipeline_id,
distance,
per_object_binding_dynamic_offset: batch_indices
.dynamic_offset
.unwrap_or_default(),
});
}
AlphaMode::Blend

View file

@ -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<M: Material>(
msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderMaterials<M>>,
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshUniform)>,
material_meshes: Query<(
&Handle<M>,
&Handle<Mesh>,
&MeshUniform,
&GpuArrayBufferIndex<MeshUniform>,
)>,
mut views: Query<(
&ExtractedView,
&VisibleEntities,
@ -796,7 +801,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
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<M: Material>(
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<M: Material>(
draw_function: alpha_mask_draw_prepass,
pipeline_id,
distance,
per_object_binding_dynamic_offset: batch_indices
.dynamic_offset
.unwrap_or_default(),
});
}
AlphaMode::Blend

View file

@ -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<f32>,
#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<f32>(vertex.position, 1.0));
out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(mesh.previous_model, vec4<f32>(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<f32>(vertex.position, 1.0)
);
#endif // MOTION_VECTOR_PREPASS
return out;

View file

@ -15,5 +15,4 @@ var<uniform> previous_view_proj: mat4x4<f32>;
// Material bindings will be in @group(1)
@group(2) @binding(0)
var<uniform> mesh: bevy_pbr::mesh_types::Mesh;
#import bevy_pbr::mesh_bindings mesh

View file

@ -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<M: Material>(
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
prepass_pipeline: Res<PrepassPipeline<M>>,
casting_meshes: Query<(&Handle<Mesh>, &Handle<M>), Without<NotShadowCaster>>,
casting_meshes: Query<
(&GpuArrayBufferIndex<MeshUniform>, &Handle<Mesh>, &Handle<M>),
Without<NotShadowCaster>,
>,
render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderMaterials<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
@ -1601,7 +1604,9 @@ pub fn queue_shadows<M: Material>(
// 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<M: Material>(
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<M: Material>(
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());
}
}

View file

@ -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::<MeshUniform>::default());
app.add_plugins(GpuComponentArrayBufferPlugin::<MeshUniform>::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::<MeshUniform>::batch_size(
render_app.world.resource::<RenderDevice>(),
) {
mesh_bindings_shader_defs.push(ShaderDefVal::UInt(
"PER_OBJECT_BUFFER_BATCH_SIZE".into(),
per_object_buffer_batch_size,
));
}
render_app.init_resource::<MeshPipeline>();
}
// 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<MeshBindGroups>,
mesh_pipeline: Res<MeshPipeline>,
render_device: Res<RenderDevice>,
mesh_uniforms: Res<ComponentUniforms<MeshUniform>>,
mesh_uniforms: Res<GpuArrayBuffer<MeshUniform>>,
skinned_mesh_uniform: Res<SkinnedMeshUniform>,
weights_uniform: Res<MorphUniform>,
) {
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<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
type ViewWorldQuery = ();
type ItemWorldQuery = (
Read<Handle<Mesh>>,
Read<DynamicUniformIndex<MeshUniform>>,
Read<GpuArrayBufferIndex<MeshUniform>>,
Option<Read<SkinnedMeshJoints>>,
Option<Read<MorphIndex>>,
);
@ -1207,7 +1223,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
fn render<'w>(
_item: &P,
_view: (),
(mesh, mesh_index, skin_index, morph_index): ROQueryItem<Self::ItemWorldQuery>,
(mesh, batch_indices, skin_index, morph_index): ROQueryItem<Self::ItemWorldQuery>,
bind_groups: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
@ -1223,14 +1239,23 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
);
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<P: PhaseItem> RenderCommand<P> for DrawMesh {
type Param = SRes<RenderAssets<Mesh>>;
type ViewWorldQuery = ();
type ItemWorldQuery = Read<Handle<Mesh>>;
type ItemWorldQuery = (Read<GpuArrayBufferIndex<MeshUniform>>, Read<Handle<Mesh>>);
#[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<P: PhaseItem> RenderCommand<P> 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

View file

@ -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<f32>,
#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;
}

View file

@ -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::<MeshUniform>::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),

View file

@ -4,12 +4,22 @@
#ifdef MESH_BINDGROUP_1
#ifdef PER_OBJECT_BUFFER_BATCH_SIZE
@group(1) @binding(0)
var<uniform> mesh: Mesh;
var<uniform> mesh: array<Mesh, #{PER_OBJECT_BUFFER_BATCH_SIZE}u>;
#else
@group(1) @binding(0)
var<storage> mesh: array<Mesh>;
#endif // PER_OBJECT_BUFFER_BATCH_SIZE
#else // MESH_BINDGROUP_1
#ifdef PER_OBJECT_BUFFER_BATCH_SIZE
@group(2) @binding(0)
var<uniform> mesh: Mesh;
var<uniform> mesh: array<Mesh, #{PER_OBJECT_BUFFER_BATCH_SIZE}u>;
#else
@group(2) @binding(0)
var<storage> mesh: array<Mesh>;
#endif // PER_OBJECT_BUFFER_BATCH_SIZE
#endif
#endif // MESH_BINDGROUP_1

View file

@ -20,7 +20,7 @@ fn mesh_position_local_to_clip(model: mat4x4<f32>, vertex_position: vec4<f32>) -
return mesh_position_world_to_clip(world_position);
}
fn mesh_normal_local_to_world(vertex_normal: vec3<f32>) -> vec3<f32> {
fn mesh_normal_local_to_world(vertex_normal: vec3<f32>, instance_index: u32) -> vec3<f32> {
// 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<f32>) -> vec3<f32> {
// http://www.mikktspace.com/
return normalize(
mat3x3<f32>(
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<f32>, vertex_tangent: vec4<f32>) -> vec4<f32> {
fn mesh_tangent_local_to_world(model: mat4x4<f32>, vertex_tangent: vec4<f32>, instance_index: u32) -> vec4<f32> {
// 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<f32>, vertex_tangent: vec4<f32>) ->
),
// 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)
);
}

View file

@ -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<f32>,
@location(0) world_position: vec4<f32>,
@ -15,4 +15,7 @@ struct MeshVertexOutput {
#ifdef VERTEX_COLORS
@location(4) color: vec4<f32>,
#endif
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
@location(5) instance_index: u32,
#endif
}

View file

@ -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 {

View file

@ -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);
}

View file

@ -6,6 +6,7 @@
#endif
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
#ifdef SKINNED
@location(4) joint_indexes: vec4<u32>,
@ -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;

View file

@ -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<PipelineCache>,
msaa: Res<Msaa>,
mut material_meshes: ParamSet<(
Query<(Entity, &Handle<Mesh>, &MeshUniform)>,
Query<(Entity, &Handle<Mesh>, &MeshUniform), With<Wireframe>>,
Query<(
Entity,
&Handle<Mesh>,
&MeshUniform,
&GpuArrayBufferIndex<MeshUniform>,
)>,
Query<
(
Entity,
&Handle<Mesh>,
&MeshUniform,
&GpuArrayBufferIndex<MeshUniform>,
),
With<Wireframe>,
>,
)>,
mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>,
) {
@ -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<Mesh>, &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<Mesh>,
&MeshUniform,
&GpuArrayBufferIndex<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),
per_object_binding_dynamic_offset: batch_indices
.dynamic_offset
.unwrap_or_default(),
});
}
};
if wireframe_config.global {
let query = material_meshes.p0();

View file

@ -18,14 +18,18 @@ pub struct GpuComponentArrayBufferPlugin<C: Component + GpuArrayBufferable>(Phan
impl<C: Component + GpuArrayBufferable> Plugin for GpuComponentArrayBufferPlugin<C> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.insert_resource(GpuArrayBuffer::<C>::new(
render_app.world.resource::<RenderDevice>(),
))
.add_systems(
Render,
prepare_gpu_component_array_buffers::<C>.in_set(RenderSet::Prepare),
);
render_app.add_systems(
Render,
prepare_gpu_component_array_buffers::<C>.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::<C>::new(
render_app.world.resource::<RenderDevice>(),
));
}
}
}