Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use GpuArrayBuffer for MeshUniform #9254

Merged
merged 15 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions crates/bevy_core_pipeline/src/core_3d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not for this pr but just wondering, is there any possibility for / value in some kind of spatial sorting before putting the mesh data in the gpu array? i think currently they are entity iterator sorted, but it seems like (at least for static meshes) there'd be some benefit to having a batch be as proximally local as possible and then to sort batches based on the nearest member, or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will happen when the render set reordering and batching is implemented. The reason for reordering the render sets to extract, prepare assets, queue, sort, prepare+batch, render is to allow the order of draws to be known when preparing the data so that the order can be taken into account.


#[inline]
fn entity(&self) -> Entity {
Expand All @@ -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]
Expand All @@ -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)
});
}
}

Expand All @@ -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,
superdump marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand All @@ -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]
Expand All @@ -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)
});
}
}

Expand Down
32 changes: 25 additions & 7 deletions crates/bevy_core_pipeline/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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]
Expand All @@ -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)
});
}
}

Expand All @@ -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 {
Expand All @@ -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]
Expand All @@ -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)
});
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_pbr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
21 changes: 16 additions & 5 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)) = (
Expand Down Expand Up @@ -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(_) => {
Expand All @@ -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
Expand Down
25 changes: 15 additions & 10 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ use bevy_render::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType,
ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState,
DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache,
PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderDefVal, ShaderRef,
DynamicUniformBuffer, FragmentState, FrontFace, GpuArrayBufferIndex, MultisampleState,
PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, Shader, ShaderRef,
ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError,
SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType,
TextureViewDimension, VertexState,
Expand Down Expand Up @@ -226,7 +226,6 @@ pub struct PrepassPipeline<M: Material> {
pub view_layout_motion_vectors: BindGroupLayout,
pub view_layout_no_motion_vectors: BindGroupLayout,
pub mesh_layouts: MeshLayouts,
pub mesh_buffer_batch_size: Option<u32>,
pub material_layout: BindGroupLayout,
pub material_vertex_shader: Option<Handle<Shader>>,
pub material_fragment_shader: Option<Handle<Shader>>,
Expand Down Expand Up @@ -314,7 +313,6 @@ impl<M: Material> FromWorld for PrepassPipeline<M> {
view_layout_motion_vectors,
view_layout_no_motion_vectors,
mesh_layouts: mesh_pipeline.mesh_layouts.clone(),
mesh_buffer_batch_size: mesh_pipeline.mesh_buffer_batch_size,
material_vertex_shader: match M::prepass_vertex_shader() {
ShaderRef::Default => None,
ShaderRef::Handle(handle) => Some(handle),
Expand Down Expand Up @@ -354,10 +352,6 @@ where
let mut shader_defs = Vec::new();
let mut vertex_attributes = Vec::new();

if let Some(batch_size) = self.mesh_buffer_batch_size {
shader_defs.push(ShaderDefVal::UInt("MESH_BATCH_SIZE".into(), batch_size));
}

// NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material.
// The main limitation right now is that bind group order is hardcoded in shaders.
bind_group_layouts.insert(1, self.material_layout.clone());
Expand Down Expand Up @@ -757,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,
Expand Down Expand Up @@ -802,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;
};

Expand Down Expand Up @@ -854,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(_) => {
Expand All @@ -862,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
Expand Down
27 changes: 19 additions & 8 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>>>,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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(),
});
}
}
Expand All @@ -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 {
Expand All @@ -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]
Expand All @@ -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());
}
}

Expand Down
Loading
Loading