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.current_entity = Some(self.world.spawn(components));
self 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_app::prelude::{EventReader, Events};
use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_core::AsBytes; 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_math::*;
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use std::borrow::Cow; use std::borrow::Cow;
use crate::pipeline::{InputStepMode, VertexAttributeDescriptor, VertexBufferDescriptor}; 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 INDEX_BUFFER_ASSET_INDEX: u64 = 0;
pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; 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); remove_resource_save(render_resource_context, handle, INDEX_BUFFER_ASSET_INDEX);
} }
#[derive(Default)]
pub struct MeshEntities {
entities: HashSet<Entity>,
waiting: HashSet<Entity>,
}
#[derive(Default)] #[derive(Default)]
pub struct MeshResourceProviderState { pub struct MeshResourceProviderState {
mesh_event_reader: EventReader<AssetEvent<Mesh>>, mesh_event_reader: EventReader<AssetEvent<Mesh>>,
mesh_entities: HashMap<Handle<Mesh>, MeshEntities>,
} }
pub fn mesh_resource_provider_system( pub fn mesh_resource_provider_system(
@ -330,7 +337,10 @@ pub fn mesh_resource_provider_system(
render_resource_context: Res<Box<dyn RenderResourceContext>>, render_resource_context: Res<Box<dyn RenderResourceContext>>,
meshes: Res<Assets<Mesh>>, meshes: Res<Assets<Mesh>>,
mesh_events: Res<Events<AssetEvent<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 mut changed_meshes = bevy_utils::HashSet::<Handle<Mesh>>::default();
let render_resource_context = &**render_resource_context; let render_resource_context = &**render_resource_context;
@ -383,39 +393,69 @@ pub fn mesh_resource_provider_system(
)), )),
VERTEX_ATTRIBUTE_BUFFER_ID, 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 // 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) { if let Some(mesh) = meshes.get(handle) {
for render_pipeline in render_pipelines.pipelines.iter_mut() { mesh_entities.entities.insert(entity);
render_pipeline.specialization.primitive_topology = mesh.primitive_topology; mesh_entities.waiting.remove(&entity);
// TODO: don't allocate a new vertex buffer descriptor for every entity update_entity_mesh(render_resource_context, mesh, handle, render_pipelines);
render_pipeline.specialization.vertex_buffer_descriptor = } else {
mesh.get_vertex_buffer_descriptor(); mesh_entities.waiting.insert(entity);
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);
}
} }
} }
} }
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() { for render_command in draw.render_commands.iter() {
match render_command { match render_command {
RenderCommand::SetPipeline { pipeline } => { RenderCommand::SetPipeline { pipeline } => {
// TODO: Filter pipelines if draw_state.is_pipeline_set(pipeline.clone_weak()) {
continue;
}
render_pass.set_pipeline(pipeline); render_pass.set_pipeline(pipeline);
let descriptor = pipelines.get(pipeline).unwrap(); let descriptor = pipelines.get(pipeline).unwrap();
draw_state.set_pipeline(pipeline, descriptor); draw_state.set_pipeline(pipeline, descriptor);
@ -290,18 +292,27 @@ where
offset, offset,
slot, slot,
} => { } => {
if draw_state.is_vertex_buffer_set(*slot, *buffer, *offset) {
continue;
}
render_pass.set_vertex_buffer(*slot, *buffer, *offset); 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 } => { RenderCommand::SetIndexBuffer { buffer, offset } => {
if draw_state.is_index_buffer_set(*buffer, *offset) {
continue;
}
render_pass.set_index_buffer(*buffer, *offset); render_pass.set_index_buffer(*buffer, *offset);
draw_state.set_index_buffer(*buffer) draw_state.set_index_buffer(*buffer, *offset)
} }
RenderCommand::SetBindGroup { RenderCommand::SetBindGroup {
index, index,
bind_group, bind_group,
dynamic_uniform_indices, 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 pipeline = pipelines.get(draw_state.pipeline.as_ref().unwrap()).unwrap();
let layout = pipeline.get_layout().unwrap(); let layout = pipeline.get_layout().unwrap();
let bind_group_descriptor = layout.get_bind_group(*index).unwrap(); let bind_group_descriptor = layout.get_bind_group(*index).unwrap();
@ -329,8 +340,8 @@ where
struct DrawState { struct DrawState {
pipeline: Option<Handle<PipelineDescriptor>>, pipeline: Option<Handle<PipelineDescriptor>>,
bind_groups: Vec<Option<BindGroupId>>, bind_groups: Vec<Option<BindGroupId>>,
vertex_buffers: Vec<Option<BufferId>>, vertex_buffers: Vec<Option<(BufferId, u64)>>,
index_buffer: Option<BufferId>, index_buffer: Option<(BufferId, u64)>,
} }
impl DrawState { impl DrawState {
@ -338,12 +349,24 @@ impl DrawState {
self.bind_groups[index as usize] = Some(bind_group); self.bind_groups[index as usize] = Some(bind_group);
} }
pub fn set_vertex_buffer(&mut self, index: u32, buffer: BufferId) { pub fn is_bind_group_set(&self, index: u32, bind_group: BindGroupId) -> bool {
self.vertex_buffers[index as usize] = Some(buffer); self.bind_groups[index as usize] == Some(bind_group)
} }
pub fn set_index_buffer(&mut self, buffer: BufferId) { pub fn set_vertex_buffer(&mut self, index: u32, buffer: BufferId, offset: u64) {
self.index_buffer = Some(buffer); 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 { pub fn can_draw(&self) -> bool {
@ -355,6 +378,10 @@ impl DrawState {
self.can_draw() && self.index_buffer.is_some() 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( pub fn set_pipeline(
&mut self, &mut self,
handle: &Handle<PipelineDescriptor>, handle: &Handle<PipelineDescriptor>,

View File

@ -10,7 +10,10 @@ use crate::{
}; };
use bevy_asset::{Asset, Assets, Handle, HandleId}; 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 bevy_utils::HashMap;
use renderer::{AssetRenderResourceBindings, BufferId, RenderResourceType, RenderResources}; use renderer::{AssetRenderResourceBindings, BufferId, RenderResourceType, RenderResources};
use std::{hash::Hash, marker::PhantomData, ops::DerefMut}; 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 { if self.len <= self.buffer_capacity {
return; return false;
} }
self.allocate_buffer(render_resource_context); self.allocate_buffer(render_resource_context);
// TODO: allow shrinking // TODO: allow shrinking
true
} }
pub fn allocate_buffer(&mut self, render_resource_context: &dyn RenderResourceContext) { 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 /// 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() { for buffer_array in self.buffer_arrays.iter_mut() {
if let Some(buffer_array) = buffer_array { 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. /// Update the staging buffer to provide enough space to copy data to target buffers.
@ -238,91 +259,83 @@ where
staging_buffer: &mut [u8], staging_buffer: &mut [u8],
) { ) {
for (i, render_resource) in uniforms.iter().enumerate() { for (i, render_resource) in uniforms.iter().enumerate() {
match render_resource.resource_type() { if let Some(RenderResourceType::Buffer) = render_resource.resource_type() {
Some(RenderResourceType::Buffer) => { let size = render_resource.buffer_byte_len().unwrap();
let size = render_resource.buffer_byte_len().unwrap(); let render_resource_name = uniforms.get_render_resource_name(i).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 aligned_size = let buffer_array = self.buffer_arrays[i].as_mut().unwrap();
render_resource_context.get_aligned_uniform_size(size, false); let range = 0..aligned_size as u64;
let buffer_array = self.buffer_arrays[i].as_mut().unwrap(); let (target_buffer, target_offset) = if dynamic_uniforms {
let range = 0..aligned_size as u64; let binding = buffer_array.get_binding(id).unwrap();
let (target_buffer, target_offset) = if dynamic_uniforms { let dynamic_index = if let RenderResourceBinding::Buffer {
let binding = buffer_array.get_binding(id).unwrap(); dynamic_index: Some(dynamic_index),
let dynamic_index = if let RenderResourceBinding::Buffer { ..
dynamic_index: Some(dynamic_index), } = binding
.. {
} = binding dynamic_index
{
dynamic_index
} else {
panic!("dynamic index should always be set");
};
render_resource_bindings.set(render_resource_name, binding);
(buffer_array.buffer.unwrap(), dynamic_index)
} else { } else {
let mut matching_buffer = None; panic!("dynamic index should always be set");
if let Some(binding) = render_resource_bindings.get(render_resource_name) { };
let buffer_id = binding.get_buffer().unwrap(); render_resource_bindings.set(render_resource_name, binding);
if let Some(BufferInfo { (buffer_array.buffer.unwrap(), dynamic_index)
size: current_size, .. } else {
}) = render_resource_context.get_buffer_info(buffer_id) let mut matching_buffer = None;
{ if let Some(binding) = render_resource_bindings.get(render_resource_name) {
if aligned_size == current_size { let buffer_id = binding.get_buffer().unwrap();
matching_buffer = Some(buffer_id); if let Some(BufferInfo {
} else { size: current_size, ..
render_resource_context.remove_buffer(buffer_id); }) = 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 { let buffer = render_resource_context.create_buffer(BufferInfo {
matching_buffer size: aligned_size,
} else { buffer_usage: BufferUsage::COPY_DST | usage,
let mut usage = BufferUsage::UNIFORM; ..Default::default()
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 { render_resource_bindings.set(
size: aligned_size, render_resource_name,
buffer_usage: BufferUsage::COPY_DST | usage, RenderResourceBinding::Buffer {
..Default::default() buffer,
}); range,
dynamic_index: None,
render_resource_bindings.set( },
render_resource_name, );
RenderResourceBinding::Buffer { buffer
buffer,
range,
dynamic_index: None,
},
);
buffer
};
(resource, 0)
}; };
render_resource.write_buffer_bytes( (resource, 0)
&mut staging_buffer[self.current_staging_buffer_offset };
..(self.current_staging_buffer_offset + size)],
);
self.queued_buffer_writes.push(QueuedBufferWrite { render_resource.write_buffer_bytes(
buffer: target_buffer, &mut staging_buffer[self.current_staging_buffer_offset
target_offset: target_offset as usize, ..(self.current_staging_buffer_offset + size)],
source_offset: self.current_staging_buffer_offset, );
size,
}); self.queued_buffer_writes.push(QueuedBufferWrite {
self.current_staging_buffer_offset += size; buffer: target_buffer,
} target_offset: target_offset as usize,
Some(RenderResourceType::Texture) => { /* ignore textures */ } source_offset: self.current_staging_buffer_offset,
Some(RenderResourceType::Sampler) => { /* ignore samplers */ } size,
None => { /* ignore None */ } });
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>( fn render_resources_node_system<T: RenderResources>(
mut state: Local<RenderResourcesNodeState<Entity, T>>, mut state: Local<RenderResourcesNodeState<Entity, T>>,
render_resource_context: Res<Box<dyn RenderResourceContext>>, 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 state = state.deref_mut();
let uniform_buffer_arrays = &mut state.uniform_buffer_arrays; let uniform_buffer_arrays = &mut state.uniform_buffer_arrays;
let render_resource_context = &**render_resource_context; let render_resource_context = &**render_resource_context;
uniform_buffer_arrays.begin_update(); uniform_buffer_arrays.begin_update();
// initialize uniform buffer arrays using the first RenderResources // 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); 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); 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 { if !draw.is_visible {
continue; 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); uniform_buffer_arrays.resize_staging_buffer(render_resource_context);
if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer { if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer {
@ -458,19 +477,41 @@ fn render_resources_node_system<T: RenderResources>(
staging_buffer, staging_buffer,
0..state.uniform_buffer_arrays.staging_buffer_size as u64, 0..state.uniform_buffer_arrays.staging_buffer_size as u64,
&mut |mut staging_buffer, _render_resource_context| { &mut |mut staging_buffer, _render_resource_context| {
for (entity, uniforms, draw, mut render_pipelines) in query.iter_mut() { // if the buffer array was resized, write all entities to the new buffer, otherwise only write changes
if !draw.is_visible { if resized {
continue; 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( state.uniform_buffer_arrays.write_uniform_buffers(
entity, entity,
&uniforms, &uniforms,
state.dynamic_uniforms, state.dynamic_uniforms,
render_resource_context, render_resource_context,
&mut render_pipelines.bindings, &mut render_pipelines.bindings,
&mut staging_buffer, &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 { fn get_specialized_shader(&self, shader: &Shader, _macros: Option<&[String]>) -> Shader {
shader.clone() shader.clone()
} }
fn remove_stale_bind_groups(&self) {}
} }

View File

@ -99,6 +99,10 @@ impl RenderResourceBindings {
self.dirty_bind_groups.insert(*id); 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)); self.bind_group_descriptors.insert(descriptor.id, Some(id));
BindGroupStatus::Changed(id) BindGroupStatus::Changed(id)
} else { } else {
self.bind_group_descriptors.insert(descriptor.id, None);
BindGroupStatus::NoMatch BindGroupStatus::NoMatch
} }
} }
@ -158,10 +163,9 @@ impl RenderResourceBindings {
.expect("RenderResourceSet was just changed, so it should exist"); .expect("RenderResourceSet was just changed, so it should exist");
render_resource_context.create_bind_group(bind_group_descriptor.id, bind_group); 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) => { 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 let bind_group = self
.get_bind_group(id) .get_bind_group(id)
.expect("RenderResourceSet was just changed, so it should exist"); .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, bind_group: &BindGroup,
); );
fn clear_bind_groups(&self); fn clear_bind_groups(&self);
fn remove_stale_bind_groups(&self);
/// Reflects the pipeline layout from its shaders. /// 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 /// 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(); let material = materials.get(handle).unwrap();
if let Some(ref texture_handle) = material.texture { if let Some(ref texture_handle) = material.texture {
if let Some(texture) = textures.get(texture_handle) { 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> { pub fn current_entity(&self) -> Option<Entity> {
self.world_builder.current_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 { pub trait BuildWorldChildren {

View File

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

View File

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

View File

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

View File

@ -115,6 +115,6 @@ impl WgpuRenderer {
let render_resource_context = resources.get::<Box<dyn RenderResourceContext>>().unwrap(); let render_resource_context = resources.get::<Box<dyn RenderResourceContext>>().unwrap();
render_resource_context.drop_all_swap_chain_textures(); 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_utils::HashMap;
use bevy_window::WindowId; use bevy_window::WindowId;
use crossbeam_channel::{Receiver, Sender, TryRecvError};
use parking_lot::{RwLock, RwLockReadGuard}; use parking_lot::{RwLock, RwLockReadGuard};
use std::sync::Arc; use std::sync::Arc;
@ -45,6 +46,7 @@ pub struct WgpuResourcesReadLock<'a> {
pub render_pipelines: pub render_pipelines:
RwLockReadGuard<'a, HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>>, RwLockReadGuard<'a, HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>>,
pub bind_groups: RwLockReadGuard<'a, HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>, pub bind_groups: RwLockReadGuard<'a, HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>,
pub used_bind_group_sender: Sender<BindGroupId>,
} }
impl<'a> WgpuResourcesReadLock<'a> { impl<'a> WgpuResourcesReadLock<'a> {
@ -55,6 +57,7 @@ impl<'a> WgpuResourcesReadLock<'a> {
swap_chain_frames: &self.swap_chain_frames, swap_chain_frames: &self.swap_chain_frames,
render_pipelines: &self.render_pipelines, render_pipelines: &self.render_pipelines,
bind_groups: &self.bind_groups, 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 swap_chain_frames: &'a HashMap<TextureId, wgpu::SwapChainFrame>,
pub render_pipelines: &'a HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>, pub render_pipelines: &'a HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>,
pub bind_groups: &'a HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>, pub bind_groups: &'a HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>,
pub used_bind_group_sender: &'a Sender<BindGroupId>,
} }
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
@ -85,6 +89,7 @@ pub struct WgpuResources {
pub bind_groups: Arc<RwLock<HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>>, pub bind_groups: Arc<RwLock<HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>>,
pub bind_group_layouts: Arc<RwLock<HashMap<BindGroupDescriptorId, wgpu::BindGroupLayout>>>, pub bind_group_layouts: Arc<RwLock<HashMap<BindGroupDescriptorId, wgpu::BindGroupLayout>>>,
pub asset_resources: Arc<RwLock<HashMap<(HandleUntyped, u64), RenderResourceId>>>, pub asset_resources: Arc<RwLock<HashMap<(HandleUntyped, u64), RenderResourceId>>>,
pub bind_group_counter: BindGroupCounter,
} }
impl WgpuResources { impl WgpuResources {
@ -95,6 +100,7 @@ impl WgpuResources {
swap_chain_frames: self.swap_chain_frames.read(), swap_chain_frames: self.swap_chain_frames.read(),
render_pipelines: self.render_pipelines.read(), render_pipelines: self.render_pipelines.read(),
bind_groups: self.bind_groups.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 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(),
}
}
} }