Path: blob/main/crates/bevy_render/src/render_resource/pipeline_specializer.rs
6596 views
use crate::render_resource::{1CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache,2RenderPipelineDescriptor,3};4use bevy_ecs::resource::Resource;5use bevy_mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError, VertexBufferLayout};6use bevy_platform::{7collections::{8hash_map::{Entry, RawEntryMut, VacantEntry},9HashMap,10},11hash::FixedHasher,12};13use bevy_utils::default;14use core::{fmt::Debug, hash::Hash};15use thiserror::Error;16use tracing::error;1718/// A trait that allows constructing different variants of a render pipeline from a key.19///20/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key21/// contains no data then you don't need to specialize. For example, if you are using the22/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,23/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and24/// store its ID.25///26/// See [`SpecializedRenderPipelines`] for more info.27pub trait SpecializedRenderPipeline {28/// The key that defines each "variant" of the render pipeline.29type Key: Clone + Hash + PartialEq + Eq;3031/// Construct a new render pipeline based on the provided key.32fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor;33}3435/// A convenience cache for creating different variants of a render pipeline based on some key.36///37/// Some render pipelines may need to be configured differently depending on the exact situation.38/// This cache allows constructing different render pipelines for each situation based on a key,39/// making it easy to A) construct the necessary pipelines, and B) reuse already constructed40/// pipelines.41///42/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key43/// contains no data then you don't need to specialize. For example, if you are using the44/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,45/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and46/// store its ID.47#[derive(Resource)]48pub struct SpecializedRenderPipelines<S: SpecializedRenderPipeline> {49cache: HashMap<S::Key, CachedRenderPipelineId>,50}5152impl<S: SpecializedRenderPipeline> Default for SpecializedRenderPipelines<S> {53fn default() -> Self {54Self { cache: default() }55}56}5758impl<S: SpecializedRenderPipeline> SpecializedRenderPipelines<S> {59/// Get or create a specialized instance of the pipeline corresponding to `key`.60pub fn specialize(61&mut self,62cache: &PipelineCache,63pipeline_specializer: &S,64key: S::Key,65) -> CachedRenderPipelineId {66*self.cache.entry(key.clone()).or_insert_with(|| {67let descriptor = pipeline_specializer.specialize(key);68cache.queue_render_pipeline(descriptor)69})70}71}7273/// A trait that allows constructing different variants of a compute pipeline from a key.74///75/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key76/// contains no data then you don't need to specialize. For example, if you are using the77/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,78/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and79/// store its ID.80///81/// See [`SpecializedComputePipelines`] for more info.82pub trait SpecializedComputePipeline {83/// The key that defines each "variant" of the compute pipeline.84type Key: Clone + Hash + PartialEq + Eq;8586/// Construct a new compute pipeline based on the provided key.87fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor;88}8990/// A convenience cache for creating different variants of a compute pipeline based on some key.91///92/// Some compute pipelines may need to be configured differently depending on the exact situation.93/// This cache allows constructing different compute pipelines for each situation based on a key,94/// making it easy to A) construct the necessary pipelines, and B) reuse already constructed95/// pipelines.96///97/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key98/// contains no data then you don't need to specialize. For example, if you are using the99/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,100/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and101/// store its ID.102#[derive(Resource)]103pub struct SpecializedComputePipelines<S: SpecializedComputePipeline> {104cache: HashMap<S::Key, CachedComputePipelineId>,105}106107impl<S: SpecializedComputePipeline> Default for SpecializedComputePipelines<S> {108fn default() -> Self {109Self { cache: default() }110}111}112113impl<S: SpecializedComputePipeline> SpecializedComputePipelines<S> {114/// Get or create a specialized instance of the pipeline corresponding to `key`.115pub fn specialize(116&mut self,117cache: &PipelineCache,118specialize_pipeline: &S,119key: S::Key,120) -> CachedComputePipelineId {121*self.cache.entry(key.clone()).or_insert_with(|| {122let descriptor = specialize_pipeline.specialize(key);123cache.queue_compute_pipeline(descriptor)124})125}126}127128/// A trait that allows constructing different variants of a render pipeline from a key and the129/// particular mesh's vertex buffer layout.130///131/// See [`SpecializedMeshPipelines`] for more info.132pub trait SpecializedMeshPipeline {133/// The key that defines each "variant" of the render pipeline.134type Key: Clone + Hash + PartialEq + Eq;135136/// Construct a new render pipeline based on the provided key and vertex layout.137///138/// The returned pipeline descriptor should have a single vertex buffer, which is derived from139/// `layout`.140fn specialize(141&self,142key: Self::Key,143layout: &MeshVertexBufferLayoutRef,144) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError>;145}146147/// A cache of different variants of a render pipeline based on a key and the particular mesh's148/// vertex buffer layout.149#[derive(Resource)]150pub struct SpecializedMeshPipelines<S: SpecializedMeshPipeline> {151mesh_layout_cache: HashMap<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>,152vertex_layout_cache: VertexLayoutCache<S>,153}154155type VertexLayoutCache<S> = HashMap<156VertexBufferLayout,157HashMap<<S as SpecializedMeshPipeline>::Key, CachedRenderPipelineId>,158>;159160impl<S: SpecializedMeshPipeline> Default for SpecializedMeshPipelines<S> {161fn default() -> Self {162Self {163mesh_layout_cache: Default::default(),164vertex_layout_cache: Default::default(),165}166}167}168169impl<S: SpecializedMeshPipeline> SpecializedMeshPipelines<S> {170/// Construct a new render pipeline based on the provided key and the mesh's vertex buffer171/// layout.172#[inline]173pub fn specialize(174&mut self,175cache: &PipelineCache,176pipeline_specializer: &S,177key: S::Key,178layout: &MeshVertexBufferLayoutRef,179) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError> {180return match self.mesh_layout_cache.entry((layout.clone(), key.clone())) {181Entry::Occupied(entry) => Ok(*entry.into_mut()),182Entry::Vacant(entry) => specialize_slow(183&mut self.vertex_layout_cache,184cache,185pipeline_specializer,186key,187layout,188entry,189),190};191192#[cold]193fn specialize_slow<S>(194vertex_layout_cache: &mut VertexLayoutCache<S>,195cache: &PipelineCache,196specialize_pipeline: &S,197key: S::Key,198layout: &MeshVertexBufferLayoutRef,199entry: VacantEntry<200(MeshVertexBufferLayoutRef, S::Key),201CachedRenderPipelineId,202FixedHasher,203>,204) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError>205where206S: SpecializedMeshPipeline,207{208let descriptor = specialize_pipeline209.specialize(key.clone(), layout)210.map_err(|mut err| {211{212let SpecializedMeshPipelineError::MissingVertexAttribute(err) = &mut err;213err.pipeline_type = Some(core::any::type_name::<S>());214}215err216})?;217// Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout218// We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them219let layout_map = match vertex_layout_cache220.raw_entry_mut()221.from_key(&descriptor.vertex.buffers[0])222{223RawEntryMut::Occupied(entry) => entry.into_mut(),224RawEntryMut::Vacant(entry) => {225entry226.insert(descriptor.vertex.buffers[0].clone(), Default::default())227.1228}229};230Ok(*entry.insert(match layout_map.entry(key) {231Entry::Occupied(entry) => {232if cfg!(debug_assertions) {233let stored_descriptor = cache.get_render_pipeline_descriptor(*entry.get());234if stored_descriptor != &descriptor {235error!(236"The cached pipeline descriptor for {} is not \237equal to the generated descriptor for the given key. \238This means the SpecializePipeline implementation uses \239unused' MeshVertexBufferLayout information to specialize \240the pipeline. This is not allowed because it would invalidate \241the pipeline cache.",242core::any::type_name::<S>()243);244}245}246*entry.into_mut()247}248Entry::Vacant(entry) => *entry.insert(cache.queue_render_pipeline(descriptor)),249}))250}251}252}253254#[derive(Error, Debug)]255pub enum SpecializedMeshPipelineError {256#[error(transparent)]257MissingVertexAttribute(#[from] MissingVertexAttributeError),258}259260261