From 7e2a6ecbd5cc4359fc1c956b572ab444ea5e7a93 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 1 Jun 2022 15:36:12 -0700 Subject: [PATCH 1/7] Use thread local to improve render parallelism --- crates/bevy_core_pipeline/src/main_pass_2d.rs | 10 +++--- crates/bevy_core_pipeline/src/main_pass_3d.rs | 18 +++++------ crates/bevy_pbr/src/material.rs | 11 +++---- crates/bevy_pbr/src/render/light.rs | 10 +++--- crates/bevy_pbr/src/wireframe.rs | 4 +-- crates/bevy_render/Cargo.toml | 1 + crates/bevy_render/src/render_phase/mod.rs | 31 +++++++++++++------ crates/bevy_sprite/src/mesh2d/material.rs | 4 +-- crates/bevy_sprite/src/render/mod.rs | 2 +- crates/bevy_ui/src/render/mod.rs | 4 +-- crates/bevy_ui/src/render/render_pass.rs | 4 +-- 11 files changed, 57 insertions(+), 42 deletions(-) diff --git a/crates/bevy_core_pipeline/src/main_pass_2d.rs b/crates/bevy_core_pipeline/src/main_pass_2d.rs index 6fc8537a158bf..259aad2579f23 100644 --- a/crates/bevy_core_pipeline/src/main_pass_2d.rs +++ b/crates/bevy_core_pipeline/src/main_pass_2d.rs @@ -9,8 +9,10 @@ use bevy_render::{ }; pub struct MainPass2dNode { - query: - QueryState<(&'static RenderPhase, &'static ViewTarget), With>, + query: QueryState< + (&'static mut RenderPhase, &'static ViewTarget), + With, + >, } impl MainPass2dNode { @@ -45,7 +47,7 @@ impl Node for MainPass2dNode { _ => return Ok(()), }; - if transparent_phase.items.is_empty() { + if transparent_phase.sorted.is_empty() { return Ok(()); } @@ -66,7 +68,7 @@ impl Node for MainPass2dNode { let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for item in &transparent_phase.items { + for item in &transparent_phase.sorted { let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); draw_function.draw(world, &mut tracked_pass, view_entity, item); } diff --git a/crates/bevy_core_pipeline/src/main_pass_3d.rs b/crates/bevy_core_pipeline/src/main_pass_3d.rs index bd2ab599c4998..4980bae273a7e 100644 --- a/crates/bevy_core_pipeline/src/main_pass_3d.rs +++ b/crates/bevy_core_pipeline/src/main_pass_3d.rs @@ -13,9 +13,9 @@ use bevy_utils::tracing::info_span; pub struct MainPass3dNode { query: QueryState< ( - &'static RenderPhase, - &'static RenderPhase, - &'static RenderPhase, + &'static mut RenderPhase, + &'static mut RenderPhase, + &'static mut RenderPhase, &'static ViewTarget, &'static ViewDepthTexture, ), @@ -55,7 +55,7 @@ impl Node for MainPass3dNode { Err(_) => return Ok(()), // No window }; - if !opaque_phase.items.is_empty() { + if !opaque_phase.sorted.is_empty() { // Run the opaque pass, sorted front-to-back // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] @@ -86,13 +86,13 @@ impl Node for MainPass3dNode { .begin_render_pass(&pass_descriptor); let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for item in &opaque_phase.items { + for item in &opaque_phase.sorted { let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); draw_function.draw(world, &mut tracked_pass, view_entity, item); } } - if !alpha_mask_phase.items.is_empty() { + if !alpha_mask_phase.sorted.is_empty() { // Run the alpha mask pass, sorted front-to-back // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] @@ -122,13 +122,13 @@ impl Node for MainPass3dNode { .begin_render_pass(&pass_descriptor); let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for item in &alpha_mask_phase.items { + for item in &alpha_mask_phase.sorted { let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); draw_function.draw(world, &mut tracked_pass, view_entity, item); } } - if !transparent_phase.items.is_empty() { + if !transparent_phase.sorted.is_empty() { // Run the transparent pass, sorted back-to-front // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] @@ -163,7 +163,7 @@ impl Node for MainPass3dNode { .begin_render_pass(&pass_descriptor); let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for item in &transparent_phase.items { + for item in &transparent_phase.sorted { let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); draw_function.draw(world, &mut tracked_pass, view_entity, item); } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index ec1cefdf550ec..085a5efa1ea1c 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -335,16 +335,15 @@ pub fn queue_material_meshes( render_meshes: Res>, render_materials: Res>, material_meshes: Query<(&Handle, &Handle, &MeshUniform)>, - mut views: Query<( + views: Query<( &ExtractedView, &VisibleEntities, - &mut RenderPhase, - &mut RenderPhase, - &mut RenderPhase, + &RenderPhase, + &RenderPhase, + &RenderPhase, )>, ) { - for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in - views.iter_mut() + for (view, visible_entities, opaque_phase, alpha_mask_phase, transparent_phase) in views.iter() { let draw_opaque_pbr = opaque_draw_functions .read() diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 57a1b825b56d1..2510159f7d54b 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1343,7 +1343,7 @@ pub fn queue_shadows( mut pipelines: ResMut>, mut pipeline_cache: ResMut, view_lights: Query<&ViewLightEntities>, - mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase)>, + view_light_shadow_phases: Query<(&LightEntity, &RenderPhase)>, point_light_entities: Query<&CubemapVisibleEntities, With>, directional_light_entities: Query<&VisibleEntities, With>, ) { @@ -1353,8 +1353,8 @@ pub fn queue_shadows( .get_id::() .unwrap(); for view_light_entity in view_lights.lights.iter().copied() { - let (light_entity, mut shadow_phase) = - view_light_shadow_phases.get_mut(view_light_entity).unwrap(); + let (light_entity, shadow_phase) = + view_light_shadow_phases.get(view_light_entity).unwrap(); let visible_entities = match light_entity { LightEntity::Directional { light_entity } => directional_light_entities .get(*light_entity) @@ -1476,7 +1476,7 @@ impl Node for ShadowPassNode { .get_manual(world, view_light_entity) .unwrap(); - if shadow_phase.items.is_empty() { + if shadow_phase.sorted.is_empty() { continue; } @@ -1499,7 +1499,7 @@ impl Node for ShadowPassNode { .begin_render_pass(&pass_descriptor); let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for item in &shadow_phase.items { + for item in &shadow_phase.sorted { let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); draw_function.draw(world, &mut tracked_pass, view_light_entity, item); } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index b6bf1646a2cae..578d651d3027c 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -109,14 +109,14 @@ fn queue_wireframes( Query<(Entity, &Handle, &MeshUniform)>, Query<(Entity, &Handle, &MeshUniform), With>, )>, - mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase)>, + views: Query<(&ExtractedView, &VisibleEntities, &RenderPhase)>, ) { let draw_custom = opaque_3d_draw_functions .read() .get_id::() .unwrap(); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); - for (view, visible_entities, mut opaque_phase) in views.iter_mut() { + for (view, visible_entities, opaque_phase) in views.iter() { let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index ef15ffac447ba..7d983d485747c 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -70,3 +70,4 @@ ruzstd = { version = "0.2.4", optional = true } # For transcoding of UASTC/ETC1S universal formats, and for .basis file support basis-universal = { version = "0.2.0", optional = true } encase = { version = "0.2", features = ["glam"] } +thread_local = "1.1" diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 76de6bf66501f..0d64aedb749b9 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -5,31 +5,44 @@ pub use draw::*; pub use draw_state::*; use bevy_ecs::prelude::{Component, Query}; +use std::cell::Cell; +use thread_local::ThreadLocal; use copyless::VecHelper; /// A resource to collect and sort draw requests for specific [`PhaseItems`](PhaseItem). #[derive(Component)] pub struct RenderPhase { - pub items: Vec, + items: ThreadLocal>>, + pub sorted: Vec, } impl Default for RenderPhase { fn default() -> Self { - Self { items: Vec::new() } + Self { + items: ThreadLocal::new(), + sorted: Vec::new(), + } } } impl RenderPhase { /// Adds a [`PhaseItem`] to this render phase. #[inline] - pub fn add(&mut self, item: I) { - self.items.alloc().init(item); + pub fn add(&self, item: I) { + let cell = self.items.get_or_default(); + let mut queue = cell.take(); + queue.alloc().init(item); + cell.set(queue); } /// Sorts all of its [`PhaseItems`](PhaseItem). pub fn sort(&mut self) { - self.items.sort_by_key(|d| d.sort_key()); + debug_assert!(self.sorted.is_empty()); + for batch in self.items.iter_mut() { + self.sorted.append(batch.get_mut()); + } + self.sorted.sort_by_key(|d| d.sort_key()); } } @@ -37,10 +50,10 @@ impl RenderPhase { /// Batches the compatible [`BatchedPhaseItem`]s of this render phase pub fn batch(&mut self) { // TODO: this could be done in-place - let mut items = std::mem::take(&mut self.items); + let mut items = std::mem::take(&mut self.sorted); let mut items = items.drain(..); - self.items.reserve(items.len()); + self.sorted.reserve(items.len()); // Start the first batch from the first item if let Some(mut current_batch) = items.next() { @@ -51,12 +64,12 @@ impl RenderPhase { BatchResult::IncompatibleItems ) { // Store the completed batch, and start a new one from the incompatible item - self.items.push(current_batch); + self.sorted.push(current_batch); current_batch = next_item; } } // Store the last batch - self.items.push(current_batch); + self.sorted.push(current_batch); } } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index bd938c91dbd96..e83430efb7cfa 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -304,13 +304,13 @@ pub fn queue_material2d_meshes( render_meshes: Res>, render_materials: Res>, material2d_meshes: Query<(&Handle, &Mesh2dHandle, &Mesh2dUniform)>, - mut views: Query<(&VisibleEntities, &mut RenderPhase)>, + views: Query<(&VisibleEntities, &RenderPhase)>, ) { if material2d_meshes.is_empty() { return; } let render_device = render_device.into_inner(); - for (visible_entities, mut transparent_phase) in views.iter_mut() { + for (visible_entities, transparent_phase) in views.iter() { let draw_transparent_pbr = transparent_draw_functions .read() .get_id::>() diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index cf07b16e65aad..44bccb00a3dba 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -394,7 +394,7 @@ pub fn queue_sprites( let extracted_sprites = &mut extracted_sprites.sprites; let image_bind_groups = &mut *image_bind_groups; - transparent_phase.items.reserve(extracted_sprites.len()); + transparent_phase.sorted.reserve(extracted_sprites.len()); // Sort sprites by z for correct transparency and then by handle to improve batching extracted_sprites.sort_unstable_by(|a, b| { diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index e42d0a83347be..b9d0380ec7827 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -401,7 +401,7 @@ pub fn queue_uinodes( mut image_bind_groups: ResMut, gpu_images: Res>, ui_batches: Query<(Entity, &UiBatch)>, - mut views: Query<&mut RenderPhase>, + views: Query<&RenderPhase>, events: Res, ) { // If an image has changed, the GpuImage has (probably) changed @@ -425,7 +425,7 @@ pub fn queue_uinodes( })); let draw_ui_function = draw_functions.read().get_id::().unwrap(); let pipeline = pipelines.specialize(&mut pipeline_cache, &ui_pipeline, UiPipelineKey {}); - for mut transparent_phase in views.iter_mut() { + for transparent_phase in views.iter() { for (entity, batch) in ui_batches.iter() { image_bind_groups .values diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 90d80296027e8..2b0de420e0b9d 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -73,7 +73,7 @@ impl Node for UiPassNode { _ => return Ok(()), }; - if transparent_phase.items.is_empty() { + if transparent_phase.sorted.is_empty() { return Ok(()); } let pass_descriptor = RenderPassDescriptor { @@ -97,7 +97,7 @@ impl Node for UiPassNode { let mut draw_functions = draw_functions.write(); let mut tracked_pass = TrackedRenderPass::new(render_pass); - for item in &transparent_phase.items { + for item in &transparent_phase.sorted { let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); draw_function.draw(world, &mut tracked_pass, view_entity, item); } From 652a8b560323f539a8e8271357e2c414087e3ffe Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 1 Jun 2022 23:20:48 -0700 Subject: [PATCH 2/7] Further optimizations --- crates/bevy_pbr/src/material.rs | 24 +- crates/bevy_pbr/src/render/light.rs | 9 +- crates/bevy_pbr/src/wireframe.rs | 13 +- crates/bevy_render/src/lib.rs | 14 +- crates/bevy_render/src/render_phase/draw.rs | 9 +- crates/bevy_render/src/render_phase/mod.rs | 32 ++- .../src/render_resource/pipeline_cache.rs | 74 ++++-- .../render_resource/pipeline_specializer.rs | 16 +- crates/bevy_sprite/src/mesh2d/material.rs | 11 +- crates/bevy_sprite/src/render/mod.rs | 234 +++++++++--------- crates/bevy_ui/src/render/mod.rs | 64 ++--- examples/2d/mesh2d_manual.rs | 2 +- examples/shader/animate_shader.rs | 6 +- examples/shader/shader_defs.rs | 6 +- examples/shader/shader_instancing.rs | 6 +- 15 files changed, 308 insertions(+), 212 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 085a5efa1ea1c..b0aa4c0ba0389 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -23,7 +23,7 @@ use bevy_render::{ SetItemPipeline, TrackedRenderPass, }, render_resource::{ - BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader, + BindGroup, BindGroupLayout, LockablePipelineCache, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, @@ -330,7 +330,7 @@ pub fn queue_material_meshes( transparent_draw_functions: Res>, material_pipeline: Res>, mut pipelines: ResMut>>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, msaa: Res, render_meshes: Res>, render_materials: Res>, @@ -358,6 +358,14 @@ pub fn queue_material_meshes( .get_id::>() .unwrap(); + let opaque_phase_cell = opaque_phase.get(); + let alpha_mask_phase_cell = alpha_mask_phase.get(); + let transparent_phase_cell = transparent_phase.get(); + + let mut opaque_phase_queue = opaque_phase_cell.take(); + let mut alpha_mask_phase_queue = alpha_mask_phase_cell.take(); + let mut transparent_phase_queue = transparent_phase_cell.take(); + let inverse_view_matrix = view.transform.compute_matrix().inverse(); let inverse_view_row_2 = inverse_view_matrix.row(2); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); @@ -379,7 +387,7 @@ pub fn queue_material_meshes( let material_key = M::key(material); let pipeline_id = pipelines.specialize( - &mut pipeline_cache, + &pipeline_cache, &material_pipeline, MaterialPipelineKey { mesh_key, @@ -401,7 +409,7 @@ pub fn queue_material_meshes( let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3)) + bias; match alpha_mode { AlphaMode::Opaque => { - opaque_phase.add(Opaque3d { + opaque_phase_queue.push(Opaque3d { entity: *visible_entity, draw_function: draw_opaque_pbr, pipeline: pipeline_id, @@ -413,7 +421,7 @@ pub fn queue_material_meshes( }); } AlphaMode::Mask(_) => { - alpha_mask_phase.add(AlphaMask3d { + alpha_mask_phase_queue.push(AlphaMask3d { entity: *visible_entity, draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, @@ -425,7 +433,7 @@ pub fn queue_material_meshes( }); } AlphaMode::Blend => { - transparent_phase.add(Transparent3d { + transparent_phase_queue.push(Transparent3d { entity: *visible_entity, draw_function: draw_transparent_pbr, pipeline: pipeline_id, @@ -441,5 +449,9 @@ pub fn queue_material_meshes( } } } + + opaque_phase_cell.set(opaque_phase_queue); + alpha_mask_phase_cell.set(alpha_mask_phase_queue); + transparent_phase_cell.set(transparent_phase_queue); } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 2510159f7d54b..d670b8d296135 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1341,7 +1341,7 @@ pub fn queue_shadows( casting_meshes: Query<&Handle, Without>, render_meshes: Res>, mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, view_lights: Query<&ViewLightEntities>, view_light_shadow_phases: Query<(&LightEntity, &RenderPhase)>, point_light_entities: Query<&CubemapVisibleEntities, With>, @@ -1355,6 +1355,8 @@ pub fn queue_shadows( for view_light_entity in view_lights.lights.iter().copied() { let (light_entity, shadow_phase) = view_light_shadow_phases.get(view_light_entity).unwrap(); + let phase_cell = shadow_phase.get(); + let mut phase_queue = phase_cell.take(); let visible_entities = match light_entity { LightEntity::Directional { light_entity } => directional_light_entities .get(*light_entity) @@ -1375,7 +1377,7 @@ pub fn queue_shadows( let key = ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology); let pipeline_id = pipelines.specialize( - &mut pipeline_cache, + &pipeline_cache, &shadow_pipeline, key, &mesh.layout, @@ -1389,7 +1391,7 @@ pub fn queue_shadows( } }; - shadow_phase.add(Shadow { + phase_queue.push(Shadow { draw_function: draw_shadow_mesh, pipeline: pipeline_id, entity, @@ -1398,6 +1400,7 @@ pub fn queue_shadows( } } } + phase_cell.set(phase_queue); } } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 578d651d3027c..ee7ecf9977540 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -12,8 +12,8 @@ use bevy_render::{ render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ - PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline, - SpecializedMeshPipelineError, SpecializedMeshPipelines, + LockablePipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, view::{ExtractedView, Msaa, VisibleEntities}, RenderApp, RenderStage, @@ -103,7 +103,7 @@ fn queue_wireframes( wireframe_config: Res, wireframe_pipeline: Res, mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, msaa: Res, mut material_meshes: ParamSet<( Query<(Entity, &Handle, &MeshUniform)>, @@ -120,13 +120,15 @@ fn queue_wireframes( let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); + let phase_cell = opaque_phase.get(); + let mut phase_queue = phase_cell.take(); let add_render_phase = |(entity, mesh_handle, mesh_uniform): (Entity, &Handle, &MeshUniform)| { if let Some(mesh) = render_meshes.get(mesh_handle) { let key = msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); let pipeline_id = pipelines.specialize( - &mut pipeline_cache, + &pipeline_cache, &wireframe_pipeline, key, &mesh.layout, @@ -138,7 +140,7 @@ fn queue_wireframes( return; } }; - opaque_phase.add(Opaque3d { + phase_queue.push(Opaque3d { entity, pipeline: pipeline_id, draw_function: draw_custom, @@ -162,6 +164,7 @@ fn queue_wireframes( .filter_map(|visible_entity| query.get(*visible_entity).ok()) .for_each(add_render_phase); } + phase_cell.set(phase_queue); } } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 1dadbf149a6a9..a58de300aa243 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -39,7 +39,9 @@ use crate::{ mesh::MeshPlugin, primitives::{CubemapFrusta, Frustum}, render_graph::RenderGraph, - render_resource::{PipelineCache, Shader, ShaderLoader}, + render_resource::{ + lock_pipeline_cache, unlock_pipeline_cache, PipelineCache, Shader, ShaderLoader, + }, renderer::render_system, texture::ImagePlugin, view::{ViewPlugin, WindowRenderPlugin}, @@ -154,8 +156,9 @@ impl Plugin for RenderPlugin { let asset_server = app.world.resource::().clone(); let mut render_app = App::empty(); - let mut extract_stage = - SystemStage::parallel().with_system(PipelineCache::extract_shaders); + let mut extract_stage = SystemStage::parallel() + .with_system(PipelineCache::extract_shaders) + .with_system(lock_pipeline_cache); // don't apply buffers when the stage finishes running // extract stage runs on the app world, but the buffers are applied to the render world extract_stage.set_apply_buffers(false); @@ -163,7 +166,10 @@ impl Plugin for RenderPlugin { .add_stage(RenderStage::Extract, extract_stage) .add_stage(RenderStage::Prepare, SystemStage::parallel()) .add_stage(RenderStage::Queue, SystemStage::parallel()) - .add_stage(RenderStage::PhaseSort, SystemStage::parallel()) + .add_stage( + RenderStage::PhaseSort, + SystemStage::parallel().with_system(unlock_pipeline_cache), + ) .add_stage( RenderStage::Render, SystemStage::parallel() diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 5ffca0ea25886..3d53c35529c86 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -45,9 +45,9 @@ pub trait PhaseItem: Send + Sync + 'static { } // TODO: make this generic? -/// /// A [`Draw`] function identifier. +/// A [`Draw`] function identifier. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct DrawFunctionId(usize); +pub struct DrawFunctionId(u32); /// Stores all draw functions for the [`PhaseItem`] type. /// For retrieval they are associated with their [`TypeId`]. @@ -64,15 +64,16 @@ impl DrawFunctionsInternal

{ /// Adds the [`Draw`] function and associates it to the type `T` pub fn add_with>(&mut self, draw_function: D) -> DrawFunctionId { + assert!(self.draw_functions.len() <= u32::MAX as usize); + let id = DrawFunctionId(self.draw_functions.len() as u32); self.draw_functions.push(Box::new(draw_function)); - let id = DrawFunctionId(self.draw_functions.len() - 1); self.indices.insert(TypeId::of::(), id); id } /// Retrieves the [`Draw`] function corresponding to the `id` mutably. pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw

> { - self.draw_functions.get_mut(id.0).map(|f| &mut **f) + self.draw_functions.get_mut(id.0 as usize).map(|f| &mut **f) } /// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`. diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 0d64aedb749b9..6af199d00f09d 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -10,6 +10,18 @@ use thread_local::ThreadLocal; use copyless::VecHelper; +pub struct RenderPhaseScope<'a, I: PhaseItem> { + phase_queue: &'a mut Vec, +} + +impl<'a, I: PhaseItem> RenderPhaseScope<'a, I> { + /// Adds a [`PhaseItem`] to this render phase. + #[inline] + pub fn add(&mut self, item: I) { + self.phase_queue.alloc().init(item); + } +} + /// A resource to collect and sort draw requests for specific [`PhaseItems`](PhaseItem). #[derive(Component)] pub struct RenderPhase { @@ -27,13 +39,17 @@ impl Default for RenderPhase { } impl RenderPhase { - /// Adds a [`PhaseItem`] to this render phase. - #[inline] - pub fn add(&self, item: I) { - let cell = self.items.get_or_default(); - let mut queue = cell.take(); - queue.alloc().init(item); - cell.set(queue); + pub fn get(&self) -> &Cell> { + self.items.get_or_default() + } + + pub fn phase_scope(&self, f: impl FnOnce(RenderPhaseScope<'_, I>)) { + let store = self.get(); + let mut phase_queue = store.take(); + f(RenderPhaseScope { + phase_queue: &mut phase_queue, + }); + store.set(phase_queue); } /// Sorts all of its [`PhaseItems`](PhaseItem). @@ -42,7 +58,7 @@ impl RenderPhase { for batch in self.items.iter_mut() { self.sorted.append(batch.get_mut()); } - self.sorted.sort_by_key(|d| d.sort_key()); + self.sorted.sort_unstable_by_key(|d| d.sort_key()); } } diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 559ff745fa314..a46bc260a53ae 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -11,8 +11,10 @@ use crate::{ }; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::event::EventReader; -use bevy_ecs::system::{Res, ResMut}; +use bevy_ecs::system::{Commands, Res, ResMut}; +use bevy_ecs::world::{FromWorld, World}; use bevy_utils::{default, tracing::error, Entry, HashMap, HashSet}; +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{hash::Hash, mem, ops::Deref, sync::Arc}; use thiserror::Error; use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout as RawVertexBufferLayout}; @@ -28,20 +30,20 @@ pub enum Pipeline { ComputePipeline(ComputePipeline), } -type CachedPipelineId = usize; +type CachedPipelineId = u32; #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub struct CachedRenderPipelineId(CachedPipelineId); +pub struct CachedRenderPipelineId(u32); impl CachedRenderPipelineId { - pub const INVALID: Self = CachedRenderPipelineId(usize::MAX); + pub const INVALID: Self = CachedRenderPipelineId(u32::MAX); } #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] pub struct CachedComputePipelineId(CachedPipelineId); impl CachedComputePipelineId { - pub const INVALID: Self = CachedComputePipelineId(usize::MAX); + pub const INVALID: Self = CachedComputePipelineId(u32::MAX); } struct CachedPipeline { @@ -241,6 +243,26 @@ impl LayoutCache { } } +pub struct LockablePipelineCache(RwLock); + +impl LockablePipelineCache { + pub fn read(&self) -> RwLockReadGuard<'_, PipelineCache> { + self.0.read() + } + + pub fn write(&self) -> RwLockWriteGuard<'_, PipelineCache> { + self.0.write() + } +} + +impl FromWorld for LockablePipelineCache { + fn from_world(world: &mut World) -> Self { + Self(RwLock::new( + world.remove_resource::().unwrap(), + )) + } +} + pub struct PipelineCache { layout_cache: LayoutCache, shader_cache: ShaderCache, @@ -249,6 +271,16 @@ pub struct PipelineCache { waiting_pipelines: HashSet, } +impl FromWorld for PipelineCache { + fn from_world(world: &mut World) -> Self { + world + .remove_resource::() + .unwrap() + .0 + .into_inner() + } +} + impl PipelineCache { pub fn new(device: RenderDevice) -> Self { Self { @@ -262,12 +294,12 @@ impl PipelineCache { #[inline] pub fn get_render_pipeline_state(&self, id: CachedRenderPipelineId) -> &CachedPipelineState { - &self.pipelines[id.0].state + &self.pipelines[id.0 as usize].state } #[inline] pub fn get_compute_pipeline_state(&self, id: CachedComputePipelineId) -> &CachedPipelineState { - &self.pipelines[id.0].state + &self.pipelines[id.0 as usize].state } #[inline] @@ -275,7 +307,7 @@ impl PipelineCache { &self, id: CachedRenderPipelineId, ) -> &RenderPipelineDescriptor { - match &self.pipelines[id.0].descriptor { + match &self.pipelines[id.0 as usize].descriptor { PipelineDescriptor::RenderPipelineDescriptor(descriptor) => descriptor, PipelineDescriptor::ComputePipelineDescriptor(_) => unreachable!(), } @@ -286,7 +318,7 @@ impl PipelineCache { &self, id: CachedComputePipelineId, ) -> &ComputePipelineDescriptor { - match &self.pipelines[id.0].descriptor { + match &self.pipelines[id.0 as usize].descriptor { PipelineDescriptor::RenderPipelineDescriptor(_) => unreachable!(), PipelineDescriptor::ComputePipelineDescriptor(descriptor) => descriptor, } @@ -295,7 +327,7 @@ impl PipelineCache { #[inline] pub fn get_render_pipeline(&self, id: CachedRenderPipelineId) -> Option<&RenderPipeline> { if let CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) = - &self.pipelines[id.0].state + &self.pipelines[id.0 as usize].state { Some(pipeline) } else { @@ -306,7 +338,7 @@ impl PipelineCache { #[inline] pub fn get_compute_pipeline(&self, id: CachedComputePipelineId) -> Option<&ComputePipeline> { if let CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) = - &self.pipelines[id.0].state + &self.pipelines[id.0 as usize].state { Some(pipeline) } else { @@ -318,7 +350,8 @@ impl PipelineCache { &mut self, descriptor: RenderPipelineDescriptor, ) -> CachedRenderPipelineId { - let id = CachedRenderPipelineId(self.pipelines.len()); + assert!(self.pipelines.len() < u32::MAX as usize); + let id = CachedRenderPipelineId(self.pipelines.len() as u32); self.pipelines.push(CachedPipeline { descriptor: PipelineDescriptor::RenderPipelineDescriptor(Box::new(descriptor)), state: CachedPipelineState::Queued, @@ -331,7 +364,8 @@ impl PipelineCache { &mut self, descriptor: ComputePipelineDescriptor, ) -> CachedComputePipelineId { - let id = CachedComputePipelineId(self.pipelines.len()); + assert!(self.pipelines.len() < u32::MAX as usize); + let id = CachedComputePipelineId(self.pipelines.len() as u32); self.pipelines.push(CachedPipeline { descriptor: PipelineDescriptor::ComputePipelineDescriptor(Box::new(descriptor)), state: CachedPipelineState::Queued, @@ -343,7 +377,7 @@ impl PipelineCache { fn set_shader(&mut self, handle: &Handle, shader: &Shader) { let pipelines_to_queue = self.shader_cache.set_shader(handle, shader.clone()); for cached_pipeline in pipelines_to_queue { - self.pipelines[cached_pipeline].state = CachedPipelineState::Queued; + self.pipelines[cached_pipeline as usize].state = CachedPipelineState::Queued; self.waiting_pipelines.insert(cached_pipeline); } } @@ -351,7 +385,7 @@ impl PipelineCache { fn remove_shader(&mut self, shader: &Handle) { let pipelines_to_queue = self.shader_cache.remove(shader); for cached_pipeline in pipelines_to_queue { - self.pipelines[cached_pipeline].state = CachedPipelineState::Queued; + self.pipelines[cached_pipeline as usize].state = CachedPipelineState::Queued; self.waiting_pipelines.insert(cached_pipeline); } } @@ -477,7 +511,7 @@ impl PipelineCache { let mut pipelines = mem::take(&mut self.pipelines); for id in waiting_pipelines { - let pipeline = &mut pipelines[id]; + let pipeline = &mut pipelines[id as usize]; match &pipeline.state { CachedPipelineState::Ok(_) => continue, CachedPipelineState::Queued => {} @@ -670,3 +704,11 @@ impl<'a> Iterator for ErrorSources<'a> { current } } + +pub(crate) fn lock_pipeline_cache(mut commands: Commands) { + commands.init_resource::(); +} + +pub(crate) fn unlock_pipeline_cache(mut commands: Commands) { + commands.init_resource::(); +} diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index ee73bf1cf6bff..e5a558cfa4325 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -2,8 +2,8 @@ use crate::render_resource::CachedComputePipelineId; use crate::{ mesh::{InnerMeshVertexBufferLayout, MeshVertexBufferLayout, MissingVertexAttributeError}, render_resource::{ - CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache, RenderPipelineDescriptor, - VertexBufferLayout, + CachedRenderPipelineId, ComputePipelineDescriptor, LockablePipelineCache, + RenderPipelineDescriptor, VertexBufferLayout, }, }; use bevy_utils::{ @@ -31,13 +31,13 @@ impl Default for SpecializedRenderPipelines { impl SpecializedRenderPipelines { pub fn specialize( &mut self, - cache: &mut PipelineCache, + cache: &LockablePipelineCache, specialize_pipeline: &S, key: S::Key, ) -> CachedRenderPipelineId { *self.cache.entry(key.clone()).or_insert_with(|| { let descriptor = specialize_pipeline.specialize(key); - cache.queue_render_pipeline(descriptor) + cache.write().queue_render_pipeline(descriptor) }) } } @@ -60,13 +60,13 @@ impl Default for SpecializedComputePipelines { impl SpecializedComputePipelines { pub fn specialize( &mut self, - cache: &mut PipelineCache, + cache: &LockablePipelineCache, specialize_pipeline: &S, key: S::Key, ) -> CachedComputePipelineId { *self.cache.entry(key.clone()).or_insert_with(|| { let descriptor = specialize_pipeline.specialize(key); - cache.queue_compute_pipeline(descriptor) + cache.write().queue_compute_pipeline(descriptor) }) } } @@ -99,7 +99,7 @@ impl SpecializedMeshPipelines { #[inline] pub fn specialize( &mut self, - cache: &mut PipelineCache, + cache: &LockablePipelineCache, specialize_pipeline: &S, key: S::Key, layout: &MeshVertexBufferLayout, @@ -137,6 +137,7 @@ impl SpecializedMeshPipelines { Ok(*entry.insert(match layout_map.entry(key) { Entry::Occupied(entry) => { if cfg!(debug_assertions) { + let cache = cache.read(); let stored_descriptor = cache.get_render_pipeline_descriptor(*entry.get()); if stored_descriptor != &descriptor { error!("The cached pipeline descriptor for {} is not equal to the generated descriptor for the given key. This means the SpecializePipeline implementation uses 'unused' MeshVertexBufferLayout information to specialize the pipeline. This is not allowed because it would invalidate the pipeline cache.", std::any::type_name::()); @@ -145,6 +146,7 @@ impl SpecializedMeshPipelines { *entry.into_mut() } Entry::Vacant(entry) => { + let mut cache = cache.write(); *entry.insert(cache.queue_render_pipeline(descriptor)) } })) diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index e83430efb7cfa..11f0f5c4ec22a 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -20,7 +20,7 @@ use bevy_render::{ SetItemPipeline, TrackedRenderPass, }, render_resource::{ - BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader, + BindGroup, BindGroupLayout, LockablePipelineCache, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, @@ -298,7 +298,7 @@ pub fn queue_material2d_meshes( transparent_draw_functions: Res>, material2d_pipeline: Res>, mut pipelines: ResMut>>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, render_device: Res, msaa: Res, render_meshes: Res>, @@ -317,6 +317,8 @@ pub fn queue_material2d_meshes( .unwrap(); let msaa_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); + let phase_cell = transparent_phase.get(); + let mut phase_queue = phase_cell.take(); for visible_entity in &visible_entities.entities { if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = @@ -329,7 +331,7 @@ pub fn queue_material2d_meshes( let material_key = M::key(render_device, material2d); let pipeline_id = pipelines.specialize( - &mut pipeline_cache, + &pipeline_cache, &material2d_pipeline, Material2dKey { mesh_key, @@ -347,7 +349,7 @@ pub fn queue_material2d_meshes( }; let mesh_z = mesh2d_uniform.transform.w_axis.z; - transparent_phase.add(Transparent2d { + phase_queue.push(Transparent2d { entity: *visible_entity, draw_function: draw_transparent_pbr, pipeline: pipeline_id, @@ -363,6 +365,7 @@ pub fn queue_material2d_meshes( } } } + phase_cell.set(phase_queue); } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 44bccb00a3dba..cdbb9a122fa92 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -342,7 +342,7 @@ pub fn queue_sprites( view_uniforms: Res, sprite_pipeline: Res, mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, mut image_bind_groups: ResMut, gpu_images: Res>, msaa: Res, @@ -378,9 +378,9 @@ pub fn queue_sprites( let draw_sprite_function = draw_functions.read().get_id::().unwrap(); let key = SpritePipelineKey::from_msaa_samples(msaa.samples); - let pipeline = pipelines.specialize(&mut pipeline_cache, &sprite_pipeline, key); + let pipeline = pipelines.specialize(&pipeline_cache, &sprite_pipeline, key); let colored_pipeline = pipelines.specialize( - &mut pipeline_cache, + &pipeline_cache, &sprite_pipeline, key | SpritePipelineKey::COLORED, ); @@ -416,130 +416,134 @@ pub fn queue_sprites( }; let mut current_batch_entity = Entity::from_raw(u32::MAX); let mut current_image_size = Vec2::ZERO; - // Add a phase item for each sprite, and detect when succesive items can be batched. - // Spawn an entity with a `SpriteBatch` component for each possible batch. - // Compatible items share the same entity. - // Batches are merged later (in `batch_phase_system()`), so that they can be interrupted - // by any other phase item (and they can interrupt other items from batching). - for extracted_sprite in extracted_sprites.iter() { - let new_batch = SpriteBatch { - image_handle_id: extracted_sprite.image_handle_id, - colored: extracted_sprite.color != Color::WHITE, - }; - if new_batch != current_batch { - // Set-up a new possible batch - if let Some(gpu_image) = - gpu_images.get(&Handle::weak(new_batch.image_handle_id)) - { - current_batch = new_batch; - current_image_size = Vec2::new(gpu_image.size.x, gpu_image.size.y); - current_batch_entity = commands.spawn_bundle((current_batch,)).id(); - - image_bind_groups - .values - .entry(Handle::weak(current_batch.image_handle_id)) - .or_insert_with(|| { - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView( - &gpu_image.texture_view, - ), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&gpu_image.sampler), - }, - ], - label: Some("sprite_material_bind_group"), - layout: &sprite_pipeline.material_layout, - }) - }); - } else { - // Skip this item if the texture is not ready - continue; + transparent_phase.phase_scope(|mut phase| { + // Add a phase item for each sprite, and detect when succesive items can be batched. + // Spawn an entity with a `SpriteBatch` component for each possible batch. + // Compatible items share the same entity. + // Batches are merged later (in `batch_phase_system()`), so that they can be interrupted + // by any other phase item (and they can interrupt other items from batching). + for extracted_sprite in extracted_sprites.iter() { + let new_batch = SpriteBatch { + image_handle_id: extracted_sprite.image_handle_id, + colored: extracted_sprite.color != Color::WHITE, + }; + if new_batch != current_batch { + // Set-up a new possible batch + if let Some(gpu_image) = + gpu_images.get(&Handle::weak(new_batch.image_handle_id)) + { + current_batch = new_batch; + current_image_size = Vec2::new(gpu_image.size.x, gpu_image.size.y); + current_batch_entity = commands.spawn_bundle((current_batch,)).id(); + + image_bind_groups + .values + .entry(Handle::weak(current_batch.image_handle_id)) + .or_insert_with(|| { + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView( + &gpu_image.texture_view, + ), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler( + &gpu_image.sampler, + ), + }, + ], + label: Some("sprite_material_bind_group"), + layout: &sprite_pipeline.material_layout, + }) + }); + } else { + // Skip this item if the texture is not ready + continue; + } } - } - // Calculate vertex data for this item + // Calculate vertex data for this item - let mut uvs = QUAD_UVS; - if extracted_sprite.flip_x { - uvs = [uvs[1], uvs[0], uvs[3], uvs[2]]; - } - if extracted_sprite.flip_y { - uvs = [uvs[3], uvs[2], uvs[1], uvs[0]]; - } + let mut uvs = QUAD_UVS; + if extracted_sprite.flip_x { + uvs = [uvs[1], uvs[0], uvs[3], uvs[2]]; + } + if extracted_sprite.flip_y { + uvs = [uvs[3], uvs[2], uvs[1], uvs[0]]; + } - // By default, the size of the quad is the size of the texture - let mut quad_size = current_image_size; + // By default, the size of the quad is the size of the texture + let mut quad_size = current_image_size; - // If a rect is specified, adjust UVs and the size of the quad - if let Some(rect) = extracted_sprite.rect { - let rect_size = rect.size(); - for uv in &mut uvs { - *uv = (rect.min + *uv * rect_size) / current_image_size; + // If a rect is specified, adjust UVs and the size of the quad + if let Some(rect) = extracted_sprite.rect { + let rect_size = rect.size(); + for uv in &mut uvs { + *uv = (rect.min + *uv * rect_size) / current_image_size; + } + quad_size = rect_size; } - quad_size = rect_size; - } - // Override the size if a custom one is specified - if let Some(custom_size) = extracted_sprite.custom_size { - quad_size = custom_size; - } - - // Apply size and global transform - let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| { - extracted_sprite - .transform - .mul_vec3(((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.)) - .into() - }); - - // These items will be sorted by depth with other phase items - let sort_key = FloatOrd(extracted_sprite.transform.translation.z); - - // Store the vertex data and add the item to the render phase - if current_batch.colored { - for i in QUAD_INDICES { - sprite_meta.colored_vertices.push(ColoredSpriteVertex { - position: positions[i], - uv: uvs[i].into(), - color: extracted_sprite.color.as_linear_rgba_f32(), - }); + // Override the size if a custom one is specified + if let Some(custom_size) = extracted_sprite.custom_size { + quad_size = custom_size; } - let item_start = colored_index; - colored_index += QUAD_INDICES.len() as u32; - let item_end = colored_index; - - transparent_phase.add(Transparent2d { - draw_function: draw_sprite_function, - pipeline: colored_pipeline, - entity: current_batch_entity, - sort_key, - batch_range: Some(item_start..item_end), + + // Apply size and global transform + let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| { + extracted_sprite + .transform + .mul_vec3(((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.)) + .into() }); - } else { - for i in QUAD_INDICES { - sprite_meta.vertices.push(SpriteVertex { - position: positions[i], - uv: uvs[i].into(), + + // These items will be sorted by depth with other phase items + let sort_key = FloatOrd(extracted_sprite.transform.translation.z); + + // Store the vertex data and add the item to the render phase + if current_batch.colored { + for i in QUAD_INDICES { + sprite_meta.colored_vertices.push(ColoredSpriteVertex { + position: positions[i], + uv: uvs[i].into(), + color: extracted_sprite.color.as_linear_rgba_f32(), + }); + } + let item_start = colored_index; + colored_index += QUAD_INDICES.len() as u32; + let item_end = colored_index; + + phase.add(Transparent2d { + draw_function: draw_sprite_function, + pipeline: colored_pipeline, + entity: current_batch_entity, + sort_key, + batch_range: Some(item_start..item_end), + }); + } else { + for i in QUAD_INDICES { + sprite_meta.vertices.push(SpriteVertex { + position: positions[i], + uv: uvs[i].into(), + }); + } + let item_start = index; + index += QUAD_INDICES.len() as u32; + let item_end = index; + + phase.add(Transparent2d { + draw_function: draw_sprite_function, + pipeline, + entity: current_batch_entity, + sort_key, + batch_range: Some(item_start..item_end), }); } - let item_start = index; - index += QUAD_INDICES.len() as u32; - let item_end = index; - - transparent_phase.add(Transparent2d { - draw_function: draw_sprite_function, - pipeline, - entity: current_batch_entity, - sort_key, - batch_range: Some(item_start..item_end), - }); } - } + }); } sprite_meta .vertices diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b9d0380ec7827..00ac332af2986 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -397,7 +397,7 @@ pub fn queue_uinodes( view_uniforms: Res, ui_pipeline: Res, mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, mut image_bind_groups: ResMut, gpu_images: Res>, ui_batches: Query<(Entity, &UiBatch)>, @@ -424,37 +424,41 @@ pub fn queue_uinodes( layout: &ui_pipeline.view_layout, })); let draw_ui_function = draw_functions.read().get_id::().unwrap(); - let pipeline = pipelines.specialize(&mut pipeline_cache, &ui_pipeline, UiPipelineKey {}); + let pipeline = pipelines.specialize(&pipeline_cache, &ui_pipeline, UiPipelineKey {}); for transparent_phase in views.iter() { - for (entity, batch) in ui_batches.iter() { - image_bind_groups - .values - .entry(batch.image.clone_weak()) - .or_insert_with(|| { - let gpu_image = gpu_images.get(&batch.image).unwrap(); - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&gpu_image.texture_view), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&gpu_image.sampler), - }, - ], - label: Some("ui_material_bind_group"), - layout: &ui_pipeline.image_layout, - }) + transparent_phase.phase_scope(|mut phase| { + for (entity, batch) in ui_batches.iter() { + image_bind_groups + .values + .entry(batch.image.clone_weak()) + .or_insert_with(|| { + let gpu_image = gpu_images.get(&batch.image).unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView( + &gpu_image.texture_view, + ), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: Some("ui_material_bind_group"), + layout: &ui_pipeline.image_layout, + }) + }); + + phase.add(TransparentUi { + draw_function: draw_ui_function, + pipeline, + entity, + sort_key: FloatOrd(batch.z), }); - - transparent_phase.add(TransparentUi { - draw_function: draw_ui_function, - pipeline, - entity, - sort_key: FloatOrd(batch.z), - }); - } + } + }); } } } diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 72a6cbfd4604b..633422acb9db5 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -302,7 +302,7 @@ pub fn queue_colored_mesh2d( transparent_draw_functions: Res>, colored_mesh2d_pipeline: Res, mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, msaa: Res, render_meshes: Res>, colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dUniform), With>, diff --git a/examples/shader/animate_shader.rs b/examples/shader/animate_shader.rs index 98a5cc6a5ebdb..7d2675d110b18 100644 --- a/examples/shader/animate_shader.rs +++ b/examples/shader/animate_shader.rs @@ -102,10 +102,10 @@ fn queue_custom( custom_pipeline: Res, msaa: Res, mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, render_meshes: Res>, material_meshes: Query<(Entity, &MeshUniform, &Handle), With>, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, + views: Query<(&ExtractedView, &RenderPhase)>, ) { let draw_custom = transparent_3d_draw_functions .read() @@ -115,7 +115,7 @@ fn queue_custom( let key = MeshPipelineKey::from_msaa_samples(msaa.samples) | MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); - for (view, mut transparent_phase) in views.iter_mut() { + for (view, transparent_phase) in views.iter() { let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() { diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index 4997051b0c36a..a711c6e9d310a 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -141,9 +141,9 @@ fn queue_custom( custom_pipeline: Res, msaa: Res, mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, material_meshes: Query<(Entity, &Handle, &MeshUniform, &IsRed)>, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, + views: Query<(&ExtractedView, &RenderPhase)>, ) { let draw_custom = transparent_3d_draw_functions .read() @@ -159,7 +159,7 @@ fn queue_custom( msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); let pipeline = pipelines .specialize( - &mut pipeline_cache, + &pipeline_cache, &custom_pipeline, (*is_red, key), &mesh.layout, diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 52d641ed2bffa..7536255d2ea5a 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -104,13 +104,13 @@ fn queue_custom( custom_pipeline: Res, msaa: Res, mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + pipeline_cache: Res, meshes: Res>, material_meshes: Query< (Entity, &MeshUniform, &Handle), (With>, With), >, - mut views: Query<(&ExtractedView, &mut RenderPhase)>, + views: Query<(&ExtractedView, &RenderPhase)>, ) { let draw_custom = transparent_3d_draw_functions .read() @@ -119,7 +119,7 @@ fn queue_custom( let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); - for (view, mut transparent_phase) in views.iter_mut() { + for (view, mut transparent_phase) in views.iter() { let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() { From f898b622dc7748c21e9c51ec62b2eea9be6e95a8 Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 1 Jun 2022 23:58:10 -0700 Subject: [PATCH 3/7] Use copyless more --- crates/bevy_pbr/Cargo.toml | 1 + crates/bevy_pbr/src/material.rs | 15 ++-- crates/bevy_pbr/src/render/light.rs | 63 ++++++++-------- crates/bevy_pbr/src/wireframe.rs | 87 +++++++++++---------- crates/bevy_sprite/src/mesh2d/material.rs | 92 +++++++++++------------ 5 files changed, 129 insertions(+), 129 deletions(-) diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 37107c44d433a..2ee2fcf1eb506 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -28,3 +28,4 @@ bevy_window = { path = "../bevy_window", version = "0.8.0-dev" } bitflags = "1.2" # direct dependency required for derive macro bytemuck = { version = "1", features = ["derive"] } +copyless = "0.1" diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index b0aa4c0ba0389..a888ec5332565 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -31,6 +31,7 @@ use bevy_render::{ RenderApp, RenderStage, }; use bevy_utils::tracing::error; +use copyless::VecHelper; use std::hash::Hash; use std::marker::PhantomData; @@ -358,6 +359,10 @@ pub fn queue_material_meshes( .get_id::>() .unwrap(); + let inverse_view_matrix = view.transform.compute_matrix().inverse(); + let inverse_view_row_2 = inverse_view_matrix.row(2); + let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); + let opaque_phase_cell = opaque_phase.get(); let alpha_mask_phase_cell = alpha_mask_phase.get(); let transparent_phase_cell = transparent_phase.get(); @@ -366,10 +371,6 @@ pub fn queue_material_meshes( let mut alpha_mask_phase_queue = alpha_mask_phase_cell.take(); let mut transparent_phase_queue = transparent_phase_cell.take(); - let inverse_view_matrix = view.transform.compute_matrix().inverse(); - let inverse_view_row_2 = inverse_view_matrix.row(2); - let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); - for visible_entity in &visible_entities.entities { if let Ok((material_handle, mesh_handle, mesh_uniform)) = material_meshes.get(*visible_entity) @@ -409,7 +410,7 @@ pub fn queue_material_meshes( let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3)) + bias; match alpha_mode { AlphaMode::Opaque => { - opaque_phase_queue.push(Opaque3d { + opaque_phase_queue.alloc().init(Opaque3d { entity: *visible_entity, draw_function: draw_opaque_pbr, pipeline: pipeline_id, @@ -421,7 +422,7 @@ pub fn queue_material_meshes( }); } AlphaMode::Mask(_) => { - alpha_mask_phase_queue.push(AlphaMask3d { + alpha_mask_phase_queue.alloc().init(AlphaMask3d { entity: *visible_entity, draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, @@ -433,7 +434,7 @@ pub fn queue_material_meshes( }); } AlphaMode::Blend => { - transparent_phase_queue.push(Transparent3d { + transparent_phase_queue.alloc().init(Transparent3d { entity: *visible_entity, draw_function: draw_transparent_pbr, pipeline: pipeline_id, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index d670b8d296135..4b80a41de31af 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1355,8 +1355,6 @@ pub fn queue_shadows( for view_light_entity in view_lights.lights.iter().copied() { let (light_entity, shadow_phase) = view_light_shadow_phases.get(view_light_entity).unwrap(); - let phase_cell = shadow_phase.get(); - let mut phase_queue = phase_cell.take(); let visible_entities = match light_entity { LightEntity::Directional { light_entity } => directional_light_entities .get(*light_entity) @@ -1369,38 +1367,39 @@ pub fn queue_shadows( .expect("Failed to get point light visible entities") .get(*face_index), }; - // NOTE: Lights with shadow mapping disabled will have no visible entities - // so no meshes will be queued - for entity in visible_entities.iter().copied() { - if let Ok(mesh_handle) = casting_meshes.get(entity) { - if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = - ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &shadow_pipeline, - key, - &mesh.layout, - ); - - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - continue; - } - }; - - phase_queue.push(Shadow { - draw_function: draw_shadow_mesh, - pipeline: pipeline_id, - entity, - distance: 0.0, // TODO: sort back-to-front - }); + shadow_phase.phase_scope(|mut phase| { + // NOTE: Lights with shadow mapping disabled will have no visible entities + // so no meshes will be queued + for entity in visible_entities.iter().copied() { + if let Ok(mesh_handle) = casting_meshes.get(entity) { + if let Some(mesh) = render_meshes.get(mesh_handle) { + let key = + ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline_id = pipelines.specialize( + &pipeline_cache, + &shadow_pipeline, + key, + &mesh.layout, + ); + + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + phase.add(Shadow { + draw_function: draw_shadow_mesh, + pipeline: pipeline_id, + entity, + distance: 0.0, // TODO: sort back-to-front + }); + } } } - } - phase_cell.set(phase_queue); + }); } } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index ee7ecf9977540..867da229d7b48 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -120,51 +120,50 @@ fn queue_wireframes( let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); - let phase_cell = opaque_phase.get(); - let mut phase_queue = phase_cell.take(); - let add_render_phase = - |(entity, mesh_handle, mesh_uniform): (Entity, &Handle, &MeshUniform)| { - if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = msaa_key - | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &wireframe_pipeline, - key, - &mesh.layout, - ); - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - return; - } - }; - phase_queue.push(Opaque3d { - entity, - pipeline: pipeline_id, - draw_function: draw_custom, - distance: view_row_2.dot(mesh_uniform.transform.col(3)), - }); - } - }; + opaque_phase.phase_scope(|mut phase| { + let add_render_phase = + |(entity, mesh_handle, mesh_uniform): (Entity, &Handle, &MeshUniform)| { + if let Some(mesh) = render_meshes.get(mesh_handle) { + let key = msaa_key + | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline_id = pipelines.specialize( + &pipeline_cache, + &wireframe_pipeline, + key, + &mesh.layout, + ); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + return; + } + }; + phase.add(Opaque3d { + entity, + pipeline: pipeline_id, + draw_function: draw_custom, + distance: view_row_2.dot(mesh_uniform.transform.col(3)), + }); + } + }; - if wireframe_config.global { - let query = material_meshes.p0(); - visible_entities - .entities - .iter() - .filter_map(|visible_entity| query.get(*visible_entity).ok()) - .for_each(add_render_phase); - } else { - let query = material_meshes.p1(); - visible_entities - .entities - .iter() - .filter_map(|visible_entity| query.get(*visible_entity).ok()) - .for_each(add_render_phase); - } - phase_cell.set(phase_queue); + if wireframe_config.global { + let query = material_meshes.p0(); + visible_entities + .entities + .iter() + .filter_map(|visible_entity| query.get(*visible_entity).ok()) + .for_each(add_render_phase); + } else { + let query = material_meshes.p1(); + visible_entities + .entities + .iter() + .filter_map(|visible_entity| query.get(*visible_entity).ok()) + .for_each(add_render_phase); + } + }); } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 11f0f5c4ec22a..de9a323ba255c 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -317,55 +317,55 @@ pub fn queue_material2d_meshes( .unwrap(); let msaa_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); - let phase_cell = transparent_phase.get(); - let mut phase_queue = phase_cell.take(); - - for visible_entity in &visible_entities.entities { - if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = - material2d_meshes.get(*visible_entity) - { - if let Some(material2d) = render_materials.get(material2d_handle) { - if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { - let mesh_key = msaa_key - | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); - - let material_key = M::key(render_device, material2d); - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &material2d_pipeline, - Material2dKey { - mesh_key, - material_key, - }, - &mesh.layout, - ); - - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - continue; - } - }; - - let mesh_z = mesh2d_uniform.transform.w_axis.z; - phase_queue.push(Transparent2d { - entity: *visible_entity, - draw_function: draw_transparent_pbr, - pipeline: pipeline_id, - // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the - // lowest sort key and getting closer should increase. As we have - // -z in front of the camera, the largest distance is -far with values increasing toward the - // camera. As such we can just use mesh_z as the distance - sort_key: FloatOrd(mesh_z), - // This material is not batched - batch_range: None, - }); + transparent_phase.phase_scope(|mut phase| { + for visible_entity in &visible_entities.entities { + if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = + material2d_meshes.get(*visible_entity) + { + if let Some(material2d) = render_materials.get(material2d_handle) { + if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { + let mesh_key = msaa_key + | Mesh2dPipelineKey::from_primitive_topology( + mesh.primitive_topology, + ); + + let material_key = M::key(render_device, material2d); + let pipeline_id = pipelines.specialize( + &pipeline_cache, + &material2d_pipeline, + Material2dKey { + mesh_key, + material_key, + }, + &mesh.layout, + ); + + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + let mesh_z = mesh2d_uniform.transform.w_axis.z; + phase.add(Transparent2d { + entity: *visible_entity, + draw_function: draw_transparent_pbr, + pipeline: pipeline_id, + // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the + // lowest sort key and getting closer should increase. As we have + // -z in front of the camera, the largest distance is -far with values increasing toward the + // camera. As such we can just use mesh_z as the distance + sort_key: FloatOrd(mesh_z), + // This material is not batched + batch_range: None, + }); + } } } } - } - phase_cell.set(phase_queue); + }); } } From cb4680c715dd14f3f31a9b0870415688b9c66010 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 9 Jun 2022 05:09:14 -0700 Subject: [PATCH 4/7] Add ALLOWS_UNSTABLE_SORT to allow each phase to choose how to sort --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 3 +++ crates/bevy_core_pipeline/src/core_3d/mod.rs | 6 ++++++ crates/bevy_pbr/src/render/light.rs | 3 +++ crates/bevy_render/src/render_phase/draw.rs | 4 ++++ crates/bevy_render/src/render_phase/mod.rs | 6 +++++- 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 8acdb42da54ef..0e7bdcf239ded 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -79,6 +79,9 @@ pub struct Transparent2d { } impl PhaseItem for Transparent2d { + /// Transparent sprite rendering is reliant on stable sorting for proper + /// batching as the sort key does not include the Entity, and only the z-coordinate. + const ALLOWS_UNSTABLE_SORT: bool = false; type SortKey = FloatOrd; #[inline] diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 6342d8471291b..e4bb81de16adc 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -87,6 +87,8 @@ pub struct Opaque3d { } impl PhaseItem for Opaque3d { + /// Opaque rendering is not reliant on the stable sort order. + const ALLOWS_UNSTABLE_SORT: bool = true; type SortKey = FloatOrd; #[inline] @@ -122,6 +124,8 @@ pub struct AlphaMask3d { } impl PhaseItem for AlphaMask3d { + /// Alpha mask rendering is not reliant on the stable sort order. + const ALLOWS_UNSTABLE_SORT: bool = true; type SortKey = FloatOrd; #[inline] @@ -157,6 +161,8 @@ pub struct Transparent3d { } impl PhaseItem for Transparent3d { + /// 3D transparent rendering is not reliant on the stable sort order. + const ALLOWS_UNSTABLE_SORT: bool = true; type SortKey = FloatOrd; #[inline] diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 1c40eee6b395d..6bfb63c4176a0 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1424,6 +1424,9 @@ pub struct Shadow { } impl PhaseItem for Shadow { + /// Shadow rendering is not batched, thus not reliant on a stable sort + /// during the sort phase. + const ALLOWS_UNSTABLE_SORT: bool = true; type SortKey = FloatOrd; #[inline] diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 3d53c35529c86..5a93094222967 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -36,6 +36,10 @@ pub trait Draw: Send + Sync + 'static { /// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) stage and /// [`RenderStage::Render`](crate::RenderStage::Render) stage, respectively. pub trait PhaseItem: Send + Sync + 'static { + /// If set to true, the sort phase will use an unstable sort instead of a stable one + /// which can result in higher performance. Enable only if rendering correctness does + /// not strictly rely on relative sort order. + const ALLOWS_UNSTABLE_SORT: bool; /// The type used for ordering the items. The smallest values are drawn first. type SortKey: Ord; /// Determines the order in which the items are drawn during the corresponding [`RenderPhase`](super::RenderPhase). diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 6af199d00f09d..2fa47d8a9622e 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -58,7 +58,11 @@ impl RenderPhase { for batch in self.items.iter_mut() { self.sorted.append(batch.get_mut()); } - self.sorted.sort_unstable_by_key(|d| d.sort_key()); + if I::ALLOWS_UNSTABLE_SORT { + self.sorted.sort_unstable_by_key(|d| d.sort_key()); + } else { + self.sorted.sort_by_key(|d| d.sort_key()); + } } } From c6b54ffd0b72f82f62601a4febfce586e9bf2469 Mon Sep 17 00:00:00 2001 From: James Liu Date: Thu, 9 Jun 2022 18:18:11 -0700 Subject: [PATCH 5/7] Pre-reserve the length of sorted to avoid reallocations Co-authored-by: Giacomo Stevanato --- crates/bevy_render/src/render_phase/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 2fa47d8a9622e..78c92d825e195 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -55,6 +55,8 @@ impl RenderPhase { /// Sorts all of its [`PhaseItems`](PhaseItem). pub fn sort(&mut self) { debug_assert!(self.sorted.is_empty()); + let length = self.items.iter_mut().map(|batch| batch.get_mut().len()).sum(); + self.sorted.reserve(length); for batch in self.items.iter_mut() { self.sorted.append(batch.get_mut()); } From dafb2ef7cb1ff8830b78726e8692c0368c5e3fa7 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 26 Jun 2022 17:01:08 -0700 Subject: [PATCH 6/7] Fix build --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 3 --- crates/bevy_core_pipeline/src/core_3d/mod.rs | 6 ------ crates/bevy_pbr/src/render/light.rs | 3 --- crates/bevy_render/Cargo.toml | 1 - crates/bevy_render/src/render_phase/mod.rs | 8 +++++++- crates/bevy_render/src/render_resource/pipeline_cache.rs | 2 +- 6 files changed, 8 insertions(+), 15 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 4a99121656314..4e1e8098e93c9 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -79,9 +79,6 @@ pub struct Transparent2d { } impl PhaseItem for Transparent2d { - /// Transparent sprite rendering is reliant on stable sorting for proper - /// batching as the sort key does not include the Entity, and only the z-coordinate. - const ALLOWS_UNSTABLE_SORT: bool = false; type SortKey = FloatOrd; #[inline] diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index ab0ac149a9367..e3b230a1e7e20 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -87,8 +87,6 @@ pub struct Opaque3d { } impl PhaseItem for Opaque3d { - /// Opaque rendering is not reliant on the stable sort order. - const ALLOWS_UNSTABLE_SORT: bool = true; type SortKey = FloatOrd; #[inline] @@ -129,8 +127,6 @@ pub struct AlphaMask3d { } impl PhaseItem for AlphaMask3d { - /// Alpha mask rendering is not reliant on the stable sort order. - const ALLOWS_UNSTABLE_SORT: bool = true; type SortKey = FloatOrd; #[inline] @@ -171,8 +167,6 @@ pub struct Transparent3d { } impl PhaseItem for Transparent3d { - /// 3D transparent rendering is not reliant on the stable sort order. - const ALLOWS_UNSTABLE_SORT: bool = true; type SortKey = FloatOrd; #[inline] diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 3abd859636fe9..47034060585f3 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1412,9 +1412,6 @@ pub struct Shadow { } impl PhaseItem for Shadow { - /// Shadow rendering is not batched, thus not reliant on a stable sort - /// during the sort phase. - const ALLOWS_UNSTABLE_SORT: bool = true; type SortKey = FloatOrd; #[inline] diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index c84d98d4c41b2..879dbbbf9af9d 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -71,4 +71,3 @@ ruzstd = { version = "0.2.4", optional = true } # For transcoding of UASTC/ETC1S universal formats, and for .basis file support basis-universal = { version = "0.2.0", optional = true } encase = { version = "0.2", features = ["glam"] } -thread_local = "1.1" diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index eff8236a53075..3fcdc258ae398 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -54,7 +54,13 @@ impl RenderPhase { /// Sorts all of its [`PhaseItems`](PhaseItem). pub fn sort(&mut self) { - I::sort(&mut self.items); + self.sorted.clear(); + self.sorted + .reserve(self.items.iter_mut().sum(|queue| queue.get_mut().len())); + for queue in self.items.iter_mut() { + self.sorted.append(queue.get_mut()); + } + I::sort(&mut self.sorted); } } diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index e6641a2a25563..ef9d1df609f52 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -735,4 +735,4 @@ pub(crate) fn lock_pipeline_cache(mut commands: Commands) { pub(crate) fn unlock_pipeline_cache(mut commands: Commands) { commands.init_resource::(); -} \ No newline at end of file +} From 59bdf9ccfae1def753b33acc6b034adf591d62a7 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 26 Jun 2022 19:40:45 -0700 Subject: [PATCH 7/7] Formatting --- crates/bevy_render/src/render_phase/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 3fcdc258ae398..2696849911627 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -55,8 +55,12 @@ impl RenderPhase { /// Sorts all of its [`PhaseItems`](PhaseItem). pub fn sort(&mut self) { self.sorted.clear(); - self.sorted - .reserve(self.items.iter_mut().sum(|queue| queue.get_mut().len())); + self.sorted.reserve( + self.items + .iter_mut() + .map(|queue| queue.get_mut().len()) + .sum(), + ); for queue in self.items.iter_mut() { self.sorted.append(queue.get_mut()); }