mirror of
https://github.com/bevyengine/bevy
synced 2025-02-17 06:28:34 +00:00
Retain RenderMeshInstance
and MeshInputUniform
data from frame to
frame. This commit moves the front end of the rendering pipeline to a retained model when GPU preprocessing is in use (i.e. by default, except in constrained environments). `RenderMeshInstance` and `MeshUniformData` are stored from frame to frame and are updated only for the entities that changed state. This was rather tricky and requires some careful surgery to keep the data valid in the case of removals. This patch is built on top of Bevy's change detection. Generally, this worked, except that `ViewVisibility` isn't currently properly tracked. Therefore, this commit adds proper change tracking for `ViewVisibility`. Doing this required adding a new system that runs after all `check_visibility` invocations, as no single `check_visibility` invocation has enough global information to detect changes. On the Bistro exterior scene, with all textures forced to opaque, this patch improves steady-state `extract_meshes_for_gpu_building` from 93.8us to 34.5us and steady-state `collect_meshes_for_gpu_building` from 195.7us to 4.28us. Altogether this constitutes an improvement from 290us to 38us, which is a 7.46x speedup.
This commit is contained in:
parent
aab36f3951
commit
6935f967a8
5 changed files with 365 additions and 91 deletions
|
@ -483,5 +483,4 @@ pub fn write_mesh_culling_data_buffer(
|
|||
mut mesh_culling_data_buffer: ResMut<MeshCullingDataBuffer>,
|
||||
) {
|
||||
mesh_culling_data_buffer.write_buffer(&render_device, &render_queue);
|
||||
mesh_culling_data_buffer.clear();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use core::mem::{self, size_of};
|
||||
use core::mem::size_of;
|
||||
|
||||
use allocator::MeshAllocator;
|
||||
use bevy_asset::{load_internal_asset, AssetId};
|
||||
|
@ -42,8 +42,9 @@ use bevy_render::{
|
|||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bevy_utils::{
|
||||
hashbrown::hash_map::Entry,
|
||||
tracing::{error, warn},
|
||||
Entry, HashMap, Parallel,
|
||||
HashMap, Parallel,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -310,7 +311,7 @@ pub struct MeshUniform {
|
|||
/// the full [`MeshUniform`].
|
||||
///
|
||||
/// This is essentially a subset of the fields in [`MeshUniform`] above.
|
||||
#[derive(ShaderType, Pod, Zeroable, Clone, Copy)]
|
||||
#[derive(ShaderType, Pod, Zeroable, Clone, Copy, Default, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct MeshInputUniform {
|
||||
/// Affine 4x3 matrix transposed to 3x4.
|
||||
|
@ -340,18 +341,24 @@ pub struct MeshInputUniform {
|
|||
/// [`MeshAllocator`]). This value stores the offset of the first vertex in
|
||||
/// this mesh in that buffer.
|
||||
pub first_vertex_index: u32,
|
||||
/// The main world entity index (i.e. low 32 bits of raw entity ID).
|
||||
///
|
||||
/// The GPU doesn't look at this. It's only used to map the mesh input
|
||||
/// uniform data back to the entity during removal operations.
|
||||
pub main_entity_lo: u32,
|
||||
/// The main world entity generation (i.e. high 32 bits of raw entity ID).
|
||||
///
|
||||
/// The GPU doesn't look at this. It's only used to map the mesh input
|
||||
/// uniform data back to the entity during removal operations.
|
||||
pub main_entity_hi: u32,
|
||||
/// Padding.
|
||||
pub pad_a: u32,
|
||||
/// Padding.
|
||||
pub pad_b: u32,
|
||||
/// Padding.
|
||||
pub pad_c: u32,
|
||||
}
|
||||
|
||||
/// Information about each mesh instance needed to cull it on GPU.
|
||||
///
|
||||
/// This consists of its axis-aligned bounding box (AABB).
|
||||
#[derive(ShaderType, Pod, Zeroable, Clone, Copy)]
|
||||
#[derive(ShaderType, Pod, Zeroable, Clone, Copy, Default)]
|
||||
#[repr(C)]
|
||||
pub struct MeshCullingData {
|
||||
/// The 3D center of the AABB in model space, padded with an extra unused
|
||||
|
@ -557,11 +564,25 @@ pub enum RenderMeshInstanceGpuQueue {
|
|||
/// The version of [`RenderMeshInstanceGpuQueue`] that omits the
|
||||
/// [`MeshCullingData`], so that we don't waste space when GPU
|
||||
/// culling is disabled.
|
||||
CpuCulling(Vec<(MainEntity, RenderMeshInstanceGpuBuilder)>),
|
||||
CpuCulling {
|
||||
/// Stores GPU data for each entity that became visible or changed in
|
||||
/// such a way that necessitates updating the [`MeshInputUniform`] (e.g.
|
||||
/// changed transform).
|
||||
changed: Vec<(MainEntity, RenderMeshInstanceGpuBuilder)>,
|
||||
/// Stores the IDs of entities that became invisible this frame.
|
||||
removed: Vec<MainEntity>,
|
||||
},
|
||||
/// The version of [`RenderMeshInstanceGpuQueue`] that contains the
|
||||
/// [`MeshCullingData`], used when any view has GPU culling
|
||||
/// enabled.
|
||||
GpuCulling(Vec<(MainEntity, RenderMeshInstanceGpuBuilder, MeshCullingData)>),
|
||||
GpuCulling {
|
||||
/// Stores GPU data for each entity that became visible or changed in
|
||||
/// such a way that necessitates updating the [`MeshInputUniform`] (e.g.
|
||||
/// changed transform).
|
||||
changed: Vec<(MainEntity, RenderMeshInstanceGpuBuilder, MeshCullingData)>,
|
||||
/// Stores the IDs of entities that became invisible this frame.
|
||||
removed: Vec<MainEntity>,
|
||||
},
|
||||
}
|
||||
|
||||
/// The per-thread queues containing mesh instances, populated during the
|
||||
|
@ -630,6 +651,21 @@ pub struct RenderMeshInstancesCpu(MainEntityHashMap<RenderMeshInstanceCpu>);
|
|||
#[derive(Default, Deref, DerefMut)]
|
||||
pub struct RenderMeshInstancesGpu(MainEntityHashMap<RenderMeshInstanceGpu>);
|
||||
|
||||
/// The result of an entity removal.
|
||||
///
|
||||
/// Whenever entity data is removed from the [`MeshInputUniform`] buffer, the
|
||||
/// last entity is swapped in to fill it. If culling is enabled, the mesh
|
||||
/// culling data corresponding to an entity must have the same indices as the
|
||||
/// mesh input uniforms for that entity. Thus we need to pass this information
|
||||
/// to [`MeshCullingData::remove`] so that it can update its buffer to match the
|
||||
/// [`MeshInputUniform`] buffer.
|
||||
struct RemovedMeshInputUniformIndices {
|
||||
/// The index of the mesh input that was removed.
|
||||
removed_index: usize,
|
||||
/// The index of the mesh input that was moved to fill its place.
|
||||
moved_index: usize,
|
||||
}
|
||||
|
||||
impl RenderMeshInstances {
|
||||
/// Creates a new [`RenderMeshInstances`] instance.
|
||||
fn new(use_gpu_instance_buffer_builder: bool) -> RenderMeshInstances {
|
||||
|
@ -729,10 +765,26 @@ impl RenderMeshInstanceGpuQueue {
|
|||
/// enabled.
|
||||
fn init(&mut self, any_gpu_culling: bool) {
|
||||
match (any_gpu_culling, &mut *self) {
|
||||
(true, RenderMeshInstanceGpuQueue::GpuCulling(queue)) => queue.clear(),
|
||||
(true, _) => *self = RenderMeshInstanceGpuQueue::GpuCulling(vec![]),
|
||||
(false, RenderMeshInstanceGpuQueue::CpuCulling(queue)) => queue.clear(),
|
||||
(false, _) => *self = RenderMeshInstanceGpuQueue::CpuCulling(vec![]),
|
||||
(true, RenderMeshInstanceGpuQueue::GpuCulling { changed, removed }) => {
|
||||
changed.clear();
|
||||
removed.clear();
|
||||
}
|
||||
(true, _) => {
|
||||
*self = RenderMeshInstanceGpuQueue::GpuCulling {
|
||||
changed: vec![],
|
||||
removed: vec![],
|
||||
}
|
||||
}
|
||||
(false, RenderMeshInstanceGpuQueue::CpuCulling { changed, removed }) => {
|
||||
changed.clear();
|
||||
removed.clear();
|
||||
}
|
||||
(false, _) => {
|
||||
*self = RenderMeshInstanceGpuQueue::CpuCulling {
|
||||
changed: vec![],
|
||||
removed: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -744,24 +796,56 @@ impl RenderMeshInstanceGpuQueue {
|
|||
culling_data_builder: Option<MeshCullingData>,
|
||||
) {
|
||||
match (&mut *self, culling_data_builder) {
|
||||
(&mut RenderMeshInstanceGpuQueue::CpuCulling(ref mut queue), None) => {
|
||||
(
|
||||
&mut RenderMeshInstanceGpuQueue::CpuCulling {
|
||||
changed: ref mut queue,
|
||||
..
|
||||
},
|
||||
None,
|
||||
) => {
|
||||
queue.push((entity, instance_builder));
|
||||
}
|
||||
(
|
||||
&mut RenderMeshInstanceGpuQueue::GpuCulling(ref mut queue),
|
||||
&mut RenderMeshInstanceGpuQueue::GpuCulling {
|
||||
changed: ref mut queue,
|
||||
..
|
||||
},
|
||||
Some(culling_data_builder),
|
||||
) => {
|
||||
queue.push((entity, instance_builder, culling_data_builder));
|
||||
}
|
||||
(_, None) => {
|
||||
*self = RenderMeshInstanceGpuQueue::CpuCulling(vec![(entity, instance_builder)]);
|
||||
*self = RenderMeshInstanceGpuQueue::CpuCulling {
|
||||
changed: vec![(entity, instance_builder)],
|
||||
removed: vec![],
|
||||
};
|
||||
}
|
||||
(_, Some(culling_data_builder)) => {
|
||||
*self = RenderMeshInstanceGpuQueue::GpuCulling(vec![(
|
||||
entity,
|
||||
instance_builder,
|
||||
culling_data_builder,
|
||||
)]);
|
||||
*self = RenderMeshInstanceGpuQueue::GpuCulling {
|
||||
changed: vec![(entity, instance_builder, culling_data_builder)],
|
||||
removed: vec![],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, entity: MainEntity, gpu_culling: bool) {
|
||||
match (&mut *self, gpu_culling) {
|
||||
(RenderMeshInstanceGpuQueue::None, false) => {
|
||||
*self = RenderMeshInstanceGpuQueue::CpuCulling {
|
||||
changed: vec![],
|
||||
removed: vec![entity],
|
||||
}
|
||||
}
|
||||
(RenderMeshInstanceGpuQueue::None, true) => {
|
||||
*self = RenderMeshInstanceGpuQueue::GpuCulling {
|
||||
changed: vec![],
|
||||
removed: vec![entity],
|
||||
}
|
||||
}
|
||||
(RenderMeshInstanceGpuQueue::CpuCulling { removed, .. }, _)
|
||||
| (RenderMeshInstanceGpuQueue::GpuCulling { removed, .. }, _) => {
|
||||
removed.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -769,12 +853,13 @@ impl RenderMeshInstanceGpuQueue {
|
|||
|
||||
impl RenderMeshInstanceGpuBuilder {
|
||||
/// Flushes this mesh instance to the [`RenderMeshInstanceGpu`] and
|
||||
/// [`MeshInputUniform`] tables.
|
||||
fn add_to(
|
||||
/// [`MeshInputUniform`] tables, replacing the existing entry if applicable.
|
||||
fn update(
|
||||
self,
|
||||
entity: MainEntity,
|
||||
render_mesh_instances: &mut MainEntityHashMap<RenderMeshInstanceGpu>,
|
||||
current_input_buffer: &mut RawBufferVec<MeshInputUniform>,
|
||||
previous_input_buffer: &mut RawBufferVec<MeshInputUniform>,
|
||||
mesh_allocator: &MeshAllocator,
|
||||
) -> usize {
|
||||
let first_vertex_index = match mesh_allocator.mesh_vertex_slice(&self.shared.mesh_asset_id)
|
||||
|
@ -783,37 +868,101 @@ impl RenderMeshInstanceGpuBuilder {
|
|||
None => 0,
|
||||
};
|
||||
|
||||
// Push the mesh input uniform.
|
||||
let current_uniform_index = current_input_buffer.push(MeshInputUniform {
|
||||
let main_entity_bits = entity.to_bits();
|
||||
|
||||
// Create the mesh input uniform.
|
||||
let mut mesh_input_uniform = MeshInputUniform {
|
||||
world_from_local: self.world_from_local.to_transpose(),
|
||||
lightmap_uv_rect: self.lightmap_uv_rect,
|
||||
flags: self.mesh_flags.bits(),
|
||||
previous_input_index: match self.previous_input_index {
|
||||
Some(previous_input_index) => previous_input_index.into(),
|
||||
None => u32::MAX,
|
||||
},
|
||||
previous_input_index: u32::MAX,
|
||||
first_vertex_index,
|
||||
main_entity_lo: main_entity_bits as u32,
|
||||
main_entity_hi: (main_entity_bits >> 32) as u32,
|
||||
pad_a: 0,
|
||||
pad_b: 0,
|
||||
pad_c: 0,
|
||||
});
|
||||
};
|
||||
|
||||
// Record the [`RenderMeshInstance`].
|
||||
render_mesh_instances.insert(
|
||||
entity,
|
||||
RenderMeshInstanceGpu {
|
||||
translation: self.world_from_local.translation,
|
||||
shared: self.shared,
|
||||
current_uniform_index: (current_uniform_index as u32)
|
||||
.try_into()
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
);
|
||||
// Did the last frame contain this entity as well?
|
||||
let current_uniform_index;
|
||||
match render_mesh_instances.entry(entity) {
|
||||
Entry::Occupied(mut occupied_entry) => {
|
||||
// Yes, it did. Replace its entry with the new one.
|
||||
|
||||
// Reserve a slot.
|
||||
current_uniform_index =
|
||||
u32::from(occupied_entry.get_mut().current_uniform_index) as usize;
|
||||
|
||||
// Save the old mesh input uniform. The mesh preprocessing
|
||||
// shader will need it to compute motion vectors.
|
||||
let previous_mesh_input_uniform =
|
||||
current_input_buffer.values()[current_uniform_index];
|
||||
let previous_input_index = previous_input_buffer.push(previous_mesh_input_uniform);
|
||||
mesh_input_uniform.previous_input_index = previous_input_index as u32;
|
||||
|
||||
// Write in the new mesh input uniform.
|
||||
current_input_buffer.values_mut()[current_uniform_index] = mesh_input_uniform;
|
||||
|
||||
occupied_entry.replace_entry(RenderMeshInstanceGpu {
|
||||
translation: self.world_from_local.translation,
|
||||
shared: self.shared,
|
||||
current_uniform_index: NonMaxU32::new(current_uniform_index as u32)
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
|
||||
Entry::Vacant(vacant_entry) => {
|
||||
// No, this is a new entity. Push its data on to the buffer.
|
||||
current_uniform_index = current_input_buffer.push(mesh_input_uniform);
|
||||
vacant_entry.insert(RenderMeshInstanceGpu {
|
||||
translation: self.world_from_local.translation,
|
||||
shared: self.shared,
|
||||
current_uniform_index: NonMaxU32::new(current_uniform_index as u32)
|
||||
.unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
current_uniform_index
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a [`MeshInputUniform`] corresponding to an entity that became
|
||||
/// invisible from the buffer.
|
||||
fn remove_mesh_input_uniform(
|
||||
entity: MainEntity,
|
||||
render_mesh_instances: &mut MainEntityHashMap<RenderMeshInstanceGpu>,
|
||||
current_input_buffer: &mut RawBufferVec<MeshInputUniform>,
|
||||
) -> Option<RemovedMeshInputUniformIndices> {
|
||||
// Remove the uniform data.
|
||||
let removed_render_mesh_instance = render_mesh_instances.remove(&entity)?;
|
||||
let removed_uniform_index = removed_render_mesh_instance.current_uniform_index.get() as usize;
|
||||
|
||||
// Now *move* the final mesh uniform to fill its place in the buffer, so
|
||||
// that the buffer remains contiguous.
|
||||
let moved_uniform_index = current_input_buffer.len() - 1;
|
||||
if moved_uniform_index != removed_uniform_index {
|
||||
let moved_uniform = current_input_buffer.pop().unwrap();
|
||||
current_input_buffer.values_mut()[removed_uniform_index] = moved_uniform;
|
||||
|
||||
let moved_entity: MainEntity = Entity::from_bits(
|
||||
moved_uniform.main_entity_lo as u64 | ((moved_uniform.main_entity_hi as u64) << 32),
|
||||
)
|
||||
.into();
|
||||
if let Some(moved_render_mesh_instance) = render_mesh_instances.get_mut(&moved_entity) {
|
||||
moved_render_mesh_instance.current_uniform_index =
|
||||
NonMaxU32::new(removed_uniform_index as u32).unwrap_or_default();
|
||||
}
|
||||
} else {
|
||||
current_input_buffer.pop();
|
||||
}
|
||||
|
||||
// Tell our caller what we did.
|
||||
Some(RemovedMeshInputUniformIndices {
|
||||
removed_index: removed_uniform_index,
|
||||
moved_index: moved_uniform_index,
|
||||
})
|
||||
}
|
||||
|
||||
impl MeshCullingData {
|
||||
/// Returns a new [`MeshCullingData`] initialized with the given AABB.
|
||||
///
|
||||
|
@ -833,9 +982,16 @@ impl MeshCullingData {
|
|||
}
|
||||
|
||||
/// Flushes this mesh instance culling data to the
|
||||
/// [`MeshCullingDataBuffer`].
|
||||
fn add_to(&self, mesh_culling_data_buffer: &mut MeshCullingDataBuffer) -> usize {
|
||||
mesh_culling_data_buffer.push(*self)
|
||||
/// [`MeshCullingDataBuffer`], replacing the existing entry if applicable.
|
||||
fn update(
|
||||
&self,
|
||||
mesh_culling_data_buffer: &mut MeshCullingDataBuffer,
|
||||
instance_data_index: usize,
|
||||
) {
|
||||
while mesh_culling_data_buffer.len() < instance_data_index + 1 {
|
||||
mesh_culling_data_buffer.push(MeshCullingData::default());
|
||||
}
|
||||
mesh_culling_data_buffer.values_mut()[instance_data_index] = *self;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -846,6 +1002,17 @@ impl Default for MeshCullingDataBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
impl MeshCullingDataBuffer {
|
||||
fn remove(&mut self, removed_indices: RemovedMeshInputUniformIndices) {
|
||||
if removed_indices.moved_index != removed_indices.removed_index {
|
||||
let moved_culling_data = self.pop().unwrap();
|
||||
self.values_mut()[removed_indices.removed_index] = moved_culling_data;
|
||||
} else {
|
||||
self.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data that [`crate::material::queue_material_meshes`] and similar systems
|
||||
/// need in order to place entities that contain meshes in the right batch.
|
||||
#[derive(Deref)]
|
||||
|
@ -962,28 +1129,50 @@ pub fn extract_meshes_for_cpu_building(
|
|||
/// Extracts meshes from the main world into the render world and queues
|
||||
/// [`MeshInputUniform`]s to be uploaded to the GPU.
|
||||
///
|
||||
/// This is optimized to only look at entities that have changed since the last
|
||||
/// frame.
|
||||
///
|
||||
/// This is the variant of the system that runs when we're using GPU
|
||||
/// [`MeshUniform`] building.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn extract_meshes_for_gpu_building(
|
||||
mut render_mesh_instances: ResMut<RenderMeshInstances>,
|
||||
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
||||
mut render_mesh_instance_queues: ResMut<RenderMeshInstanceGpuQueues>,
|
||||
meshes_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&ViewVisibility,
|
||||
&GlobalTransform,
|
||||
Option<&PreviousGlobalTransform>,
|
||||
Option<&Lightmap>,
|
||||
Option<&Aabb>,
|
||||
&Mesh3d,
|
||||
Has<NotShadowReceiver>,
|
||||
Has<TransmittedShadowReceiver>,
|
||||
Has<NotShadowCaster>,
|
||||
Has<NoAutomaticBatching>,
|
||||
Has<VisibilityRange>,
|
||||
)>,
|
||||
changed_meshes_query: Extract<
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&ViewVisibility,
|
||||
&GlobalTransform,
|
||||
Option<&PreviousGlobalTransform>,
|
||||
Option<&Lightmap>,
|
||||
Option<&Aabb>,
|
||||
&Mesh3d,
|
||||
Has<NotShadowReceiver>,
|
||||
Has<TransmittedShadowReceiver>,
|
||||
Has<NotShadowCaster>,
|
||||
Has<NoAutomaticBatching>,
|
||||
Has<VisibilityRange>,
|
||||
),
|
||||
Or<(
|
||||
Changed<ViewVisibility>,
|
||||
Changed<GlobalTransform>,
|
||||
Changed<PreviousGlobalTransform>,
|
||||
Changed<Lightmap>,
|
||||
Changed<Aabb>,
|
||||
Changed<Mesh3d>,
|
||||
Changed<NotShadowReceiver>,
|
||||
Changed<TransmittedShadowReceiver>,
|
||||
Changed<NotShadowCaster>,
|
||||
Changed<NoAutomaticBatching>,
|
||||
Changed<VisibilityRange>,
|
||||
)>,
|
||||
>,
|
||||
>,
|
||||
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
|
||||
mut removed_global_transforms_query: Extract<RemovedComponents<GlobalTransform>>,
|
||||
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
|
||||
cameras_query: Extract<Query<(), (With<Camera>, With<GpuCulling>)>>,
|
||||
) {
|
||||
let any_gpu_culling = !cameras_query.is_empty();
|
||||
|
@ -1000,7 +1189,9 @@ pub fn extract_meshes_for_gpu_building(
|
|||
);
|
||||
};
|
||||
|
||||
meshes_query.par_iter().for_each_init(
|
||||
// Find all meshes that have changed, and record information needed to
|
||||
// construct the `MeshInputUniform` for them.
|
||||
changed_meshes_query.par_iter().for_each_init(
|
||||
|| render_mesh_instance_queues.borrow_local_mut(),
|
||||
|queue,
|
||||
(
|
||||
|
@ -1018,6 +1209,7 @@ pub fn extract_meshes_for_gpu_building(
|
|||
visibility_range,
|
||||
)| {
|
||||
if !view_visibility.get() {
|
||||
queue.remove(entity.into(), any_gpu_culling);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1070,6 +1262,16 @@ pub fn extract_meshes_for_gpu_building(
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Also record info about each mesh that became invisible.
|
||||
let mut queue = render_mesh_instance_queues.borrow_local_mut();
|
||||
for entity in removed_visibilities_query
|
||||
.read()
|
||||
.chain(removed_global_transforms_query.read())
|
||||
.chain(removed_meshes_query.read())
|
||||
{
|
||||
queue.remove(entity.into(), any_gpu_culling);
|
||||
}
|
||||
}
|
||||
|
||||
/// A system that sets the [`RenderMeshInstanceFlags`] for each mesh based on
|
||||
|
@ -1119,49 +1321,77 @@ pub fn collect_meshes_for_gpu_building(
|
|||
};
|
||||
|
||||
// Collect render mesh instances. Build up the uniform buffer.
|
||||
|
||||
let gpu_preprocessing::BatchedInstanceBuffers {
|
||||
ref mut current_input_buffer,
|
||||
ref mut previous_input_buffer,
|
||||
..
|
||||
} = batched_instance_buffers.into_inner();
|
||||
|
||||
// Swap buffers.
|
||||
mem::swap(current_input_buffer, previous_input_buffer);
|
||||
previous_input_buffer.clear();
|
||||
|
||||
// Build the [`RenderMeshInstance`]s and [`MeshInputUniform`]s.
|
||||
render_mesh_instances.clear();
|
||||
|
||||
for queue in render_mesh_instance_queues.iter_mut() {
|
||||
match *queue {
|
||||
RenderMeshInstanceGpuQueue::None => {
|
||||
// This can only happen if the queue is empty.
|
||||
}
|
||||
RenderMeshInstanceGpuQueue::CpuCulling(ref mut queue) => {
|
||||
for (entity, mesh_instance_builder) in queue.drain(..) {
|
||||
mesh_instance_builder.add_to(
|
||||
|
||||
RenderMeshInstanceGpuQueue::CpuCulling {
|
||||
ref mut changed,
|
||||
ref mut removed,
|
||||
} => {
|
||||
for (entity, mesh_instance_builder) in changed.drain(..) {
|
||||
mesh_instance_builder.update(
|
||||
entity,
|
||||
&mut *render_mesh_instances,
|
||||
current_input_buffer,
|
||||
previous_input_buffer,
|
||||
&mesh_allocator,
|
||||
);
|
||||
}
|
||||
}
|
||||
RenderMeshInstanceGpuQueue::GpuCulling(ref mut queue) => {
|
||||
for (entity, mesh_instance_builder, mesh_culling_builder) in queue.drain(..) {
|
||||
let instance_data_index = mesh_instance_builder.add_to(
|
||||
|
||||
for entity in removed.drain(..) {
|
||||
remove_mesh_input_uniform(
|
||||
entity,
|
||||
&mut *render_mesh_instances,
|
||||
current_input_buffer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RenderMeshInstanceGpuQueue::GpuCulling {
|
||||
ref mut changed,
|
||||
ref mut removed,
|
||||
} => {
|
||||
for (entity, mesh_instance_builder, mesh_culling_builder) in changed.drain(..) {
|
||||
let instance_data_index = mesh_instance_builder.update(
|
||||
entity,
|
||||
&mut *render_mesh_instances,
|
||||
current_input_buffer,
|
||||
previous_input_buffer,
|
||||
&mesh_allocator,
|
||||
);
|
||||
let culling_data_index =
|
||||
mesh_culling_builder.add_to(&mut mesh_culling_data_buffer);
|
||||
debug_assert_eq!(instance_data_index, culling_data_index);
|
||||
mesh_culling_builder.update(&mut mesh_culling_data_buffer, instance_data_index);
|
||||
}
|
||||
|
||||
for entity in removed.drain(..) {
|
||||
if let Some(removed_indices) = remove_mesh_input_uniform(
|
||||
entity,
|
||||
&mut *render_mesh_instances,
|
||||
current_input_buffer,
|
||||
) {
|
||||
mesh_culling_data_buffer.remove(removed_indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buffers can't be empty. Make sure there's something in the previous input buffer.
|
||||
if previous_input_buffer.is_empty() {
|
||||
previous_input_buffer.push(MeshInputUniform::default());
|
||||
}
|
||||
}
|
||||
|
||||
/// All data needed to construct a pipeline for rendering 3D meshes.
|
||||
|
|
|
@ -292,8 +292,6 @@ where
|
|||
/// Clears out the buffers in preparation for a new frame.
|
||||
pub fn clear(&mut self) {
|
||||
self.data_buffer.clear();
|
||||
self.current_input_buffer.clear();
|
||||
self.previous_input_buffer.clear();
|
||||
for work_item_buffer in self.work_item_buffers.values_mut() {
|
||||
work_item_buffer.buffer.clear();
|
||||
}
|
||||
|
@ -671,13 +669,12 @@ pub fn write_batched_instance_buffers<GFBD>(
|
|||
ref mut data_buffer,
|
||||
work_item_buffers: ref mut index_buffers,
|
||||
ref mut current_input_buffer,
|
||||
previous_input_buffer: _,
|
||||
ref mut previous_input_buffer,
|
||||
} = gpu_array_buffer.into_inner();
|
||||
|
||||
data_buffer.write_buffer(&render_device);
|
||||
current_input_buffer.write_buffer(&render_device, &render_queue);
|
||||
// There's no need to write `previous_input_buffer`, as we wrote
|
||||
// that on the previous frame, and it hasn't changed.
|
||||
previous_input_buffer.write_buffer(&render_device, &render_queue);
|
||||
|
||||
for index_buffer in index_buffers.values_mut() {
|
||||
index_buffer
|
||||
|
|
|
@ -174,6 +174,11 @@ impl<T: NoUninit> RawBufferVec<T> {
|
|||
self.values.clear();
|
||||
}
|
||||
|
||||
/// Removes and returns the last element in the buffer.
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
self.values.pop()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> &Vec<T> {
|
||||
&self.values
|
||||
}
|
||||
|
|
|
@ -5,13 +5,14 @@ mod render_layers;
|
|||
|
||||
use core::any::TypeId;
|
||||
|
||||
use bevy_ecs::entity::EntityHashSet;
|
||||
pub use range::*;
|
||||
pub use render_layers::*;
|
||||
|
||||
use bevy_app::{Plugin, PostUpdate};
|
||||
use bevy_asset::Assets;
|
||||
use bevy_derive::Deref;
|
||||
use bevy_ecs::{prelude::*, query::QueryFilter};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{change_detection::DetectChangesMut as _, prelude::*, query::QueryFilter};
|
||||
use bevy_hierarchy::{Children, Parent};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_transform::{components::GlobalTransform, TransformSystem};
|
||||
|
@ -331,6 +332,10 @@ pub enum VisibilitySystems {
|
|||
/// the order of systems within this set is irrelevant, as [`check_visibility`]
|
||||
/// assumes that its operations are irreversible during the frame.
|
||||
CheckVisibility,
|
||||
/// Label for the system that runs after visibility checking and marks
|
||||
/// entities that have gone from visible to invisible, or vice versa, as
|
||||
/// changed.
|
||||
VisibilityChangeDetect,
|
||||
}
|
||||
|
||||
pub struct VisibilityPlugin;
|
||||
|
@ -346,12 +351,14 @@ impl Plugin for VisibilityPlugin {
|
|||
.after(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.configure_sets(PostUpdate, CheckVisibility.ambiguous_with(CheckVisibility))
|
||||
.init_resource::<PreviousVisibleEntities>()
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
calculate_bounds.in_set(CalculateBounds),
|
||||
(visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate),
|
||||
check_visibility::<With<Mesh3d>>.in_set(CheckVisibility),
|
||||
mark_view_visibility_as_changed_if_necessary.in_set(VisibilityChangeDetect),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -456,14 +463,28 @@ fn propagate_recursive(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores all entities that were visible in the previous frame.
|
||||
///
|
||||
/// At the end of visibility checking, we compare the visible entities against
|
||||
/// these and set the [`ViewVisibility`] change flags accordingly.
|
||||
#[derive(Resource, Default, Deref, DerefMut)]
|
||||
pub struct PreviousVisibleEntities(EntityHashSet);
|
||||
|
||||
/// Resets the view visibility of every entity.
|
||||
/// Entities that are visible will be marked as such later this frame
|
||||
/// by a [`VisibilitySystems::CheckVisibility`] system.
|
||||
fn reset_view_visibility(mut query: Query<&mut ViewVisibility>) {
|
||||
query.iter_mut().for_each(|mut view_visibility| {
|
||||
// NOTE: We do not use `set_if_neq` here, as we don't care about
|
||||
// change detection for view visibility, and adding a branch to every
|
||||
// loop iteration would pessimize performance.
|
||||
fn reset_view_visibility(
|
||||
mut query: Query<(Entity, &mut ViewVisibility)>,
|
||||
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
|
||||
) {
|
||||
previous_visible_entities.clear();
|
||||
|
||||
query.iter_mut().for_each(|(entity, mut view_visibility)| {
|
||||
// Record the entities that were previously visible.
|
||||
if view_visibility.get() {
|
||||
previous_visible_entities.insert(entity);
|
||||
}
|
||||
|
||||
*view_visibility.bypass_change_detection() = ViewVisibility::HIDDEN;
|
||||
});
|
||||
}
|
||||
|
@ -569,7 +590,10 @@ pub fn check_visibility<QF>(
|
|||
}
|
||||
}
|
||||
|
||||
view_visibility.set();
|
||||
// Make sure we don't trigger changed notifications
|
||||
// unnecessarily.
|
||||
view_visibility.bypass_change_detection().set();
|
||||
|
||||
queue.push(entity);
|
||||
},
|
||||
);
|
||||
|
@ -579,6 +603,25 @@ pub fn check_visibility<QF>(
|
|||
}
|
||||
}
|
||||
|
||||
/// A system that marks [`ViewVisibility`] components as changed if their
|
||||
/// visibility changed this frame.
|
||||
///
|
||||
/// Ordinary change detection doesn't work for this use case because we use
|
||||
/// multiple [`check_visibility`] systems, and visibility is set if *any one* of
|
||||
/// those systems judges the entity to be visible. Thus, in order to perform
|
||||
/// change detection, we need this system, which is a follow-up pass that has a
|
||||
/// global view of whether the entity became visible or not.
|
||||
fn mark_view_visibility_as_changed_if_necessary(
|
||||
mut query: Query<(Entity, &mut ViewVisibility)>,
|
||||
previous_visible_entities: Res<PreviousVisibleEntities>,
|
||||
) {
|
||||
for (entity, mut view_visibility) in query.iter_mut() {
|
||||
if previous_visible_entities.contains(&entity) != view_visibility.get() {
|
||||
view_visibility.set_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
Loading…
Add table
Reference in a new issue