Path: blob/main/crates/bevy_render/src/render_resource/pipeline_specializer.rs
9390 views
use bevy_material::descriptor::{1CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor,2RenderPipelineDescriptor,3};45use crate::render_resource::PipelineCache;6use bevy_ecs::resource::Resource;7use bevy_log::error;8use bevy_material::specialize::SpecializedMeshPipelineError;9use bevy_mesh::{MeshVertexBufferLayoutRef, VertexBufferLayout};10use bevy_platform::{11collections::{12hash_map::{Entry, RawEntryMut, VacantEntry},13HashMap,14},15hash::FixedHasher,16};17use bevy_utils::default;18use core::hash::Hash;1920/// A trait that allows constructing different variants of a render pipeline from a key.21///22/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key23/// contains no data then you don't need to specialize. For example, if you are using the24/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,25/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and26/// store its ID.27///28/// See [`SpecializedRenderPipelines`] for more info.29pub trait SpecializedRenderPipeline {30/// The key that defines each "variant" of the render pipeline.31type Key: Clone + Hash + PartialEq + Eq;3233/// Construct a new render pipeline based on the provided key.34fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor;35}3637/// A convenience cache for creating different variants of a render pipeline based on some key.38///39/// Some render pipelines may need to be configured differently depending on the exact situation.40/// This cache allows constructing different render pipelines for each situation based on a key,41/// making it easy to A) construct the necessary pipelines, and B) reuse already constructed42/// pipelines.43///44/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key45/// contains no data then you don't need to specialize. For example, if you are using the46/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,47/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and48/// store its ID.49#[derive(Resource)]50pub struct SpecializedRenderPipelines<S: SpecializedRenderPipeline> {51cache: HashMap<S::Key, CachedRenderPipelineId>,52}5354impl<S: SpecializedRenderPipeline> Default for SpecializedRenderPipelines<S> {55fn default() -> Self {56Self { cache: default() }57}58}5960impl<S: SpecializedRenderPipeline> SpecializedRenderPipelines<S> {61/// Get or create a specialized instance of the pipeline corresponding to `key`.62pub fn specialize(63&mut self,64cache: &PipelineCache,65pipeline_specializer: &S,66key: S::Key,67) -> CachedRenderPipelineId {68*self.cache.entry(key.clone()).or_insert_with(|| {69let descriptor = pipeline_specializer.specialize(key);70cache.queue_render_pipeline(descriptor)71})72}73}7475/// A trait that allows constructing different variants of a compute pipeline from a key.76///77/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key78/// contains no data then you don't need to specialize. For example, if you are using the79/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,80/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and81/// store its ID.82///83/// See [`SpecializedComputePipelines`] for more info.84pub trait SpecializedComputePipeline {85/// The key that defines each "variant" of the compute pipeline.86type Key: Clone + Hash + PartialEq + Eq;8788/// Construct a new compute pipeline based on the provided key.89fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor;90}9192/// A convenience cache for creating different variants of a compute pipeline based on some key.93///94/// Some compute pipelines may need to be configured differently depending on the exact situation.95/// This cache allows constructing different compute pipelines for each situation based on a key,96/// making it easy to A) construct the necessary pipelines, and B) reuse already constructed97/// pipelines.98///99/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key100/// contains no data then you don't need to specialize. For example, if you are using the101/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,102/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and103/// store its ID.104#[derive(Resource)]105pub struct SpecializedComputePipelines<S: SpecializedComputePipeline> {106cache: HashMap<S::Key, CachedComputePipelineId>,107}108109impl<S: SpecializedComputePipeline> Default for SpecializedComputePipelines<S> {110fn default() -> Self {111Self { cache: default() }112}113}114115impl<S: SpecializedComputePipeline> SpecializedComputePipelines<S> {116/// Get or create a specialized instance of the pipeline corresponding to `key`.117pub fn specialize(118&mut self,119cache: &PipelineCache,120specialize_pipeline: &S,121key: S::Key,122) -> CachedComputePipelineId {123*self.cache.entry(key.clone()).or_insert_with(|| {124let descriptor = specialize_pipeline.specialize(key);125cache.queue_compute_pipeline(descriptor)126})127}128}129130/// A trait that allows constructing different variants of a render pipeline from a key and the131/// particular mesh's vertex buffer layout.132///133/// See [`SpecializedMeshPipelines`] for more info.134pub trait SpecializedMeshPipeline {135/// The key that defines each "variant" of the render pipeline.136type Key: Clone + Hash + PartialEq + Eq;137138/// Construct a new render pipeline based on the provided key and vertex layout.139///140/// The returned pipeline descriptor should have a single vertex buffer, which is derived from141/// `layout`.142fn specialize(143&self,144key: Self::Key,145layout: &MeshVertexBufferLayoutRef,146) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError>;147}148149/// A cache of different variants of a render pipeline based on a key and the particular mesh's150/// vertex buffer layout.151#[derive(Resource)]152pub struct SpecializedMeshPipelines<S: SpecializedMeshPipeline> {153mesh_layout_cache: HashMap<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>,154vertex_layout_cache: VertexLayoutCache<S>,155}156157type VertexLayoutCache<S> = HashMap<158VertexBufferLayout,159HashMap<<S as SpecializedMeshPipeline>::Key, CachedRenderPipelineId>,160>;161162impl<S: SpecializedMeshPipeline> Default for SpecializedMeshPipelines<S> {163fn default() -> Self {164Self {165mesh_layout_cache: Default::default(),166vertex_layout_cache: Default::default(),167}168}169}170171impl<S: SpecializedMeshPipeline> SpecializedMeshPipelines<S> {172/// Construct a new render pipeline based on the provided key and the mesh's vertex buffer173/// layout.174#[inline]175pub fn specialize(176&mut self,177cache: &PipelineCache,178pipeline_specializer: &S,179key: S::Key,180layout: &MeshVertexBufferLayoutRef,181) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError> {182return match self.mesh_layout_cache.entry((layout.clone(), key.clone())) {183Entry::Occupied(entry) => Ok(*entry.into_mut()),184Entry::Vacant(entry) => specialize_slow(185&mut self.vertex_layout_cache,186cache,187pipeline_specializer,188key,189layout,190entry,191),192};193194#[cold]195fn specialize_slow<S>(196vertex_layout_cache: &mut VertexLayoutCache<S>,197cache: &PipelineCache,198specialize_pipeline: &S,199key: S::Key,200layout: &MeshVertexBufferLayoutRef,201entry: VacantEntry<202(MeshVertexBufferLayoutRef, S::Key),203CachedRenderPipelineId,204FixedHasher,205>,206) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError>207where208S: SpecializedMeshPipeline,209{210let descriptor = specialize_pipeline211.specialize(key.clone(), layout)212.map_err(|mut err| {213{214let SpecializedMeshPipelineError::MissingVertexAttribute(err) = &mut err;215err.pipeline_type = Some(core::any::type_name::<S>());216}217err218})?;219// Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout220// We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them221let layout_map = match vertex_layout_cache222.raw_entry_mut()223.from_key(&descriptor.vertex.buffers[0])224{225RawEntryMut::Occupied(entry) => entry.into_mut(),226RawEntryMut::Vacant(entry) => {227entry228.insert(descriptor.vertex.buffers[0].clone(), Default::default())229.1230}231};232Ok(*entry.insert(match layout_map.entry(key) {233Entry::Occupied(entry) => {234if cfg!(debug_assertions) {235let stored_descriptor = cache.get_render_pipeline_descriptor(*entry.get());236if stored_descriptor != &descriptor {237error!(238"The cached pipeline descriptor for {} is not \239equal to the generated descriptor for the given key. \240This means the SpecializePipeline implementation uses \241unused' MeshVertexBufferLayout information to specialize \242the pipeline. This is not allowed because it would invalidate \243the pipeline cache.",244core::any::type_name::<S>()245);246}247}248*entry.into_mut()249}250Entry::Vacant(entry) => *entry.insert(cache.queue_render_pipeline(descriptor)),251}))252}253}254}255256257