Renderer Optimization Round 1 (#958)

* only update global transforms when they (or their ancestors) have changed

* only update render resource nodes when they have changed (quality check plz)

* only update entity mesh specialization when mesh (or mesh component) has changed

* only update sprite size when changed

* remove stale bind groups

* fix setting size of loading sprites

* store unmatched render resource binding results

* reduce state changes

* cargo fmt + clippy

* remove cached "NoMatch" results when new bindings are added to RenderResourceBindings

* inline current_entity in world_builder

* try creating bind groups even when they havent changed

* render_resources_node: update all entities when resized

* fmt
This commit is contained in:
Carter Anderson 2020-12-01 13:17:48 -08:00 committed by GitHub
parent 3cee95e59a
commit b5ffab7135
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 407 additions and 166 deletions

View File

@ -62,4 +62,17 @@ impl<'a> WorldBuilder<'a> {
self.current_entity = Some(self.world.spawn(components));
self
}
#[inline]
pub fn current_entity(&self) -> Option<Entity> {
self.current_entity
}
pub fn for_current_entity(&mut self, f: impl FnOnce(Entity)) -> &mut Self {
let current_entity = self
.current_entity
.expect("The 'current entity' is not set. You should spawn an entity first.");
f(current_entity);
self
}
}

View File

@ -5,13 +5,13 @@ use crate::{
use bevy_app::prelude::{EventReader, Events};
use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_core::AsBytes;
use bevy_ecs::{Local, Query, Res};
use bevy_ecs::{Changed, Entity, Local, Mut, Query, QuerySet, Res, With};
use bevy_math::*;
use bevy_reflect::TypeUuid;
use std::borrow::Cow;
use crate::pipeline::{InputStepMode, VertexAttributeDescriptor, VertexBufferDescriptor};
use bevy_utils::HashMap;
use bevy_utils::{HashMap, HashSet};
pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0;
pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10;
@ -320,9 +320,16 @@ fn remove_current_mesh_resources(
remove_resource_save(render_resource_context, handle, INDEX_BUFFER_ASSET_INDEX);
}
#[derive(Default)]
pub struct MeshEntities {
entities: HashSet<Entity>,
waiting: HashSet<Entity>,
}
#[derive(Default)]
pub struct MeshResourceProviderState {
mesh_event_reader: EventReader<AssetEvent<Mesh>>,
mesh_entities: HashMap<Handle<Mesh>, MeshEntities>,
}
pub fn mesh_resource_provider_system(
@ -330,7 +337,10 @@ pub fn mesh_resource_provider_system(
render_resource_context: Res<Box<dyn RenderResourceContext>>,
meshes: Res<Assets<Mesh>>,
mesh_events: Res<Events<AssetEvent<Mesh>>>,
mut query: Query<(&Handle<Mesh>, &mut RenderPipelines)>,
mut queries: QuerySet<(
Query<&mut RenderPipelines, With<Handle<Mesh>>>,
Query<(Entity, &Handle<Mesh>, &mut RenderPipelines), Changed<Handle<Mesh>>>,
)>,
) {
let mut changed_meshes = bevy_utils::HashSet::<Handle<Mesh>>::default();
let render_resource_context = &**render_resource_context;
@ -383,39 +393,69 @@ pub fn mesh_resource_provider_system(
)),
VERTEX_ATTRIBUTE_BUFFER_ID,
);
if let Some(mesh_entities) = state.mesh_entities.get_mut(changed_mesh_handle) {
for entity in mesh_entities.waiting.drain() {
if let Ok(render_pipelines) = queries.q0_mut().get_mut(entity) {
mesh_entities.entities.insert(entity);
update_entity_mesh(
render_resource_context,
mesh,
changed_mesh_handle,
render_pipelines,
);
}
}
}
}
}
// handover buffers to pipeline
for (handle, mut render_pipelines) in query.iter_mut() {
for (entity, handle, render_pipelines) in queries.q1_mut().iter_mut() {
let mesh_entities = state
.mesh_entities
.entry(handle.clone_weak())
.or_insert_with(MeshEntities::default);
if let Some(mesh) = meshes.get(handle) {
for render_pipeline in render_pipelines.pipelines.iter_mut() {
render_pipeline.specialization.primitive_topology = mesh.primitive_topology;
// TODO: don't allocate a new vertex buffer descriptor for every entity
render_pipeline.specialization.vertex_buffer_descriptor =
mesh.get_vertex_buffer_descriptor();
render_pipeline.specialization.index_format = mesh
.indices()
.map(|i| i.into())
.unwrap_or(IndexFormat::Uint32);
}
if let Some(RenderResourceId::Buffer(index_buffer_resource)) =
render_resource_context.get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX)
{
// set index buffer into binding
render_pipelines
.bindings
.set_index_buffer(index_buffer_resource);
}
if let Some(RenderResourceId::Buffer(vertex_attribute_buffer_resource)) =
render_resource_context.get_asset_resource(handle, VERTEX_ATTRIBUTE_BUFFER_ID)
{
// set index buffer into binding
render_pipelines.bindings.vertex_attribute_buffer =
Some(vertex_attribute_buffer_resource);
}
mesh_entities.entities.insert(entity);
mesh_entities.waiting.remove(&entity);
update_entity_mesh(render_resource_context, mesh, handle, render_pipelines);
} else {
mesh_entities.waiting.insert(entity);
}
}
}
fn update_entity_mesh(
render_resource_context: &dyn RenderResourceContext,
mesh: &Mesh,
handle: &Handle<Mesh>,
mut render_pipelines: Mut<RenderPipelines>,
) {
for render_pipeline in render_pipelines.pipelines.iter_mut() {
render_pipeline.specialization.primitive_topology = mesh.primitive_topology;
// TODO: don't allocate a new vertex buffer descriptor for every entity
render_pipeline.specialization.vertex_buffer_descriptor =
mesh.get_vertex_buffer_descriptor();
render_pipeline.specialization.index_format = mesh
.indices()
.map(|i| i.into())
.unwrap_or(IndexFormat::Uint32);
}
if let Some(RenderResourceId::Buffer(index_buffer_resource)) =
render_resource_context.get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX)
{
// set index buffer into binding
render_pipelines
.bindings
.set_index_buffer(index_buffer_resource);
}
if let Some(RenderResourceId::Buffer(vertex_attribute_buffer_resource)) =
render_resource_context.get_asset_resource(handle, VERTEX_ATTRIBUTE_BUFFER_ID)
{
// set index buffer into binding
render_pipelines.bindings.vertex_attribute_buffer = Some(vertex_attribute_buffer_resource);
}
}

