From 65ec025d70a85eb4f1f385d9082c206aa5576d01 Mon Sep 17 00:00:00 2001 From: Ku95 Date: Fri, 18 Feb 2022 13:22:08 +0100 Subject: [PATCH 1/5] refactored the pipeline cache to accommodate both render and compute pipelines --- crates/bevy_pbr/src/lib.rs | 4 +- crates/bevy_pbr/src/material.rs | 12 +- crates/bevy_pbr/src/render/light.rs | 6 +- crates/bevy_pbr/src/render/mesh.rs | 2 +- crates/bevy_pbr/src/wireframe.rs | 12 +- crates/bevy_render/src/lib.rs | 8 +- crates/bevy_render/src/render_phase/draw.rs | 9 +- crates/bevy_render/src/render_resource/mod.rs | 14 +- .../src/render_resource/pipeline.rs | 13 + .../src/render_resource/pipeline_cache.rs | 376 +++++++++++------- .../render_resource/pipeline_specializer.rs | 47 ++- crates/bevy_sprite/src/lib.rs | 4 +- crates/bevy_sprite/src/mesh2d/material.rs | 12 +- crates/bevy_sprite/src/mesh2d/mesh.rs | 4 +- crates/bevy_sprite/src/render/mod.rs | 6 +- crates/bevy_ui/src/render/mod.rs | 6 +- crates/bevy_ui/src/render/pipeline.rs | 2 +- examples/2d/mesh2d_manual.rs | 15 +- examples/shader/animate_shader.rs | 8 +- .../shader/compute_shader_game_of_life.rs | 176 +++++--- examples/shader/shader_defs.rs | 12 +- examples/shader/shader_instancing.rs | 8 +- 22 files changed, 477 insertions(+), 279 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 8325030519b98..acc01514db321 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -40,7 +40,7 @@ use bevy_render::{ prelude::Color, render_graph::RenderGraph, render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, - render_resource::{Shader, SpecializedPipelines}, + render_resource::{Shader, SpecializedRenderPipelines}, view::VisibilitySystems, RenderApp, RenderStage, }; @@ -178,7 +178,7 @@ impl Plugin for PbrPlugin { .init_resource::>() .init_resource::() .init_resource::() - .init_resource::>(); + .init_resource::>(); let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); render_app.add_render_command::(); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index d9ee95446a887..774cb0ea9241d 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -23,8 +23,8 @@ use bevy_render::{ SetItemPipeline, TrackedRenderPass, }, render_resource::{ - BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, - SpecializedPipeline, SpecializedPipelines, + BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader, + SpecializedRenderPipeline, SpecializedRenderPipelines, }, renderer::RenderDevice, view::{ExtractedView, Msaa, VisibleEntities}, @@ -188,7 +188,7 @@ impl Plugin for MaterialPlugin { .add_render_command::>() .add_render_command::>() .init_resource::>() - .init_resource::>>() + .init_resource::>>() .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); } } @@ -202,7 +202,7 @@ pub struct MaterialPipeline { marker: PhantomData, } -impl SpecializedPipeline for MaterialPipeline { +impl SpecializedRenderPipeline for MaterialPipeline { type Key = (MeshPipelineKey, M::Key); fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { @@ -275,8 +275,8 @@ pub fn queue_material_meshes( alpha_mask_draw_functions: Res>, transparent_draw_functions: Res>, material_pipeline: Res>, - mut pipelines: ResMut>>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>>, + mut pipeline_cache: ResMut, msaa: Res, render_meshes: Res>, render_materials: Res>, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index cc606057f2944..a6bd7937503b7 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -244,7 +244,7 @@ impl ShadowPipelineKey { } } -impl SpecializedPipeline for ShadowPipeline { +impl SpecializedRenderPipeline for ShadowPipeline { type Key = ShadowPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { @@ -1090,8 +1090,8 @@ pub fn queue_shadows( shadow_pipeline: Res, casting_meshes: Query<&Handle, Without>, render_meshes: Res>, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, view_lights: Query<&ViewLightEntities>, mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase)>, point_light_entities: Query<&CubemapVisibleEntities, With>, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3e8965aa28ee1..4df9d70c93b86 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -411,7 +411,7 @@ impl MeshPipelineKey { } } -impl SpecializedPipeline for MeshPipeline { +impl SpecializedRenderPipeline for MeshPipeline { type Key = MeshPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 9f1308179e518..ad1849d121dfa 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -10,7 +10,9 @@ use bevy_render::{ mesh::Mesh, render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, - render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines}, + render_resource::{ + PipelineCache, Shader, SpecializedRenderPipeline, SpecializedRenderPipelines, + }, view::{ExtractedView, Msaa}, RenderApp, RenderStage, }; @@ -35,7 +37,7 @@ impl Plugin for WireframePlugin { render_app .add_render_command::() .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_wireframes) .add_system_to_stage(RenderStage::Extract, extract_wireframe_config) .add_system_to_stage(RenderStage::Queue, queue_wireframes); @@ -79,7 +81,7 @@ impl FromWorld for WireframePipeline { } } -impl SpecializedPipeline for WireframePipeline { +impl SpecializedRenderPipeline for WireframePipeline { type Key = MeshPipelineKey; fn specialize(&self, key: Self::Key) -> bevy_render::render_resource::RenderPipelineDescriptor { @@ -98,8 +100,8 @@ fn queue_wireframes( render_meshes: Res>, wireframe_config: Res, wireframe_pipeline: Res, - mut pipeline_cache: ResMut, - mut specialized_pipelines: ResMut>, + mut pipeline_cache: ResMut, + mut specialized_pipelines: ResMut>, msaa: Res, mut material_meshes: QuerySet<( QueryState<(Entity, &Handle, &MeshUniform)>, diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index d44cb38ee8b8e..5446b913a8f64 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -36,7 +36,7 @@ use crate::{ mesh::MeshPlugin, primitives::{CubemapFrusta, Frustum}, render_graph::RenderGraph, - render_resource::{RenderPipelineCache, Shader, ShaderLoader}, + render_resource::{PipelineCache, Shader, ShaderLoader}, renderer::render_system, texture::ImagePlugin, view::{ViewPlugin, WindowRenderPlugin}, @@ -145,12 +145,12 @@ impl Plugin for RenderPlugin { .init_resource::() .register_type::() .register_type::(); - let render_pipeline_cache = RenderPipelineCache::new(device.clone()); + let render_pipeline_cache = PipelineCache::new(device.clone()); let asset_server = app.world.get_resource::().unwrap().clone(); let mut render_app = App::empty(); let mut extract_stage = - SystemStage::parallel().with_system(RenderPipelineCache::extract_shaders); + SystemStage::parallel().with_system(PipelineCache::extract_shaders); // 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); @@ -162,7 +162,7 @@ impl Plugin for RenderPlugin { .add_stage( RenderStage::Render, SystemStage::parallel() - .with_system(RenderPipelineCache::process_pipeline_queue_system) + .with_system(PipelineCache::process_pipeline_queue_system) .with_system(render_system.exclusive_system().at_end()), ) .add_stage(RenderStage::Cleanup, SystemStage::parallel()) diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 73628316f38d0..ff5d04e299927 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -1,6 +1,6 @@ use crate::{ render_phase::TrackedRenderPass, - render_resource::{CachedPipelineId, RenderPipelineCache}, + render_resource::{CachedPipelineId, PipelineCache}, }; use bevy_app::App; use bevy_ecs::{ @@ -225,7 +225,7 @@ impl RenderCommand

for E { pub struct SetItemPipeline; impl RenderCommand

for SetItemPipeline { - type Param = SRes; + type Param = SRes; #[inline] fn render<'w>( _view: Entity, @@ -233,7 +233,10 @@ impl RenderCommand

for SetItemPipeline { pipeline_cache: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - if let Some(pipeline) = pipeline_cache.into_inner().get(item.cached_pipeline()) { + if let Some(pipeline) = pipeline_cache + .into_inner() + .get_render_pipeline(item.cached_pipeline()) + { pass.set_render_pipeline(pipeline); RenderCommandResult::Success } else { diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 8089199f790da..ec69680caf2d1 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -26,13 +26,13 @@ pub use wgpu::{ BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBinding, BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites, - CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePassDescriptor, - ComputePipelineDescriptor, DepthBiasState, DepthStencilState, Extent3d, Face, - Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState, FrontFace, - ImageCopyBuffer, ImageCopyBufferBase, ImageCopyTexture, ImageCopyTextureBase, ImageDataLayout, - ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, MapMode, MultisampleState, - Operations, Origin3d, PipelineLayout, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, - PrimitiveTopology, RenderPassColorAttachment, RenderPassDepthStencilAttachment, + CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePass, ComputePassDescriptor, + ComputePipelineDescriptor as RawComputePipelineDescriptor, DepthBiasState, DepthStencilState, + Extent3d, Face, Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState, + FrontFace, ImageCopyBuffer, ImageCopyBufferBase, ImageCopyTexture, ImageCopyTextureBase, + ImageDataLayout, ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, MapMode, + MultisampleState, Operations, Origin3d, PipelineLayout, PipelineLayoutDescriptor, PolygonMode, + PrimitiveState, PrimitiveTopology, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipelineDescriptor as RawRenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index d3bd820053fdc..ab57c62cd0689 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -140,3 +140,16 @@ pub struct FragmentState { /// The color state of the render targets. pub targets: Vec, } + +/// Describes a compute pipeline. +#[derive(Clone, Debug)] +pub struct ComputePipelineDescriptor { + pub label: Option>, + pub layout: Option>, + /// The compiled shader module for this stage. + pub shader: Handle, + pub shader_defs: Vec, + /// The name of the entry point in the compiled shader. There must be a + /// function with this name in the shader. + pub entry_point: Cow<'static, str>, +} diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 77ed782eb61cb..618b36479158c 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -1,8 +1,10 @@ +use crate::render_resource::{ComputePipeline, RawComputePipelineDescriptor}; use crate::{ render_resource::{ - AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ProcessShaderError, - RawFragmentState, RawRenderPipelineDescriptor, RawVertexState, RenderPipeline, - RenderPipelineDescriptor, Shader, ShaderImport, ShaderProcessor, ShaderReflectError, + AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ComputePipelineDescriptor, + ProcessShaderError, ProcessedShader, RawFragmentState, RawRenderPipelineDescriptor, + RawVertexState, RenderPipeline, RenderPipelineDescriptor, Shader, ShaderImport, + ShaderProcessor, ShaderReflectError, }, renderer::RenderDevice, RenderWorld, @@ -15,14 +17,15 @@ use std::{hash::Hash, ops::Deref, sync::Arc}; use thiserror::Error; use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout}; -use super::ProcessedShader; +enum PipelineDescriptor { + RenderPipelineDescriptor(RenderPipelineDescriptor), + ComputePipelineDescriptor(ComputePipelineDescriptor), +} -#[derive(Default)] -pub struct ShaderData { - pipelines: HashSet, - processed_shaders: HashMap, Arc>, - resolved_imports: HashMap>, - dependents: HashSet>, +#[derive(Debug)] +pub enum Pipeline { + RenderPipeline(RenderPipeline), + ComputePipeline(ComputePipeline), } #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] @@ -32,6 +35,38 @@ impl CachedPipelineId { pub const INVALID: Self = CachedPipelineId(usize::MAX); } +struct CachedPipeline { + descriptor: PipelineDescriptor, + state: CachedPipelineState, +} + +#[derive(Debug)] +pub enum CachedPipelineState { + Queued, + Ok(Pipeline), + Err(PipelineCacheError), +} + +impl CachedPipelineState { + pub fn unwrap(&self) -> &Pipeline { + match self { + CachedPipelineState::Ok(pipeline) => pipeline, + CachedPipelineState::Queued => { + panic!("Pipeline has not been compiled yet. It is still in the 'Queued' state.") + } + CachedPipelineState::Err(err) => panic!("{}", err), + } + } +} + +#[derive(Default)] +pub struct ShaderData { + pipelines: HashSet, + processed_shaders: HashMap, Arc>, + resolved_imports: HashMap>, + dependents: HashSet>, +} + #[derive(Default)] struct ShaderCache { data: HashMap, ShaderData>, @@ -48,11 +83,11 @@ impl ShaderCache { pipeline: CachedPipelineId, handle: &Handle, shader_defs: &[String], - ) -> Result, RenderPipelineError> { + ) -> Result, PipelineCacheError> { let shader = self .shaders .get(handle) - .ok_or_else(|| RenderPipelineError::ShaderNotLoaded(handle.clone_weak()))?; + .ok_or_else(|| PipelineCacheError::ShaderNotLoaded(handle.clone_weak()))?; let data = self.data.entry(handle.clone_weak()).or_default(); let n_asset_imports = shader .imports() @@ -64,7 +99,7 @@ impl ShaderCache { .filter(|import| matches!(import, ShaderImport::AssetPath(_))) .count(); if n_asset_imports != n_resolved_asset_imports { - return Err(RenderPipelineError::ShaderImportNotYetAvailable); + return Err(PipelineCacheError::ShaderImportNotYetAvailable); } data.pipelines.insert(pipeline); @@ -82,7 +117,7 @@ impl ShaderCache { let module_descriptor = match processed.get_module_descriptor() { Ok(module_descriptor) => module_descriptor, Err(err) => { - return Err(RenderPipelineError::AsModuleDescriptorError(err, processed)); + return Err(PipelineCacheError::AsModuleDescriptorError(err, processed)); } }; entry.insert(Arc::new( @@ -182,7 +217,7 @@ impl LayoutCache { } } -pub struct RenderPipelineCache { +pub struct PipelineCache { layout_cache: LayoutCache, shader_cache: ShaderCache, device: RenderDevice, @@ -190,45 +225,7 @@ pub struct RenderPipelineCache { waiting_pipelines: HashSet, } -struct CachedPipeline { - descriptor: RenderPipelineDescriptor, - state: CachedPipelineState, -} - -#[derive(Debug)] -pub enum CachedPipelineState { - Queued, - Ok(RenderPipeline), - Err(RenderPipelineError), -} - -impl CachedPipelineState { - pub fn unwrap(&self) -> &RenderPipeline { - match self { - CachedPipelineState::Ok(pipeline) => pipeline, - CachedPipelineState::Queued => { - panic!("Pipeline has not been compiled yet. It is still in the 'Queued' state.") - } - CachedPipelineState::Err(err) => panic!("{}", err), - } - } -} - -#[derive(Error, Debug)] -pub enum RenderPipelineError { - #[error( - "Pipeline cound not be compiled because the following shader is not loaded yet: {0:?}" - )] - ShaderNotLoaded(Handle), - #[error(transparent)] - ProcessShaderError(#[from] ProcessShaderError), - #[error("{0}")] - AsModuleDescriptorError(AsModuleDescriptorError, ProcessedShader), - #[error("Shader import not yet available.")] - ShaderImportNotYetAvailable, -} - -impl RenderPipelineCache { +impl PipelineCache { pub fn new(device: RenderDevice) -> Self { Self { device, @@ -245,18 +242,47 @@ impl RenderPipelineCache { } #[inline] - pub fn get(&self, id: CachedPipelineId) -> Option<&RenderPipeline> { - if let CachedPipelineState::Ok(pipeline) = &self.pipelines[id.0].state { + pub fn get_render_pipeline(&self, id: CachedPipelineId) -> Option<&RenderPipeline> { + if let CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) = + &self.pipelines[id.0].state + { + Some(pipeline) + } else { + None + } + } + + #[inline] + pub fn get_compute_pipeline(&self, id: CachedPipelineId) -> Option<&ComputePipeline> { + if let CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) = + &self.pipelines[id.0].state + { Some(pipeline) } else { None } } - pub fn queue(&mut self, descriptor: RenderPipelineDescriptor) -> CachedPipelineId { + pub fn queue_render_pipeline( + &mut self, + descriptor: RenderPipelineDescriptor, + ) -> CachedPipelineId { + let id = CachedPipelineId(self.pipelines.len()); + self.pipelines.push(CachedPipeline { + descriptor: PipelineDescriptor::RenderPipelineDescriptor(descriptor), + state: CachedPipelineState::Queued, + }); + self.waiting_pipelines.insert(id); + id + } + + pub fn queue_compute_pipeline( + &mut self, + descriptor: ComputePipelineDescriptor, + ) -> CachedPipelineId { let id = CachedPipelineId(self.pipelines.len()); self.pipelines.push(CachedPipeline { - descriptor, + descriptor: PipelineDescriptor::ComputePipelineDescriptor(descriptor), state: CachedPipelineState::Queued, }); self.waiting_pipelines.insert(id); @@ -279,23 +305,141 @@ impl RenderPipelineCache { } } + fn process_render_pipeline( + &mut self, + id: CachedPipelineId, + descriptor: &RenderPipelineDescriptor, + ) -> CachedPipelineState { + let vertex_module = match self.shader_cache.get( + &self.device, + id, + &descriptor.vertex.shader, + &descriptor.vertex.shader_defs, + ) { + Ok(module) => module, + Err(err) => { + return CachedPipelineState::Err(err); + } + }; + + let fragment_data = if let Some(fragment) = &descriptor.fragment { + let fragment_module = match self.shader_cache.get( + &self.device, + id, + &fragment.shader, + &fragment.shader_defs, + ) { + Ok(module) => module, + Err(err) => { + return CachedPipelineState::Err(err); + } + }; + Some(( + fragment_module, + fragment.entry_point.deref(), + &fragment.targets, + )) + } else { + None + }; + + let vertex_buffer_layouts = descriptor + .vertex + .buffers + .iter() + .map(|layout| VertexBufferLayout { + array_stride: layout.array_stride, + attributes: &layout.attributes, + step_mode: layout.step_mode, + }) + .collect::>(); + + let layout = if let Some(layout) = &descriptor.layout { + Some(self.layout_cache.get(&self.device, layout)) + } else { + None + }; + + let descriptor = RawRenderPipelineDescriptor { + multiview: None, + depth_stencil: descriptor.depth_stencil.clone(), + label: descriptor.label.as_deref(), + layout, + multisample: descriptor.multisample, + primitive: descriptor.primitive, + vertex: RawVertexState { + buffers: &vertex_buffer_layouts, + entry_point: descriptor.vertex.entry_point.deref(), + module: &vertex_module, + }, + fragment: fragment_data + .as_ref() + .map(|(module, entry_point, targets)| RawFragmentState { + entry_point, + module, + targets, + }), + }; + + let pipeline = self.device.create_render_pipeline(&descriptor); + + CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) + } + + fn process_compute_pipeline( + &mut self, + id: CachedPipelineId, + descriptor: &ComputePipelineDescriptor, + ) -> CachedPipelineState { + let compute_module = match self.shader_cache.get( + &self.device, + id, + &descriptor.shader, + &descriptor.shader_defs, + ) { + Ok(module) => module, + Err(err) => { + return CachedPipelineState::Err(err); + } + }; + + let layout = if let Some(layout) = &descriptor.layout { + Some(self.layout_cache.get(&self.device, layout)) + } else { + None + }; + + let descriptor = RawComputePipelineDescriptor { + label: descriptor.label.as_deref(), + layout, + module: &compute_module, + entry_point: descriptor.entry_point.as_ref(), + }; + + let pipeline = self.device.create_compute_pipeline(&descriptor); + + CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) + } + pub fn process_queue(&mut self) { - let pipelines = std::mem::take(&mut self.waiting_pipelines); - for id in pipelines { - let state = &mut self.pipelines[id.0]; - match &state.state { + let waiting_pipelines = std::mem::take(&mut self.waiting_pipelines); + let mut pipelines = std::mem::take(&mut self.pipelines); + + for id in waiting_pipelines { + let pipeline = &mut pipelines[id.0]; + match &pipeline.state { CachedPipelineState::Ok(_) => continue, CachedPipelineState::Queued => {} CachedPipelineState::Err(err) => { match err { - RenderPipelineError::ShaderNotLoaded(_) - | RenderPipelineError::ShaderImportNotYetAvailable => { /* retry */ } + PipelineCacheError::ShaderNotLoaded(_) + | PipelineCacheError::ShaderImportNotYetAvailable => { /* retry */ } // shader could not be processed ... retrying won't help - RenderPipelineError::ProcessShaderError(err) => { + PipelineCacheError::ProcessShaderError(err) => { error!("failed to process shader: {}", err); continue; } - RenderPipelineError::AsModuleDescriptorError(err, source) => { + PipelineCacheError::AsModuleDescriptorError(err, source) => { log_shader_error(source, err); continue; } @@ -303,85 +447,21 @@ impl RenderPipelineCache { } } - let descriptor = &state.descriptor; - let vertex_module = match self.shader_cache.get( - &self.device, - id, - &descriptor.vertex.shader, - &descriptor.vertex.shader_defs, - ) { - Ok(module) => module, - Err(err) => { - state.state = CachedPipelineState::Err(err); - self.waiting_pipelines.insert(id); - continue; + pipeline.state = match &pipeline.descriptor { + PipelineDescriptor::RenderPipelineDescriptor(descriptor) => { + self.process_render_pipeline(id, descriptor) + } + PipelineDescriptor::ComputePipelineDescriptor(descriptor) => { + self.process_compute_pipeline(id, descriptor) } }; - let fragment_data = if let Some(fragment) = &descriptor.fragment { - let fragment_module = match self.shader_cache.get( - &self.device, - id, - &fragment.shader, - &fragment.shader_defs, - ) { - Ok(module) => module, - Err(err) => { - state.state = CachedPipelineState::Err(err); - self.waiting_pipelines.insert(id); - continue; - } - }; - Some(( - fragment_module, - fragment.entry_point.deref(), - &fragment.targets, - )) - } else { - None - }; - - let vertex_buffer_layouts = descriptor - .vertex - .buffers - .iter() - .map(|layout| VertexBufferLayout { - array_stride: layout.array_stride, - attributes: &layout.attributes, - step_mode: layout.step_mode, - }) - .collect::>(); - - let layout = if let Some(layout) = &descriptor.layout { - Some(self.layout_cache.get(&self.device, layout)) - } else { - None - }; - - let descriptor = RawRenderPipelineDescriptor { - multiview: None, - depth_stencil: descriptor.depth_stencil.clone(), - label: descriptor.label.as_deref(), - layout, - multisample: descriptor.multisample, - primitive: descriptor.primitive, - vertex: RawVertexState { - buffers: &vertex_buffer_layouts, - entry_point: descriptor.vertex.entry_point.deref(), - module: &vertex_module, - }, - fragment: fragment_data - .as_ref() - .map(|(module, entry_point, targets)| RawFragmentState { - entry_point, - module, - targets, - }), - }; - - let pipeline = self.device.create_render_pipeline(&descriptor); - state.state = CachedPipelineState::Ok(pipeline); + if let CachedPipelineState::Err(_) = pipeline.state { + self.waiting_pipelines.insert(id); + } } + + self.pipelines = pipelines; } pub(crate) fn process_pipeline_queue_system(mut cache: ResMut) { @@ -498,6 +578,20 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) { } } +#[derive(Error, Debug)] +pub enum PipelineCacheError { + #[error( + "Pipeline cound not be compiled because the following shader is not loaded yet: {0:?}" + )] + ShaderNotLoaded(Handle), + #[error(transparent)] + ProcessShaderError(#[from] ProcessShaderError), + #[error("{0}")] + AsModuleDescriptorError(AsModuleDescriptorError, ProcessedShader), + #[error("Shader import not yet available.")] + ShaderImportNotYetAvailable, +} + struct ErrorSources<'a> { current: Option<&'a (dyn std::error::Error + 'static)>, } diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index a5cb472aa48f8..e9c69d0255233 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -1,12 +1,14 @@ -use crate::render_resource::{CachedPipelineId, RenderPipelineCache, RenderPipelineDescriptor}; +use crate::render_resource::{ + CachedPipelineId, ComputePipelineDescriptor, PipelineCache, RenderPipelineDescriptor, +}; use bevy_utils::HashMap; use std::hash::Hash; -pub struct SpecializedPipelines { +pub struct SpecializedRenderPipelines { cache: HashMap, } -impl Default for SpecializedPipelines { +impl Default for SpecializedRenderPipelines { fn default() -> Self { Self { cache: Default::default(), @@ -14,21 +16,52 @@ impl Default for SpecializedPipelines { } } -impl SpecializedPipelines { +impl SpecializedRenderPipelines { pub fn specialize( &mut self, - cache: &mut RenderPipelineCache, + cache: &mut PipelineCache, specialize_pipeline: &S, key: S::Key, ) -> CachedPipelineId { *self.cache.entry(key.clone()).or_insert_with(|| { let descriptor = specialize_pipeline.specialize(key); - cache.queue(descriptor) + cache.queue_render_pipeline(descriptor) }) } } -pub trait SpecializedPipeline { +pub trait SpecializedRenderPipeline { type Key: Clone + Hash + PartialEq + Eq; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor; } + +pub struct SpecializedComputePipelines { + cache: HashMap, +} + +impl Default for SpecializedComputePipelines { + fn default() -> Self { + Self { + cache: Default::default(), + } + } +} + +impl SpecializedComputePipelines { + pub fn specialize( + &mut self, + cache: &mut PipelineCache, + specialize_pipeline: &S, + key: S::Key, + ) -> CachedPipelineId { + *self.cache.entry(key.clone()).or_insert_with(|| { + let descriptor = specialize_pipeline.specialize(key); + cache.queue_compute_pipeline(descriptor) + }) + } +} + +pub trait SpecializedComputePipeline { + type Key: Clone + Hash + PartialEq + Eq; + fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor; +} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index eb70848f04e27..41ffbca949b55 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -35,7 +35,7 @@ use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel}; use bevy_reflect::TypeUuid; use bevy_render::{ render_phase::AddRenderCommand, - render_resource::{Shader, SpecializedPipelines}, + render_resource::{Shader, SpecializedRenderPipelines}, RenderApp, RenderStage, }; @@ -64,7 +64,7 @@ impl Plugin for SpritePlugin { render_app .init_resource::() .init_resource::() - .init_resource::>() + .init_resource::>() .init_resource::() .init_resource::() .init_resource::() diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 0e3ceaae55cc0..89a9d028730a1 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -20,8 +20,8 @@ use bevy_render::{ SetItemPipeline, TrackedRenderPass, }, render_resource::{ - BindGroup, BindGroupLayout, RenderPipelineCache, RenderPipelineDescriptor, Shader, - SpecializedPipeline, SpecializedPipelines, + BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader, + SpecializedRenderPipeline, SpecializedRenderPipelines, }, renderer::RenderDevice, view::{ComputedVisibility, Msaa, Visibility, VisibleEntities}, @@ -172,7 +172,7 @@ impl Plugin for Material2dPlugin { render_app .add_render_command::>() .init_resource::>() - .init_resource::>>() + .init_resource::>>() .add_system_to_stage(RenderStage::Queue, queue_material2d_meshes::); } } @@ -186,7 +186,7 @@ pub struct Material2dPipeline { marker: PhantomData, } -impl SpecializedPipeline for Material2dPipeline { +impl SpecializedRenderPipeline for Material2dPipeline { type Key = (Mesh2dPipelineKey, M::Key); fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { @@ -259,8 +259,8 @@ impl EntityRenderCommand pub fn queue_material2d_meshes( transparent_draw_functions: Res>, material2d_pipeline: Res>, - mut pipelines: ResMut>>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>>, + mut pipeline_cache: ResMut, msaa: Res, render_meshes: Res>, render_materials: Res>, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 7468990b47ea8..3b9c00172db6e 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -64,7 +64,7 @@ impl Plugin for Mesh2dRenderPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_mesh2d) .add_system_to_stage(RenderStage::Queue, queue_mesh2d_bind_group) .add_system_to_stage(RenderStage::Queue, queue_mesh2d_view_bind_groups); @@ -276,7 +276,7 @@ impl Mesh2dPipelineKey { } } -impl SpecializedPipeline for Mesh2dPipeline { +impl SpecializedRenderPipeline for Mesh2dPipeline { type Key = Mesh2dPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 4eceaf2bdfc87..a7ac40288f571 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -109,7 +109,7 @@ impl SpritePipelineKey { } } -impl SpecializedPipeline for SpritePipeline { +impl SpecializedRenderPipeline for SpritePipeline { type Key = SpritePipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { @@ -347,8 +347,8 @@ pub fn queue_sprites( mut sprite_meta: ResMut, view_uniforms: Res, sprite_pipeline: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, mut image_bind_groups: ResMut, gpu_images: Res>, msaa: Res, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 947eda82c7e08..3f3962dfbcf79 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -73,7 +73,7 @@ pub fn build_ui_render(app: &mut App) { render_app .init_resource::() - .init_resource::>() + .init_resource::>() .init_resource::() .init_resource::() .init_resource::() @@ -416,8 +416,8 @@ pub fn queue_uinodes( mut ui_meta: ResMut, view_uniforms: Res, ui_pipeline: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, mut image_bind_groups: ResMut, gpu_images: Res>, ui_batches: Query<(Entity, &UiBatch)>, diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 4538dc922b131..7fa13d2740fa4 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -62,7 +62,7 @@ impl FromWorld for UiPipeline { #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct UiPipelineKey {} -impl SpecializedPipeline for UiPipeline { +impl SpecializedRenderPipeline for UiPipeline { type Key = UiPipelineKey; /// FIXME: there are no specialization for now, should this be removed? fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 371645839ac08..6d48ae4c86cf2 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -9,9 +9,10 @@ use bevy::{ render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ BlendState, ColorTargetState, ColorWrites, Face, FragmentState, FrontFace, - MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineCache, - RenderPipelineDescriptor, SpecializedPipeline, SpecializedPipelines, TextureFormat, - VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + MultisampleState, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, + RenderPipelineDescriptor, SpecializedRenderPipeline, SpecializedRenderPipelines, + TextureFormat, VertexAttribute, VertexBufferLayout, VertexFormat, VertexState, + VertexStepMode, }, texture::BevyDefault, view::VisibleEntities, @@ -125,7 +126,7 @@ impl FromWorld for ColoredMesh2dPipeline { } // We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline` -impl SpecializedPipeline for ColoredMesh2dPipeline { +impl SpecializedRenderPipeline for ColoredMesh2dPipeline { type Key = Mesh2dPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { @@ -281,7 +282,7 @@ impl Plugin for ColoredMesh2dPlugin { render_app .add_render_command::() .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_colored_mesh2d) .add_system_to_stage(RenderStage::Queue, queue_colored_mesh2d); } @@ -309,8 +310,8 @@ pub fn extract_colored_mesh2d( pub fn queue_colored_mesh2d( transparent_draw_functions: Res>, colored_mesh2d_pipeline: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, 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 6e8f4b00af0fc..a3942058ef7ef 100644 --- a/examples/shader/animate_shader.rs +++ b/examples/shader/animate_shader.rs @@ -66,7 +66,7 @@ impl Plugin for CustomMaterialPlugin { bind_group: None, }) .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_time) .add_system_to_stage(RenderStage::Extract, extract_custom_material) .add_system_to_stage(RenderStage::Prepare, prepare_time) @@ -94,8 +94,8 @@ fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, msaa: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, material_meshes: Query<(Entity, &MeshUniform), (With>, With)>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) { @@ -207,7 +207,7 @@ impl FromWorld for CustomPipeline { } } -impl SpecializedPipeline for CustomPipeline { +impl SpecializedRenderPipeline for CustomPipeline { type Key = MeshPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 9ad3db0b5bfe1..0610eaff2b54e 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -10,10 +10,27 @@ use bevy::{ }, window::WindowDescriptor, }; +use std::borrow::Cow; +use std::ops::Deref; const SIZE: (u32, u32) = (1280, 720); const WORKGROUP_SIZE: u32 = 8; +// the layout descriptor of the bind group of game of life compute shader +const BIND_GROUP_LAYOUT: BindGroupLayoutDescriptor = BindGroupLayoutDescriptor { + label: None, + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::StorageTexture { + access: StorageTextureAccess::ReadWrite, + format: TextureFormat::Rgba8Unorm, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }], +}; + fn main() { App::new() .insert_resource(ClearColor(Color::BLACK)) @@ -63,6 +80,7 @@ impl Plugin for GameOfLifeComputePlugin { let render_app = app.sub_app_mut(RenderApp); render_app .init_resource::() + .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_game_of_life_image) .add_system_to_stage(RenderStage::Queue, queue_bind_group); @@ -80,6 +98,7 @@ struct GameOfLifeImageBindGroup(BindGroup); fn extract_game_of_life_image(mut commands: Commands, image: Res) { commands.insert_resource(GameOfLifeImage(image.0.clone())); } + fn queue_bind_group( mut commands: Commands, pipeline: Res, @@ -99,85 +118,107 @@ fn queue_bind_group( commands.insert_resource(GameOfLifeImageBindGroup(bind_group)); } +#[derive(Clone, Hash, PartialEq, Eq)] +pub enum GameOfLifePipelineKey { + Init, + Update, +} + pub struct GameOfLifePipeline { - sim_pipeline: ComputePipeline, - init_pipeline: ComputePipeline, texture_bind_group_layout: BindGroupLayout, + shader: Handle, } impl FromWorld for GameOfLifePipeline { fn from_world(world: &mut World) -> Self { let render_device = world.get_resource::().unwrap(); + let asset_server = world.get_resource::().unwrap(); - let shader_source = include_str!("../../assets/shaders/game_of_life.wgsl"); - let shader = render_device.create_shader_module(&ShaderModuleDescriptor { - label: None, - source: ShaderSource::Wgsl(shader_source.into()), - }); - - let texture_bind_group_layout = - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: None, - entries: &[BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::COMPUTE, - ty: BindingType::StorageTexture { - access: StorageTextureAccess::ReadWrite, - format: TextureFormat::Rgba8Unorm, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }], - }); - - let pipeline_layout = render_device.create_pipeline_layout(&PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[&texture_bind_group_layout], - push_constant_ranges: &[], - }); - let init_pipeline = render_device.create_compute_pipeline(&ComputePipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - module: &shader, - entry_point: "init", - }); - let sim_pipeline = render_device.create_compute_pipeline(&ComputePipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - module: &shader, - entry_point: "update", - }); + let texture_bind_group_layout = render_device.create_bind_group_layout(&BIND_GROUP_LAYOUT); + let shader = asset_server.load("shaders/game_of_life.wgsl"); GameOfLifePipeline { - sim_pipeline, - init_pipeline, texture_bind_group_layout, + shader, } } } -enum Initialized { - Default, - No, - Yes, +impl SpecializedComputePipeline for GameOfLifePipeline { + type Key = GameOfLifePipelineKey; + + fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor { + let entry_point = match key { + GameOfLifePipelineKey::Init => "init", + GameOfLifePipelineKey::Update => "update", + }; + + ComputePipelineDescriptor { + label: None, + layout: Some(vec![self.texture_bind_group_layout.clone()]), + shader: self.shader.clone(), + shader_defs: vec![], + entry_point: Cow::from(entry_point), + } + } +} + +enum GameOfLifeState { + NotReady, + Init, + Update, } struct DispatchGameOfLife { - initialized: Initialized, + state: GameOfLifeState, + init_pipeline: CachedPipelineId, + update_pipeline: CachedPipelineId, } + impl Default for DispatchGameOfLife { fn default() -> Self { Self { - initialized: Initialized::Default, + state: GameOfLifeState::NotReady, + init_pipeline: CachedPipelineId::INVALID, + update_pipeline: CachedPipelineId::INVALID, } } } + impl render_graph::Node for DispatchGameOfLife { - fn update(&mut self, _world: &mut World) { - match self.initialized { - Initialized::Default => self.initialized = Initialized::No, - Initialized::No => self.initialized = Initialized::Yes, - Initialized::Yes => {} + fn update(&mut self, world: &mut World) { + let world = world.cell(); + + let mut pipelines = world + .get_resource_mut::>() + .unwrap(); + let mut pipeline_cache = world.get_resource_mut::().unwrap(); + let game_of_life_pipeline = world.get_resource::().unwrap(); + + self.init_pipeline = pipelines.specialize( + &mut pipeline_cache, + game_of_life_pipeline.deref(), + GameOfLifePipelineKey::Init, + ); + self.update_pipeline = pipelines.specialize( + &mut pipeline_cache, + game_of_life_pipeline.deref(), + GameOfLifePipelineKey::Update, + ); + + // if the corresponding pipeline has loaded, transition to the next stage + match self.state { + GameOfLifeState::NotReady => { + if let CachedPipelineState::Ok(_) = pipeline_cache.get_state(self.init_pipeline) { + self.state = GameOfLifeState::Init + } + } + GameOfLifeState::Init => { + if let CachedPipelineState::Ok(_) = pipeline_cache.get_state(self.update_pipeline) { + self.state = GameOfLifeState::Update + } + } + GameOfLifeState::Update => {} } } @@ -187,22 +228,33 @@ impl render_graph::Node for DispatchGameOfLife { render_context: &mut RenderContext, world: &World, ) -> Result<(), render_graph::NodeRunError> { - let pipeline = world.get_resource::().unwrap(); let texture_bind_group = &world.get_resource::().unwrap().0; + let pipeline_cache = world.get_resource::().unwrap(); let mut pass = render_context .command_encoder .begin_compute_pass(&ComputePassDescriptor::default()); - if let Initialized::No = self.initialized { - pass.set_pipeline(&pipeline.init_pipeline); - pass.set_bind_group(0, texture_bind_group, &[]); - pass.dispatch(SIZE.0 / WORKGROUP_SIZE, SIZE.1 / WORKGROUP_SIZE, 1); - } - - pass.set_pipeline(&pipeline.sim_pipeline); pass.set_bind_group(0, texture_bind_group, &[]); - pass.dispatch(SIZE.0 / WORKGROUP_SIZE, SIZE.1 / WORKGROUP_SIZE, 1); + + // select the pipeline based on the current state + match self.state { + GameOfLifeState::NotReady => {} + GameOfLifeState::Init => { + let init_pipeline = pipeline_cache + .get_compute_pipeline(self.init_pipeline) + .unwrap(); + pass.set_pipeline(init_pipeline); + pass.dispatch(SIZE.0 / WORKGROUP_SIZE, SIZE.1 / WORKGROUP_SIZE, 1); + } + GameOfLifeState::Update => { + let update_pipeline = pipeline_cache + .get_compute_pipeline(self.update_pipeline) + .unwrap(); + pass.set_pipeline(update_pipeline); + pass.dispatch(SIZE.0 / WORKGROUP_SIZE, SIZE.1 / WORKGROUP_SIZE, 1); + } + } Ok(()) } diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index bec75411f0a3e..cbccd7e3c0f95 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -10,8 +10,8 @@ use bevy::{ render_component::{ExtractComponent, ExtractComponentPlugin}, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ - RenderPipelineCache, RenderPipelineDescriptor, SpecializedPipeline, - SpecializedPipelines, + PipelineCache, RenderPipelineDescriptor, SpecializedRenderPipeline, + SpecializedRenderPipelines, }, view::ExtractedView, RenderApp, RenderStage, @@ -26,7 +26,7 @@ impl Plugin for IsRedPlugin { app.sub_app_mut(RenderApp) .add_render_command::() .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Queue, queue_custom); } } @@ -98,7 +98,7 @@ impl FromWorld for IsRedPipeline { } } -impl SpecializedPipeline for IsRedPipeline { +impl SpecializedRenderPipeline for IsRedPipeline { type Key = (IsRed, MeshPipelineKey); fn specialize(&self, (is_red, pbr_pipeline_key): Self::Key) -> RenderPipelineDescriptor { @@ -133,8 +133,8 @@ fn queue_custom( render_meshes: Res>, custom_pipeline: Res, msaa: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, material_meshes: Query<(Entity, &Handle, &MeshUniform, &IsRed)>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) { diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 4c88cd17481e4..7b8ccc7e71e97 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -81,7 +81,7 @@ impl Plugin for CustomMaterialPlugin { app.sub_app_mut(RenderApp) .add_render_command::() .init_resource::() - .init_resource::>() + .init_resource::>() .add_system_to_stage(RenderStage::Queue, queue_custom) .add_system_to_stage(RenderStage::Prepare, prepare_instance_buffers); } @@ -99,8 +99,8 @@ fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, msaa: Res, - mut pipelines: ResMut>, - mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, material_meshes: Query< (Entity, &MeshUniform), (With>, With), @@ -175,7 +175,7 @@ impl FromWorld for CustomPipeline { } } -impl SpecializedPipeline for CustomPipeline { +impl SpecializedRenderPipeline for CustomPipeline { type Key = MeshPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { From 8f4784f82a1846fc22ca2046766b597989b615fe Mon Sep 17 00:00:00 2001 From: Ku95 Date: Sun, 6 Mar 2022 15:52:09 +0100 Subject: [PATCH 2/5] resolved conflicts --- crates/bevy_pbr/src/lib.rs | 1 - crates/bevy_pbr/src/render/mesh.rs | 1 - crates/bevy_pbr/src/wireframe.rs | 14 ++-- .../src/render_resource/pipeline_cache.rs | 42 ++++++++-- .../render_resource/pipeline_specializer.rs | 80 +++++++++---------- 5 files changed, 80 insertions(+), 58 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index ef2c5aeb482a5..34ed27cba44a7 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -177,7 +177,6 @@ impl Plugin for PbrPlugin { .init_resource::() .init_resource::>(); - let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); render_app.add_render_command::(); let mut graph = render_app.world.resource_mut::(); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index f268cb900d5c3..ac0ec3acd4360 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -408,7 +408,6 @@ impl MeshPipelineKey { } } - impl SpecializedMeshPipeline for MeshPipeline { type Key = MeshPipelineKey; diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 963f69da49273..b4cfefbd8fc72 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -5,17 +5,13 @@ use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; use bevy_core_pipeline::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::{Reflect, TypeUuid}; -use bevy_render::mesh::MeshVertexBufferLayout; -use bevy_render::render_resource::{ - PolygonMode, RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, - SpecializedMeshPipelines, -}; use bevy_render::{ - mesh::Mesh, + mesh::{Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ - PipelineCache, Shader, SpecializedRenderPipeline, SpecializedRenderPipelines, + PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline, + SpecializedMeshPipelineError, SpecializedMeshPipelines, }, view::{ExtractedView, Msaa}, RenderApp, RenderStage, @@ -111,7 +107,7 @@ fn queue_wireframes( render_meshes: Res>, wireframe_config: Res, wireframe_pipeline: Res, - mut pipelines: ResMut>, + mut pipelines: ResMut>, mut pipeline_cache: ResMut, msaa: Res, mut material_meshes: QuerySet<( @@ -134,7 +130,7 @@ fn queue_wireframes( if let Some(mesh) = render_meshes.get(mesh_handle) { let key = msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline_id = specialized_pipelines.specialize( + let pipeline_id = pipelines.specialize( &mut pipeline_cache, &wireframe_pipeline, key, diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 6f18434f1da5d..0e50cbc615250 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -1,8 +1,8 @@ -use crate::render_resource::{ComputePipeline, RawComputePipelineDescriptor}; use crate::{ render_resource::{ - AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ComputePipelineDescriptor, - ProcessShaderError, ProcessedShader, RawFragmentState, RawRenderPipelineDescriptor, + AsModuleDescriptorError, BindGroupLayout, BindGroupLayoutId, ComputePipeline, + ComputePipelineDescriptor, ProcessShaderError, ProcessedShader, + RawComputePipelineDescriptor, RawFragmentState, RawRenderPipelineDescriptor, RawVertexState, RenderPipeline, RenderPipelineDescriptor, Shader, ShaderImport, ShaderProcessor, ShaderReflectError, }, @@ -18,8 +18,8 @@ use thiserror::Error; use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout as RawVertexBufferLayout}; enum PipelineDescriptor { - RenderPipelineDescriptor(RenderPipelineDescriptor), - ComputePipelineDescriptor(ComputePipelineDescriptor), + RenderPipelineDescriptor(Box), + ComputePipelineDescriptor(Box), } #[derive(Debug)] @@ -241,6 +241,34 @@ impl PipelineCache { &self.pipelines[id.0].state } + #[inline] + pub fn get_render_pipeline_descriptor( + &self, + id: CachedPipelineId, + ) -> Option<&RenderPipelineDescriptor> { + if let PipelineDescriptor::RenderPipelineDescriptor(descriptor) = + &self.pipelines[id.0].descriptor + { + Some(descriptor) + } else { + None + } + } + + #[inline] + pub fn get_compute_pipeline_descriptor( + &self, + id: CachedPipelineId, + ) -> Option<&ComputePipelineDescriptor> { + if let PipelineDescriptor::ComputePipelineDescriptor(descriptor) = + &self.pipelines[id.0].descriptor + { + Some(descriptor) + } else { + None + } + } + #[inline] pub fn get_render_pipeline(&self, id: CachedPipelineId) -> Option<&RenderPipeline> { if let CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) = @@ -269,7 +297,7 @@ impl PipelineCache { ) -> CachedPipelineId { let id = CachedPipelineId(self.pipelines.len()); self.pipelines.push(CachedPipeline { - descriptor: PipelineDescriptor::RenderPipelineDescriptor(descriptor), + descriptor: PipelineDescriptor::RenderPipelineDescriptor(Box::new(descriptor)), state: CachedPipelineState::Queued, }); self.waiting_pipelines.insert(id); @@ -282,7 +310,7 @@ impl PipelineCache { ) -> CachedPipelineId { let id = CachedPipelineId(self.pipelines.len()); self.pipelines.push(CachedPipeline { - descriptor: PipelineDescriptor::ComputePipelineDescriptor(descriptor), + descriptor: PipelineDescriptor::ComputePipelineDescriptor(Box::new(descriptor)), state: CachedPipelineState::Queued, }); self.waiting_pipelines.insert(id); diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index a24c891f01b5c..5b61b40721edb 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -1,17 +1,17 @@ use crate::{ mesh::{InnerMeshVertexBufferLayout, MeshVertexBufferLayout, MissingVertexAttributeError}, render_resource::{ - CachedPipelineId, PipelineCache, RenderPipelineDescriptor, VertexBufferLayout, + CachedPipelineId, ComputePipelineDescriptor, PipelineCache, RenderPipelineDescriptor, + VertexBufferLayout, }, }; use bevy_utils::{ hashbrown::hash_map::RawEntryMut, tracing::error, Entry, HashMap, PreHashMap, PreHashMapExt, }; -use std::fmt::Debug; -use std::hash::Hash; +use std::{fmt::Debug, hash::Hash}; use thiserror::Error; -pub trait SpecializedPipeline { +pub trait SpecializedRenderPipeline { type Key: Clone + Hash + PartialEq + Eq; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor; } @@ -42,10 +42,35 @@ impl SpecializedRenderPipelines { } } -#[derive(Error, Debug)] -pub enum SpecializedMeshPipelineError { - #[error(transparent)] - MissingVertexAttribute(#[from] MissingVertexAttributeError), +pub trait SpecializedComputePipeline { + type Key: Clone + Hash + PartialEq + Eq; + fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor; +} + +pub struct SpecializedComputePipelines { + cache: HashMap, +} + +impl Default for SpecializedComputePipelines { + fn default() -> Self { + Self { + cache: Default::default(), + } + } +} + +impl SpecializedComputePipelines { + pub fn specialize( + &mut self, + cache: &mut PipelineCache, + specialize_pipeline: &S, + key: S::Key, + ) -> CachedPipelineId { + *self.cache.entry(key.clone()).or_insert_with(|| { + let descriptor = specialize_pipeline.specialize(key); + cache.queue_compute_pipeline(descriptor) + }) + } } pub trait SpecializedMeshPipeline { @@ -75,7 +100,7 @@ impl SpecializedMeshPipelines { #[inline] pub fn specialize( &mut self, - cache: &mut RenderPipelineCache, + cache: &mut PipelineCache, specialize_pipeline: &S, key: S::Key, layout: &MeshVertexBufferLayout, @@ -113,7 +138,7 @@ impl SpecializedMeshPipelines { Ok(*entry.insert(match layout_map.entry(key) { Entry::Occupied(entry) => { if cfg!(debug_assertions) { - let stored_descriptor = cache.get_descriptor(*entry.get()); + let stored_descriptor = cache.get_render_pipeline_descriptor(*entry.get()).expect("The cached pipeline is not a render pipeline."); 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::()); } @@ -121,7 +146,7 @@ impl SpecializedMeshPipelines { *entry.into_mut() } Entry::Vacant(entry) => { - *entry.insert(cache.queue(descriptor)) + *entry.insert(cache.queue_render_pipeline(descriptor)) } })) } @@ -129,33 +154,8 @@ impl SpecializedMeshPipelines { } } -pub struct SpecializedComputePipelines { - cache: HashMap, -} - -impl Default for SpecializedComputePipelines { - fn default() -> Self { - Self { - cache: Default::default(), - } - } -} - -impl SpecializedComputePipelines { - pub fn specialize( - &mut self, - cache: &mut PipelineCache, - specialize_pipeline: &S, - key: S::Key, - ) -> CachedPipelineId { - *self.cache.entry(key.clone()).or_insert_with(|| { - let descriptor = specialize_pipeline.specialize(key); - cache.queue_compute_pipeline(descriptor) - }) - } -} - -pub trait SpecializedComputePipeline { - type Key: Clone + Hash + PartialEq + Eq; - fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor; +#[derive(Error, Debug)] +pub enum SpecializedMeshPipelineError { + #[error(transparent)] + MissingVertexAttribute(#[from] MissingVertexAttributeError), } From 927f310e2dcd85f1dc0fe6f8b0d0888e9b0dc76f Mon Sep 17 00:00:00 2001 From: Ku95 Date: Sat, 19 Mar 2022 00:02:12 +0100 Subject: [PATCH 3/5] split `CachedPipelineId` into ``CachedRenderPipelineId` and `CachedComputePipelineId` --- crates/bevy_core_pipeline/src/lib.rs | 26 +++--- crates/bevy_pbr/src/render/light.rs | 8 +- crates/bevy_render/src/lib.rs | 2 + crates/bevy_render/src/render_phase/draw.rs | 8 +- .../src/render_resource/pipeline_cache.rs | 88 ++++++++++--------- .../render_resource/pipeline_specializer.rs | 31 ++++--- crates/bevy_ui/src/render/render_pass.rs | 8 +- examples/2d/mesh2d_manual.rs | 6 +- .../shader/compute_shader_game_of_life.rs | 16 ++-- 9 files changed, 103 insertions(+), 90 deletions(-) diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index bb915965c73a2..8fa6be9f299c2 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -27,7 +27,7 @@ use bevy_render::{ color::Color, render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, render_phase::{ - batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedPipelinePhaseItem, + batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase, }, render_resource::*, @@ -198,7 +198,7 @@ impl Plugin for CorePipelinePlugin { pub struct Transparent2d { pub sort_key: FloatOrd, pub entity: Entity, - pub pipeline: CachedPipelineId, + pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, /// Range in the vertex buffer of this item pub batch_range: Option>, @@ -225,9 +225,9 @@ impl EntityPhaseItem for Transparent2d { } } -impl CachedPipelinePhaseItem for Transparent2d { +impl CachedRenderPipelinePhaseItem for Transparent2d { #[inline] - fn cached_pipeline(&self) -> CachedPipelineId { + fn cached_pipeline(&self) -> CachedRenderPipelineId { self.pipeline } } @@ -244,7 +244,7 @@ impl BatchedPhaseItem for Transparent2d { pub struct Opaque3d { pub distance: f32, - pub pipeline: CachedPipelineId, + pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, } @@ -270,16 +270,16 @@ impl EntityPhaseItem for Opaque3d { } } -impl CachedPipelinePhaseItem for Opaque3d { +impl CachedRenderPipelinePhaseItem for Opaque3d { #[inline] - fn cached_pipeline(&self) -> CachedPipelineId { + fn cached_pipeline(&self) -> CachedRenderPipelineId { self.pipeline } } pub struct AlphaMask3d { pub distance: f32, - pub pipeline: CachedPipelineId, + pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, } @@ -305,16 +305,16 @@ impl EntityPhaseItem for AlphaMask3d { } } -impl CachedPipelinePhaseItem for AlphaMask3d { +impl CachedRenderPipelinePhaseItem for AlphaMask3d { #[inline] - fn cached_pipeline(&self) -> CachedPipelineId { + fn cached_pipeline(&self) -> CachedRenderPipelineId { self.pipeline } } pub struct Transparent3d { pub distance: f32, - pub pipeline: CachedPipelineId, + pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, } @@ -340,9 +340,9 @@ impl EntityPhaseItem for Transparent3d { } } -impl CachedPipelinePhaseItem for Transparent3d { +impl CachedRenderPipelinePhaseItem for Transparent3d { #[inline] - fn cached_pipeline(&self) -> CachedPipelineId { + fn cached_pipeline(&self) -> CachedRenderPipelineId { self.pipeline } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 790d584db154c..e8cab41d39778 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -18,7 +18,7 @@ use bevy_render::{ render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::{ - CachedPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem, + CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem, EntityRenderCommand, PhaseItem, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }, @@ -1119,7 +1119,7 @@ pub fn queue_shadows( pub struct Shadow { pub distance: f32, pub entity: Entity, - pub pipeline: CachedPipelineId, + pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, } @@ -1143,9 +1143,9 @@ impl EntityPhaseItem for Shadow { } } -impl CachedPipelinePhaseItem for Shadow { +impl CachedRenderPipelinePhaseItem for Shadow { #[inline] - fn cached_pipeline(&self) -> CachedPipelineId { + fn cached_pipeline(&self) -> CachedRenderPipelineId { self.pipeline } } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index ccb6ec215f225..05429f026cf9d 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -1,3 +1,5 @@ +extern crate core; + pub mod camera; pub mod color; pub mod mesh; diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index ff5d04e299927..5ffca0ea25886 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -1,6 +1,6 @@ use crate::{ render_phase::TrackedRenderPass, - render_resource::{CachedPipelineId, PipelineCache}, + render_resource::{CachedRenderPipelineId, PipelineCache}, }; use bevy_app::App; use bevy_ecs::{ @@ -162,8 +162,8 @@ pub trait EntityPhaseItem: PhaseItem { fn entity(&self) -> Entity; } -pub trait CachedPipelinePhaseItem: PhaseItem { - fn cached_pipeline(&self) -> CachedPipelineId; +pub trait CachedRenderPipelinePhaseItem: PhaseItem { + fn cached_pipeline(&self) -> CachedRenderPipelineId; } /// A [`PhaseItem`] that can be batched dynamically. @@ -224,7 +224,7 @@ impl RenderCommand

for E { } pub struct SetItemPipeline; -impl RenderCommand

for SetItemPipeline { +impl RenderCommand

for SetItemPipeline { type Param = SRes; #[inline] fn render<'w>( diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 0e50cbc615250..484fb97302960 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -12,8 +12,8 @@ use crate::{ use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::event::EventReader; use bevy_ecs::system::{Res, ResMut}; -use bevy_utils::{tracing::error, Entry, HashMap, HashSet}; -use std::{hash::Hash, ops::Deref, sync::Arc}; +use bevy_utils::{default, tracing::error, Entry, HashMap, HashSet}; +use std::{hash::Hash, mem, ops::Deref, sync::Arc}; use thiserror::Error; use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout as RawVertexBufferLayout}; @@ -28,11 +28,20 @@ pub enum Pipeline { ComputePipeline(ComputePipeline), } +type CachedPipelineId = usize; + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub struct CachedRenderPipelineId(CachedPipelineId); + +impl CachedRenderPipelineId { + pub const INVALID: Self = CachedRenderPipelineId(usize::MAX); +} + #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub struct CachedPipelineId(usize); +pub struct CachedComputePipelineId(CachedPipelineId); -impl CachedPipelineId { - pub const INVALID: Self = CachedPipelineId(usize::MAX); +impl CachedComputePipelineId { + pub const INVALID: Self = CachedComputePipelineId(usize::MAX); } struct CachedPipeline { @@ -211,7 +220,7 @@ impl LayoutCache { .collect::>(); render_device.create_pipeline_layout(&PipelineLayoutDescriptor { bind_group_layouts: &bind_group_layouts, - ..Default::default() + ..default() }) }) } @@ -229,48 +238,47 @@ impl PipelineCache { pub fn new(device: RenderDevice) -> Self { Self { device, - layout_cache: Default::default(), - shader_cache: Default::default(), - waiting_pipelines: Default::default(), - pipelines: Default::default(), + layout_cache: default(), + shader_cache: default(), + waiting_pipelines: default(), + pipelines: default(), } } #[inline] - pub fn get_state(&self, id: CachedPipelineId) -> &CachedPipelineState { + pub fn get_render_pipeline_state(&self, id: CachedRenderPipelineId) -> &CachedPipelineState { + &self.pipelines[id.0].state + } + + #[inline] + pub fn get_compute_pipeline_state(&self, id: CachedComputePipelineId) -> &CachedPipelineState { &self.pipelines[id.0].state } #[inline] pub fn get_render_pipeline_descriptor( &self, - id: CachedPipelineId, - ) -> Option<&RenderPipelineDescriptor> { - if let PipelineDescriptor::RenderPipelineDescriptor(descriptor) = - &self.pipelines[id.0].descriptor - { - Some(descriptor) - } else { - None + id: CachedRenderPipelineId, + ) -> &RenderPipelineDescriptor { + match &self.pipelines[id.0].descriptor { + PipelineDescriptor::RenderPipelineDescriptor(descriptor) => descriptor, + PipelineDescriptor::ComputePipelineDescriptor(_) => unreachable!(), } } #[inline] pub fn get_compute_pipeline_descriptor( &self, - id: CachedPipelineId, - ) -> Option<&ComputePipelineDescriptor> { - if let PipelineDescriptor::ComputePipelineDescriptor(descriptor) = - &self.pipelines[id.0].descriptor - { - Some(descriptor) - } else { - None + id: CachedComputePipelineId, + ) -> &ComputePipelineDescriptor { + match &self.pipelines[id.0].descriptor { + PipelineDescriptor::RenderPipelineDescriptor(_) => unreachable!(), + PipelineDescriptor::ComputePipelineDescriptor(descriptor) => descriptor, } } #[inline] - pub fn get_render_pipeline(&self, id: CachedPipelineId) -> Option<&RenderPipeline> { + pub fn get_render_pipeline(&self, id: CachedRenderPipelineId) -> Option<&RenderPipeline> { if let CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) = &self.pipelines[id.0].state { @@ -281,7 +289,7 @@ impl PipelineCache { } #[inline] - pub fn get_compute_pipeline(&self, id: CachedPipelineId) -> Option<&ComputePipeline> { + pub fn get_compute_pipeline(&self, id: CachedComputePipelineId) -> Option<&ComputePipeline> { if let CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) = &self.pipelines[id.0].state { @@ -294,33 +302,33 @@ impl PipelineCache { pub fn queue_render_pipeline( &mut self, descriptor: RenderPipelineDescriptor, - ) -> CachedPipelineId { - let id = CachedPipelineId(self.pipelines.len()); + ) -> CachedRenderPipelineId { + let id = CachedRenderPipelineId(self.pipelines.len()); self.pipelines.push(CachedPipeline { descriptor: PipelineDescriptor::RenderPipelineDescriptor(Box::new(descriptor)), state: CachedPipelineState::Queued, }); - self.waiting_pipelines.insert(id); + self.waiting_pipelines.insert(id.0); id } pub fn queue_compute_pipeline( &mut self, descriptor: ComputePipelineDescriptor, - ) -> CachedPipelineId { - let id = CachedPipelineId(self.pipelines.len()); + ) -> CachedComputePipelineId { + let id = CachedComputePipelineId(self.pipelines.len()); self.pipelines.push(CachedPipeline { descriptor: PipelineDescriptor::ComputePipelineDescriptor(Box::new(descriptor)), state: CachedPipelineState::Queued, }); - self.waiting_pipelines.insert(id); + self.waiting_pipelines.insert(id.0); id } 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.0].state = CachedPipelineState::Queued; + self.pipelines[cached_pipeline].state = CachedPipelineState::Queued; self.waiting_pipelines.insert(cached_pipeline); } } @@ -328,7 +336,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.0].state = CachedPipelineState::Queued; + self.pipelines[cached_pipeline].state = CachedPipelineState::Queued; self.waiting_pipelines.insert(cached_pipeline); } } @@ -450,11 +458,11 @@ impl PipelineCache { } pub fn process_queue(&mut self) { - let waiting_pipelines = std::mem::take(&mut self.waiting_pipelines); - let mut pipelines = std::mem::take(&mut self.pipelines); + let waiting_pipelines = mem::take(&mut self.waiting_pipelines); + let mut pipelines = mem::take(&mut self.pipelines); for id in waiting_pipelines { - let pipeline = &mut pipelines[id.0]; + let pipeline = &mut pipelines[id]; match &pipeline.state { CachedPipelineState::Ok(_) => continue, CachedPipelineState::Queued => {} diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index 5b61b40721edb..ee73bf1cf6bff 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -1,12 +1,14 @@ +use crate::render_resource::CachedComputePipelineId; use crate::{ mesh::{InnerMeshVertexBufferLayout, MeshVertexBufferLayout, MissingVertexAttributeError}, render_resource::{ - CachedPipelineId, ComputePipelineDescriptor, PipelineCache, RenderPipelineDescriptor, + CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache, RenderPipelineDescriptor, VertexBufferLayout, }, }; use bevy_utils::{ - hashbrown::hash_map::RawEntryMut, tracing::error, Entry, HashMap, PreHashMap, PreHashMapExt, + default, hashbrown::hash_map::RawEntryMut, tracing::error, Entry, HashMap, PreHashMap, + PreHashMapExt, }; use std::{fmt::Debug, hash::Hash}; use thiserror::Error; @@ -17,14 +19,12 @@ pub trait SpecializedRenderPipeline { } pub struct SpecializedRenderPipelines { - cache: HashMap, + cache: HashMap, } impl Default for SpecializedRenderPipelines { fn default() -> Self { - Self { - cache: Default::default(), - } + Self { cache: default() } } } @@ -34,7 +34,7 @@ impl SpecializedRenderPipelines { cache: &mut PipelineCache, specialize_pipeline: &S, key: S::Key, - ) -> CachedPipelineId { + ) -> CachedRenderPipelineId { *self.cache.entry(key.clone()).or_insert_with(|| { let descriptor = specialize_pipeline.specialize(key); cache.queue_render_pipeline(descriptor) @@ -48,14 +48,12 @@ pub trait SpecializedComputePipeline { } pub struct SpecializedComputePipelines { - cache: HashMap, + cache: HashMap, } impl Default for SpecializedComputePipelines { fn default() -> Self { - Self { - cache: Default::default(), - } + Self { cache: default() } } } @@ -65,7 +63,7 @@ impl SpecializedComputePipelines { cache: &mut PipelineCache, specialize_pipeline: &S, key: S::Key, - ) -> CachedPipelineId { + ) -> CachedComputePipelineId { *self.cache.entry(key.clone()).or_insert_with(|| { let descriptor = specialize_pipeline.specialize(key); cache.queue_compute_pipeline(descriptor) @@ -83,8 +81,9 @@ pub trait SpecializedMeshPipeline { } pub struct SpecializedMeshPipelines { - mesh_layout_cache: PreHashMap>, - vertex_layout_cache: HashMap>, + mesh_layout_cache: + PreHashMap>, + vertex_layout_cache: HashMap>, } impl Default for SpecializedMeshPipelines { @@ -104,7 +103,7 @@ impl SpecializedMeshPipelines { specialize_pipeline: &S, key: S::Key, layout: &MeshVertexBufferLayout, - ) -> Result { + ) -> Result { let map = self .mesh_layout_cache .get_or_insert_with(layout, Default::default); @@ -138,7 +137,7 @@ impl SpecializedMeshPipelines { Ok(*entry.insert(match layout_map.entry(key) { Entry::Occupied(entry) => { if cfg!(debug_assertions) { - let stored_descriptor = cache.get_render_pipeline_descriptor(*entry.get()).expect("The cached pipeline is not a render pipeline."); + 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::()); } diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 23d1577e6a3bc..16b2280c13a85 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -8,7 +8,7 @@ use bevy_render::{ render_graph::*, render_phase::*, render_resource::{ - CachedPipelineId, LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, + CachedRenderPipelineId, LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, }, renderer::*, view::*, @@ -102,7 +102,7 @@ impl Node for UiPassNode { pub struct TransparentUi { pub sort_key: FloatOrd, pub entity: Entity, - pub pipeline: CachedPipelineId, + pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, } @@ -127,9 +127,9 @@ impl EntityPhaseItem for TransparentUi { } } -impl CachedPipelinePhaseItem for TransparentUi { +impl CachedRenderPipelinePhaseItem for TransparentUi { #[inline] - fn cached_pipeline(&self) -> CachedPipelineId { + fn cached_pipeline(&self) -> CachedRenderPipelineId { self.pipeline } } diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index bd2b784af24c9..e4cc86d886164 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -9,9 +9,9 @@ use bevy::{ render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ BlendState, ColorTargetState, ColorWrites, Face, FragmentState, FrontFace, - MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, PipelineCache, - RenderPipelineDescriptor, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat, - VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, + MultisampleState, PipelineCache, PolygonMode, PrimitiveState, PrimitiveTopology, + RenderPipelineDescriptor, SpecializedRenderPipeline, SpecializedRenderPipelines, + TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, texture::BevyDefault, view::VisibleEntities, diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 181dc292683d6..3407a88eb73fc 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -171,16 +171,16 @@ enum GameOfLifeState { struct DispatchGameOfLife { state: GameOfLifeState, - init_pipeline: CachedPipelineId, - update_pipeline: CachedPipelineId, + init_pipeline: CachedComputePipelineId, + update_pipeline: CachedComputePipelineId, } impl Default for DispatchGameOfLife { fn default() -> Self { Self { state: GameOfLifeState::NotReady, - init_pipeline: CachedPipelineId::INVALID, - update_pipeline: CachedPipelineId::INVALID, + init_pipeline: CachedComputePipelineId::INVALID, + update_pipeline: CachedComputePipelineId::INVALID, } } } @@ -209,12 +209,16 @@ impl render_graph::Node for DispatchGameOfLife { // if the corresponding pipeline has loaded, transition to the next stage match self.state { GameOfLifeState::NotReady => { - if let CachedPipelineState::Ok(_) = pipeline_cache.get_state(self.init_pipeline) { + if let CachedPipelineState::Ok(_) = + pipeline_cache.get_compute_pipeline_state(self.init_pipeline) + { self.state = GameOfLifeState::Init } } GameOfLifeState::Init => { - if let CachedPipelineState::Ok(_) = pipeline_cache.get_state(self.update_pipeline) { + if let CachedPipelineState::Ok(_) = + pipeline_cache.get_compute_pipeline_state(self.update_pipeline) + { self.state = GameOfLifeState::Update } } From 0e1b57a40e9d90e407ade37519d59873c4703580 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 22 Mar 2022 15:26:57 -0700 Subject: [PATCH 4/5] remove newline --- crates/bevy_pbr/src/material.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index a28a54c842bae..c5bafd333e2a6 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -308,7 +308,6 @@ pub fn queue_material_meshes( material_pipeline: Res>, mut pipelines: ResMut>>, mut pipeline_cache: ResMut, - msaa: Res, render_meshes: Res>, render_materials: Res>, From 39a9582b1b4916cba075e0ab04222d83f425d25b Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 22 Mar 2022 17:10:51 -0700 Subject: [PATCH 5/5] Remove specialization from example for simplicity --- .../shader/compute_shader_game_of_life.rs | 136 +++++++----------- 1 file changed, 53 insertions(+), 83 deletions(-) diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 3407a88eb73fc..07e3dee47de70 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -11,26 +11,10 @@ use bevy::{ window::WindowDescriptor, }; use std::borrow::Cow; -use std::ops::Deref; const SIZE: (u32, u32) = (1280, 720); const WORKGROUP_SIZE: u32 = 8; -// the layout descriptor of the bind group of game of life compute shader -const BIND_GROUP_LAYOUT: BindGroupLayoutDescriptor = BindGroupLayoutDescriptor { - label: None, - entries: &[BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::COMPUTE, - ty: BindingType::StorageTexture { - access: StorageTextureAccess::ReadWrite, - format: TextureFormat::Rgba8Unorm, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }], -}; - fn main() { App::new() .insert_resource(ClearColor(Color::BLACK)) @@ -80,12 +64,11 @@ impl Plugin for GameOfLifeComputePlugin { let render_app = app.sub_app_mut(RenderApp); render_app .init_resource::() - .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_game_of_life_image) .add_system_to_stage(RenderStage::Queue, queue_bind_group); let mut render_graph = render_app.world.resource_mut::(); - render_graph.add_node("game_of_life", DispatchGameOfLife::default()); + render_graph.add_node("game_of_life", GameOfLifeNode::default()); render_graph .add_node_edge("game_of_life", MAIN_PASS_DEPENDENCIES) .unwrap(); @@ -118,106 +101,92 @@ fn queue_bind_group( commands.insert_resource(GameOfLifeImageBindGroup(bind_group)); } -#[derive(Clone, Hash, PartialEq, Eq)] -pub enum GameOfLifePipelineKey { - Init, - Update, -} - pub struct GameOfLifePipeline { texture_bind_group_layout: BindGroupLayout, - shader: Handle, + init_pipeline: CachedComputePipelineId, + update_pipeline: CachedComputePipelineId, } impl FromWorld for GameOfLifePipeline { fn from_world(world: &mut World) -> Self { - let render_device = world.resource::(); - let asset_server = world.resource::(); - - let texture_bind_group_layout = render_device.create_bind_group_layout(&BIND_GROUP_LAYOUT); - let shader = asset_server.load("shaders/game_of_life.wgsl"); + let texture_bind_group_layout = + world + .resource::() + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::StorageTexture { + access: StorageTextureAccess::ReadWrite, + format: TextureFormat::Rgba8Unorm, + view_dimension: TextureViewDimension::D2, + }, + count: None, + }], + }); + let shader = world + .resource::() + .load("shaders/game_of_life.wgsl"); + let mut pipeline_cache = world.resource_mut::(); + let init_pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { + label: None, + layout: Some(vec![texture_bind_group_layout.clone()]), + shader: shader.clone(), + shader_defs: vec![], + entry_point: Cow::from("init"), + }); + let update_pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { + label: None, + layout: Some(vec![texture_bind_group_layout.clone()]), + shader, + shader_defs: vec![], + entry_point: Cow::from("update"), + }); GameOfLifePipeline { texture_bind_group_layout, - shader, - } - } -} - -impl SpecializedComputePipeline for GameOfLifePipeline { - type Key = GameOfLifePipelineKey; - - fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor { - let entry_point = match key { - GameOfLifePipelineKey::Init => "init", - GameOfLifePipelineKey::Update => "update", - }; - - ComputePipelineDescriptor { - label: None, - layout: Some(vec![self.texture_bind_group_layout.clone()]), - shader: self.shader.clone(), - shader_defs: vec![], - entry_point: Cow::from(entry_point), + init_pipeline, + update_pipeline, } } } enum GameOfLifeState { - NotReady, + Loading, Init, Update, } -struct DispatchGameOfLife { +struct GameOfLifeNode { state: GameOfLifeState, - init_pipeline: CachedComputePipelineId, - update_pipeline: CachedComputePipelineId, } -impl Default for DispatchGameOfLife { +impl Default for GameOfLifeNode { fn default() -> Self { Self { - state: GameOfLifeState::NotReady, - init_pipeline: CachedComputePipelineId::INVALID, - update_pipeline: CachedComputePipelineId::INVALID, + state: GameOfLifeState::Loading, } } } -impl render_graph::Node for DispatchGameOfLife { +impl render_graph::Node for GameOfLifeNode { fn update(&mut self, world: &mut World) { - let world = world.cell(); - - let mut pipelines = world - .get_resource_mut::>() - .unwrap(); - let mut pipeline_cache = world.get_resource_mut::().unwrap(); - let game_of_life_pipeline = world.get_resource::().unwrap(); - - self.init_pipeline = pipelines.specialize( - &mut pipeline_cache, - game_of_life_pipeline.deref(), - GameOfLifePipelineKey::Init, - ); - self.update_pipeline = pipelines.specialize( - &mut pipeline_cache, - game_of_life_pipeline.deref(), - GameOfLifePipelineKey::Update, - ); + let pipeline = world.resource::(); + let pipeline_cache = world.resource::(); // if the corresponding pipeline has loaded, transition to the next stage match self.state { - GameOfLifeState::NotReady => { + GameOfLifeState::Loading => { if let CachedPipelineState::Ok(_) = - pipeline_cache.get_compute_pipeline_state(self.init_pipeline) + pipeline_cache.get_compute_pipeline_state(pipeline.init_pipeline) { self.state = GameOfLifeState::Init } } GameOfLifeState::Init => { if let CachedPipelineState::Ok(_) = - pipeline_cache.get_compute_pipeline_state(self.update_pipeline) + pipeline_cache.get_compute_pipeline_state(pipeline.update_pipeline) { self.state = GameOfLifeState::Update } @@ -234,6 +203,7 @@ impl render_graph::Node for DispatchGameOfLife { ) -> Result<(), render_graph::NodeRunError> { let texture_bind_group = &world.resource::().0; let pipeline_cache = world.resource::(); + let pipeline = world.resource::(); let mut pass = render_context .command_encoder @@ -243,17 +213,17 @@ impl render_graph::Node for DispatchGameOfLife { // select the pipeline based on the current state match self.state { - GameOfLifeState::NotReady => {} + GameOfLifeState::Loading => {} GameOfLifeState::Init => { let init_pipeline = pipeline_cache - .get_compute_pipeline(self.init_pipeline) + .get_compute_pipeline(pipeline.init_pipeline) .unwrap(); pass.set_pipeline(init_pipeline); pass.dispatch(SIZE.0 / WORKGROUP_SIZE, SIZE.1 / WORKGROUP_SIZE, 1); } GameOfLifeState::Update => { let update_pipeline = pipeline_cache - .get_compute_pipeline(self.update_pipeline) + .get_compute_pipeline(pipeline.update_pipeline) .unwrap(); pass.set_pipeline(update_pipeline); pass.dispatch(SIZE.0 / WORKGROUP_SIZE, SIZE.1 / WORKGROUP_SIZE, 1);