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:
parent
3cee95e59a
commit
b5ffab7135
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 {:?} {:?}: {:?}",
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user