View File

@ -244,7 +244,9 @@ where
for render_command in draw.render_commands.iter() {
match render_command {
RenderCommand::SetPipeline { pipeline } => {
// TODO: Filter pipelines
if draw_state.is_pipeline_set(pipeline.clone_weak()) {
continue;
}
render_pass.set_pipeline(pipeline);
let descriptor = pipelines.get(pipeline).unwrap();
draw_state.set_pipeline(pipeline, descriptor);
@ -290,18 +292,27 @@ where
offset,
slot,
} => {
if draw_state.is_vertex_buffer_set(*slot, *buffer, *offset) {
continue;
}
render_pass.set_vertex_buffer(*slot, *buffer, *offset);
draw_state.set_vertex_buffer(*slot, *buffer);
draw_state.set_vertex_buffer(*slot, *buffer, *offset);
}
RenderCommand::SetIndexBuffer { buffer, offset } => {
if draw_state.is_index_buffer_set(*buffer, *offset) {
continue;
}
render_pass.set_index_buffer(*buffer, *offset);
draw_state.set_index_buffer(*buffer)
draw_state.set_index_buffer(*buffer, *offset)
}
RenderCommand::SetBindGroup {
index,
bind_group,
dynamic_uniform_indices,
} => {
if dynamic_uniform_indices.is_none() && draw_state.is_bind_group_set(*index, *bind_group) {
continue;
}
let pipeline = pipelines.get(draw_state.pipeline.as_ref().unwrap()).unwrap();
let layout = pipeline.get_layout().unwrap();
let bind_group_descriptor = layout.get_bind_group(*index).unwrap();
@ -329,8 +340,8 @@ where
struct DrawState {
pipeline: Option<Handle<PipelineDescriptor>>,
bind_groups: Vec<Option<BindGroupId>>,
vertex_buffers: Vec<Option<BufferId>>,
index_buffer: Option<BufferId>,
vertex_buffers: Vec<Option<(BufferId, u64)>>,
index_buffer: Option<(BufferId, u64)>,
}
impl DrawState {
@ -338,12 +349,24 @@ impl DrawState {
self.bind_groups[index as usize] = Some(bind_group);
}
pub fn set_vertex_buffer(&mut self, index: u32, buffer: BufferId) {
self.vertex_buffers[index as usize] = Some(buffer);
pub fn is_bind_group_set(&self, index: u32, bind_group: BindGroupId) -> bool {
self.bind_groups[index as usize] == Some(bind_group)
}
pub fn set_index_buffer(&mut self, buffer: BufferId) {
self.index_buffer = Some(buffer);
pub fn set_vertex_buffer(&mut self, index: u32, buffer: BufferId, offset: u64) {
self.vertex_buffers[index as usize] = Some((buffer, offset));
}
pub fn is_vertex_buffer_set(&self, index: u32, buffer: BufferId, offset: u64) -> bool {
self.vertex_buffers[index as usize] == Some((buffer, offset))
}
pub fn set_index_buffer(&mut self, buffer: BufferId, offset: u64) {
self.index_buffer = Some((buffer, offset));
}
pub fn is_index_buffer_set(&self, buffer: BufferId, offset: u64) -> bool {
self.index_buffer == Some((buffer, offset))
}
pub fn can_draw(&self) -> bool {
@ -355,6 +378,10 @@ impl DrawState {
self.can_draw() && self.index_buffer.is_some()
}
pub fn is_pipeline_set(&self, pipeline: Handle<PipelineDescriptor>) -> bool {
self.pipeline == Some(pipeline)
}
pub fn set_pipeline(
&mut self,
handle: &Handle<PipelineDescriptor>,

View File

@ -10,7 +10,10 @@ use crate::{
};
use bevy_asset::{Asset, Assets, Handle, HandleId};
use bevy_ecs::{Commands, Entity, IntoSystem, Local, Query, Res, ResMut, Resources, System, World};
use bevy_ecs::{
Changed, Commands, Entity, IntoSystem, Local, Query, QuerySet, Res, ResMut, Resources, System,
World,
};
use bevy_utils::HashMap;
use renderer::{AssetRenderResourceBindings, BufferId, RenderResourceType, RenderResources};
use std::{hash::Hash, marker::PhantomData, ops::DerefMut};
@ -80,13 +83,14 @@ impl<I: Hash + Eq> BufferArray<I> {
}
}
pub fn resize(&mut self, render_resource_context: &dyn RenderResourceContext) {
pub fn resize(&mut self, render_resource_context: &dyn RenderResourceContext) -> bool {
if self.len <= self.buffer_capacity {
return;
return false;
}
self.allocate_buffer(render_resource_context);
// TODO: allow shrinking
true
}
pub fn allocate_buffer(&mut self, render_resource_context: &dyn RenderResourceContext) {
@ -189,12 +193,29 @@ where
}
/// Resize BufferArray buffers if they aren't large enough
fn resize_buffer_arrays(&mut self, render_resource_context: &dyn RenderResourceContext) {
fn resize_buffer_arrays(
&mut self,
render_resource_context: &dyn RenderResourceContext,
) -> bool {
let mut resized = false;
for buffer_array in self.buffer_arrays.iter_mut() {
if let Some(buffer_array) = buffer_array {
buffer_array.resize(render_resource_context);
resized |= buffer_array.resize(render_resource_context);
}
}
resized
}
fn set_required_staging_buffer_size_to_max(&mut self) {
let mut new_size = 0;
for buffer_array in self.buffer_arrays.iter() {
if let Some(buffer_array) = buffer_array {
new_size += buffer_array.item_size * buffer_array.len;
}
}
self.required_staging_buffer_size = new_size;
}
/// Update the staging buffer to provide enough space to copy data to target buffers.
@ -238,91 +259,83 @@ where
staging_buffer: &mut [u8],
) {
for (i, render_resource) in uniforms.iter().enumerate() {
match render_resource.resource_type() {
Some(RenderResourceType::Buffer) => {
let size = render_resource.buffer_byte_len().unwrap();
let render_resource_name = uniforms.get_render_resource_name(i).unwrap();
let aligned_size =
render_resource_context.get_aligned_uniform_size(size, false);
let buffer_array = self.buffer_arrays[i].as_mut().unwrap();
let range = 0..aligned_size as u64;
let (target_buffer, target_offset) = if dynamic_uniforms {
let binding = buffer_array.get_binding(id).unwrap();
let dynamic_index = if let RenderResourceBinding::Buffer {
dynamic_index: Some(dynamic_index),
..
} = binding
{
dynamic_index
} else {
panic!("dynamic index should always be set");
};
render_resource_bindings.set(render_resource_name, binding);
(buffer_array.buffer.unwrap(), dynamic_index)
if let Some(RenderResourceType::Buffer) = render_resource.resource_type() {
let size = render_resource.buffer_byte_len().unwrap();
let render_resource_name = uniforms.get_render_resource_name(i).unwrap();
let aligned_size = render_resource_context.get_aligned_uniform_size(size, false);
let buffer_array = self.buffer_arrays[i].as_mut().unwrap();
let range = 0..aligned_size as u64;
let (target_buffer, target_offset) = if dynamic_uniforms {
let binding = buffer_array.get_binding(id).unwrap();
let dynamic_index = if let RenderResourceBinding::Buffer {
dynamic_index: Some(dynamic_index),
..
} = binding
{
dynamic_index
} else {
let mut matching_buffer = None;
if let Some(binding) = render_resource_bindings.get(render_resource_name) {
let buffer_id = binding.get_buffer().unwrap();
if let Some(BufferInfo {
size: current_size, ..
}) = render_resource_context.get_buffer_info(buffer_id)
{
if aligned_size == current_size {
matching_buffer = Some(buffer_id);
} else {
render_resource_context.remove_buffer(buffer_id);
}
panic!("dynamic index should always be set");
};
render_resource_bindings.set(render_resource_name, binding);
(buffer_array.buffer.unwrap(), dynamic_index)
} else {
let mut matching_buffer = None;
if let Some(binding) = render_resource_bindings.get(render_resource_name) {
let buffer_id = binding.get_buffer().unwrap();
if let Some(BufferInfo {
size: current_size, ..
}) = render_resource_context.get_buffer_info(buffer_id)
{
if aligned_size == current_size {
matching_buffer = Some(buffer_id);
} else {
render_resource_context.remove_buffer(buffer_id);
}
}
}
let resource = if let Some(matching_buffer) = matching_buffer {
matching_buffer
} else {
let mut usage = BufferUsage::UNIFORM;
if let Some(render_resource_hints) = uniforms.get_render_resource_hints(i) {
if render_resource_hints.contains(RenderResourceHints::BUFFER) {
usage = BufferUsage::STORAGE
}
}
let resource = if let Some(matching_buffer) = matching_buffer {
matching_buffer
} else {
let mut usage = BufferUsage::UNIFORM;
if let Some(render_resource_hints) =
uniforms.get_render_resource_hints(i)
{
if render_resource_hints.contains(RenderResourceHints::BUFFER) {
usage = BufferUsage::STORAGE
}
}
let buffer = render_resource_context.create_buffer(BufferInfo {
size: aligned_size,
buffer_usage: BufferUsage::COPY_DST | usage,
..Default::default()
});
let buffer = render_resource_context.create_buffer(BufferInfo {
size: aligned_size,
buffer_usage: BufferUsage::COPY_DST | usage,
..Default::default()
});
render_resource_bindings.set(
render_resource_name,
RenderResourceBinding::Buffer {
buffer,
range,
dynamic_index: None,
},
);
buffer
};
(resource, 0)
render_resource_bindings.set(
render_resource_name,
RenderResourceBinding::Buffer {
buffer,
range,
dynamic_index: None,
},
);
buffer
};
render_resource.write_buffer_bytes(
&mut staging_buffer[self.current_staging_buffer_offset
..(self.current_staging_buffer_offset + size)],
);
(resource, 0)
};
self.queued_buffer_writes.push(QueuedBufferWrite {
buffer: target_buffer,
target_offset: target_offset as usize,
source_offset: self.current_staging_buffer_offset,
size,
});
self.current_staging_buffer_offset += size;
}
Some(RenderResourceType::Texture) => { /* ignore textures */ }
Some(RenderResourceType::Sampler) => { /* ignore samplers */ }
None => { /* ignore None */ }
render_resource.write_buffer_bytes(
&mut staging_buffer[self.current_staging_buffer_offset
..(self.current_staging_buffer_offset + size)],
);
self.queued_buffer_writes.push(QueuedBufferWrite {
buffer: target_buffer,
target_offset: target_offset as usize,
source_offset: self.current_staging_buffer_offset,
size,
});
self.current_staging_buffer_offset += size;
}
}
}
@ -421,22 +434,25 @@ impl<I, T: RenderResources> Default for RenderResourcesNodeState<I, T> {
fn render_resources_node_system<T: RenderResources>(
mut state: Local<RenderResourcesNodeState<Entity, T>>,
render_resource_context: Res<Box<dyn RenderResourceContext>>,
mut query: Query<(Entity, &T, &Draw, &mut RenderPipelines)>,
mut queries: QuerySet<(
Query<(Entity, &T, &Draw, &mut RenderPipelines), Changed<T>>,
Query<(Entity, &T, &Draw, &mut RenderPipelines)>,
)>,
) {
let state = state.deref_mut();
let uniform_buffer_arrays = &mut state.uniform_buffer_arrays;
let render_resource_context = &**render_resource_context;
uniform_buffer_arrays.begin_update();
// initialize uniform buffer arrays using the first RenderResources
if let Some((_, first, _, _)) = query.iter_mut().next() {
if let Some((_, first, _, _)) = queries.q0_mut().iter_mut().next() {
uniform_buffer_arrays.initialize(first, render_resource_context);
}
for entity in query.removed::<T>() {
for entity in queries.q0().removed::<T>() {
uniform_buffer_arrays.remove_bindings(*entity);
}
for (entity, uniforms, draw, mut render_pipelines) in query.iter_mut() {
for (entity, uniforms, draw, mut render_pipelines) in queries.q0_mut().iter_mut() {
if !draw.is_visible {
continue;
}
@ -449,7 +465,10 @@ fn render_resources_node_system<T: RenderResources>(
)
}
uniform_buffer_arrays.resize_buffer_arrays(render_resource_context);
let resized = uniform_buffer_arrays.resize_buffer_arrays(render_resource_context);
if resized {
uniform_buffer_arrays.set_required_staging_buffer_size_to_max()
}
uniform_buffer_arrays.resize_staging_buffer(render_resource_context);
if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer {
@ -458,19 +477,41 @@ fn render_resources_node_system<T: RenderResources>(
staging_buffer,
0..state.uniform_buffer_arrays.staging_buffer_size as u64,
&mut |mut staging_buffer, _render_resource_context| {
for (entity, uniforms, draw, mut render_pipelines) in query.iter_mut() {
if !draw.is_visible {
continue;
}
// if the buffer array was resized, write all entities to the new buffer, otherwise only write changes
if resized {
for (entity, uniforms, draw, mut render_pipelines) in
queries.q1_mut().iter_mut()
{
if !draw.is_visible {
continue;
}
state.uniform_buffer_arrays.write_uniform_buffers(
entity,
&uniforms,
state.dynamic_uniforms,
render_resource_context,
&mut render_pipelines.bindings,
&mut staging_buffer,
);
state.uniform_buffer_arrays.write_uniform_buffers(
entity,
&uniforms,
state.dynamic_uniforms,
render_resource_context,
&mut render_pipelines.bindings,
&mut staging_buffer,
);
}
} else {
for (entity, uniforms, draw, mut render_pipelines) in
queries.q0_mut().iter_mut()
{
if !draw.is_visible {
continue;
}
state.uniform_buffer_arrays.write_uniform_buffers(
entity,
&uniforms,
state.dynamic_uniforms,
render_resource_context,
&mut render_pipelines.bindings,
&mut staging_buffer,
);
}
}
},
);

View File

@ -152,4 +152,6 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
fn get_specialized_shader(&self, shader: &Shader, _macros: Option<&[String]>) -> Shader {
shader.clone()
}
fn remove_stale_bind_groups(&self) {}
}

View File

@ -99,6 +99,10 @@ impl RenderResourceBindings {
self.dirty_bind_groups.insert(*id);
}
}
} else {
// unmatched bind group descriptors might now match
self.bind_group_descriptors
.retain(|_, value| value.is_some());
}
}
@ -120,6 +124,7 @@ impl RenderResourceBindings {
self.bind_group_descriptors.insert(descriptor.id, Some(id));
BindGroupStatus::Changed(id)
} else {
self.bind_group_descriptors.insert(descriptor.id, None);
BindGroupStatus::NoMatch
}
}
@ -158,10 +163,9 @@ impl RenderResourceBindings {
.expect("RenderResourceSet was just changed, so it should exist");
render_resource_context.create_bind_group(bind_group_descriptor.id, bind_group);
}
// TODO: Don't re-create bind groups if they havent changed. this will require cleanup of orphan bind groups and
// removal of global context.clear_bind_groups()
// PERF: see above
BindGroupStatus::Unchanged(id) => {
// PERF: this is only required because RenderResourceContext::remove_stale_bind_groups doesn't inform RenderResourceBindings
// when a stale bind group has been removed
let bind_group = self
.get_bind_group(id)
.expect("RenderResourceSet was just changed, so it should exist");

View File

@ -62,6 +62,7 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
bind_group: &BindGroup,
);
fn clear_bind_groups(&self);
fn remove_stale_bind_groups(&self);
/// Reflects the pipeline layout from its shaders.
///
/// If `bevy_conventions` is true, it will be assumed that the shader follows "bevy shader conventions". These allow

View File

@ -50,7 +50,11 @@ pub fn sprite_system(
let material = materials.get(handle).unwrap();
if let Some(ref texture_handle) = material.texture {
if let Some(texture) = textures.get(texture_handle) {
sprite.size = texture.size.as_vec3().truncate();
let texture_size = texture.size.as_vec3().truncate();
// only set sprite size if it has changed (this check prevents change detection from triggering)
if sprite.size != texture_size {
sprite.size = texture_size;
}
}
}
}

View File

@ -52,6 +52,15 @@ impl<'a, 'b> WorldChildBuilder<'a, 'b> {
pub fn current_entity(&self) -> Option<Entity> {
self.world_builder.current_entity
}
pub fn for_current_entity(&mut self, f: impl FnOnce(Entity)) -> &mut Self {
let current_entity = self
.world_builder
.current_entity
.expect("The 'current entity' is not set. You should spawn an entity first.");
f(current_entity);
self
}
}
pub trait BuildWorldChildren {

View File

@ -3,22 +3,29 @@ use bevy_ecs::prelude::*;
pub fn transform_propagate_system(
mut root_query: Query<
(Option<&Children>, &Transform, &mut GlobalTransform),
(Entity, Option<&Children>, &Transform, &mut GlobalTransform),
(Without<Parent>, With<GlobalTransform>),
>,
mut transform_query: Query<(&Transform, &mut GlobalTransform), With<Parent>>,
changed_transform_query: Query<Entity, Changed<Transform>>,
children_query: Query<Option<&Children>, (With<Parent>, With<GlobalTransform>)>,
) {
for (children, transform, mut global_transform) in root_query.iter_mut() {
*global_transform = GlobalTransform::from(*transform);
for (entity, children, transform, mut global_transform) in root_query.iter_mut() {
let mut changed = false;
if changed_transform_query.get(entity).is_ok() {
*global_transform = GlobalTransform::from(*transform);
changed = true;
}
if let Some(children) = children {
for child in children.0.iter() {
propagate_recursive(
&global_transform,
&changed_transform_query,
&mut transform_query,
&children_query,
*child,
changed,
);
}
}
@ -27,13 +34,19 @@ pub fn transform_propagate_system(
fn propagate_recursive(
parent: &GlobalTransform,
changed_transform_query: &Query<Entity, Changed<Transform>>,
transform_query: &mut Query<(&Transform, &mut GlobalTransform), With<Parent>>,
children_query: &Query<Option<&Children>, (With<Parent>, With<GlobalTransform>)>,
entity: Entity,
mut changed: bool,
) {
changed |= changed_transform_query.get(entity).is_ok();
let global_matrix = {
if let Ok((transform, mut global_transform)) = transform_query.get_mut(entity) {
*global_transform = parent.mul_transform(*transform);
if changed {
*global_transform = parent.mul_transform(*transform);
}
*global_transform
} else {
return;
@ -42,7 +55,14 @@ fn propagate_recursive(
if let Ok(Some(children)) = children_query.get(entity) {
for child in children.0.iter() {
propagate_recursive(&global_matrix, transform_query, children_query, *child);
propagate_recursive(
&global_matrix,
changed_transform_query,
transform_query,
children_query,
*child,
changed,
);
}
}
}
@ -50,7 +70,7 @@ fn propagate_recursive(
#[cfg(test)]
mod test {
use super::*;
use crate::hierarchy::{parent_update_system, BuildChildren};
use crate::hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren};
use bevy_ecs::{Resources, Schedule, World};
use bevy_math::Vec3;
@ -65,30 +85,36 @@ mod test {
schedule.add_system_to_stage("update", transform_propagate_system);
// Root entity
let parent = world.spawn((
world.spawn((
Transform::from_translation(Vec3::new(1.0, 0.0, 0.0)),
GlobalTransform::identity(),
));
let children = world
.spawn_batch(vec![
(
Transform::from_translation(Vec3::new(0.0, 2.0, 0.)),
Parent(parent),
GlobalTransform::identity(),
),
(
Transform::from_translation(Vec3::new(0.0, 0.0, 3.)),
Parent(parent),
GlobalTransform::identity(),
),
])
.collect::<Vec<Entity>>();
let mut children = Vec::new();
world
.build()
.spawn((
Transform::from_translation(Vec3::new(1.0, 0.0, 0.0)),
GlobalTransform::identity(),
))
.with_children(|parent| {
parent
.spawn((
Transform::from_translation(Vec3::new(0.0, 2.0, 0.)),
GlobalTransform::identity(),
))
.for_current_entity(|entity| children.push(entity))
.spawn((
Transform::from_translation(Vec3::new(0.0, 0.0, 3.)),
GlobalTransform::identity(),
))
.for_current_entity(|entity| children.push(entity));
});
// we need to run the schedule two times because components need to be filled in
// to resolve this problem in code, just add the correct components, or use Commands
// which adds all of the components needed with the correct state (see next test)
schedule.initialize(&mut world, &mut resources);
schedule.run(&mut world, &mut resources);
schedule.run(&mut world, &mut resources);
assert_eq!(
*world.get::<GlobalTransform>(children[0]).unwrap(),

View File

@ -522,6 +522,10 @@ impl RenderResourceContext for WgpuRenderResourceContext {
self.resources.bind_groups.write().clear();
}
fn remove_stale_bind_groups(&self) {
self.resources.remove_stale_bind_groups();
}
fn get_buffer_info(&self, buffer: BufferId) -> Option<BufferInfo> {
self.resources.buffer_infos.read().get(&buffer).cloned()
}

View File

@ -74,6 +74,10 @@ impl<'a> RenderPass for WgpuRenderPass<'a> {
} else {
EMPTY
};
self.wgpu_resources
.used_bind_group_sender
.send(bind_group)
.unwrap();
trace!(
"set bind group {:?} {:?}: {:?}",

View File

@ -115,6 +115,6 @@ impl WgpuRenderer {
let render_resource_context = resources.get::<Box<dyn RenderResourceContext>>().unwrap();
render_resource_context.drop_all_swap_chain_textures();
render_resource_context.clear_bind_groups();
render_resource_context.remove_stale_bind_groups();
}
}

View File

@ -7,6 +7,7 @@ use bevy_render::{
};
use bevy_utils::HashMap;
use bevy_window::WindowId;
use crossbeam_channel::{Receiver, Sender, TryRecvError};
use parking_lot::{RwLock, RwLockReadGuard};
use std::sync::Arc;
@ -45,6 +46,7 @@ pub struct WgpuResourcesReadLock<'a> {
pub render_pipelines:
RwLockReadGuard<'a, HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>>,
pub bind_groups: RwLockReadGuard<'a, HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>,
pub used_bind_group_sender: Sender<BindGroupId>,
}
impl<'a> WgpuResourcesReadLock<'a> {
@ -55,6 +57,7 @@ impl<'a> WgpuResourcesReadLock<'a> {
swap_chain_frames: &self.swap_chain_frames,
render_pipelines: &self.render_pipelines,
bind_groups: &self.bind_groups,
used_bind_group_sender: &self.used_bind_group_sender,
}
}
}
@ -67,6 +70,7 @@ pub struct WgpuResourceRefs<'a> {
pub swap_chain_frames: &'a HashMap<TextureId, wgpu::SwapChainFrame>,
pub render_pipelines: &'a HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>,
pub bind_groups: &'a HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>,
pub used_bind_group_sender: &'a Sender<BindGroupId>,
}
#[derive(Default, Clone, Debug)]
@ -85,6 +89,7 @@ pub struct WgpuResources {
pub bind_groups: Arc<RwLock<HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>>,
pub bind_group_layouts: Arc<RwLock<HashMap<BindGroupDescriptorId, wgpu::BindGroupLayout>>>,
pub asset_resources: Arc<RwLock<HashMap<(HandleUntyped, u64), RenderResourceId>>>,
pub bind_group_counter: BindGroupCounter,
}
impl WgpuResources {
@ -95,6 +100,7 @@ impl WgpuResources {
swap_chain_frames: self.swap_chain_frames.read(),
render_pipelines: self.render_pipelines.read(),
bind_groups: self.bind_groups.read(),
used_bind_group_sender: self.bind_group_counter.used_bind_group_sender.clone(),
}
}
@ -109,4 +115,64 @@ impl WgpuResources {
false
}
}
pub fn remove_stale_bind_groups(&self) {
let mut bind_groups = self.bind_groups.write();
self.bind_group_counter
.remove_stale_bind_groups(&mut bind_groups);
}
}
#[derive(Clone, Debug)]
pub struct BindGroupCounter {
pub used_bind_group_sender: Sender<BindGroupId>,
pub used_bind_group_receiver: Receiver<BindGroupId>,
pub bind_group_usage_counts: Arc<RwLock<HashMap<BindGroupId, u64>>>,
}
impl BindGroupCounter {
pub fn remove_stale_bind_groups(
&self,
bind_groups: &mut HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>,
) {
let mut bind_group_usage_counts = self.bind_group_usage_counts.write();
loop {
let bind_group = match self.used_bind_group_receiver.try_recv() {
Ok(bind_group) => bind_group,
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => panic!("used bind group channel disconnected"),
};
let count = bind_group_usage_counts.entry(bind_group).or_insert(0);
// free every two frames
*count = 2;
}
for info in bind_groups.values_mut() {
info.bind_groups.retain(|id, _| {
let retain = {
// if a value hasn't been counted yet, give it two frames of leeway
let count = bind_group_usage_counts.entry(*id).or_insert(2);
*count -= 1;
*count > 0
};
if !retain {
bind_group_usage_counts.remove(&id);
}
retain
})
}
}
}
impl Default for BindGroupCounter {
fn default() -> Self {
let (send, recv) = crossbeam_channel::unbounded();
BindGroupCounter {
used_bind_group_sender: send,
used_bind_group_receiver: recv,
bind_group_usage_counts: Default::default(),
}
}
}