bevy_render/render_resource/
pipeline_specializer.rs

1use crate::render_resource::{
2    CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache,
3    RenderPipelineDescriptor,
4};
5use bevy_ecs::resource::Resource;
6use bevy_mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError, VertexBufferLayout};
7use bevy_platform::{
8    collections::{
9        hash_map::{Entry, RawEntryMut, VacantEntry},
10        HashMap,
11    },
12    hash::FixedHasher,
13};
14use bevy_utils::default;
15use core::{fmt::Debug, hash::Hash};
16use thiserror::Error;
17use tracing::error;
18
19/// A trait that allows constructing different variants of a render pipeline from a key.
20///
21/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key
22/// contains no data then you don't need to specialize. For example, if you are using the
23/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,
24/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and
25/// store its ID.
26///
27/// See [`SpecializedRenderPipelines`] for more info.
28pub trait SpecializedRenderPipeline {
29    /// The key that defines each "variant" of the render pipeline.
30    type Key: Clone + Hash + PartialEq + Eq;
31
32    /// Construct a new render pipeline based on the provided key.
33    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor;
34}
35
36/// A convenience cache for creating different variants of a render pipeline based on some key.
37///
38/// Some render pipelines may need to be configured differently depending on the exact situation.
39/// This cache allows constructing different render pipelines for each situation based on a key,
40/// making it easy to A) construct the necessary pipelines, and B) reuse already constructed
41/// pipelines.
42///
43/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key
44/// contains no data then you don't need to specialize. For example, if you are using the
45/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,
46/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and
47/// store its ID.
48#[derive(Resource)]
49pub struct SpecializedRenderPipelines<S: SpecializedRenderPipeline> {
50    cache: HashMap<S::Key, CachedRenderPipelineId>,
51}
52
53impl<S: SpecializedRenderPipeline> Default for SpecializedRenderPipelines<S> {
54    fn default() -> Self {
55        Self { cache: default() }
56    }
57}
58
59impl<S: SpecializedRenderPipeline> SpecializedRenderPipelines<S> {
60    /// Get or create a specialized instance of the pipeline corresponding to `key`.
61    pub fn specialize(
62        &mut self,
63        cache: &PipelineCache,
64        pipeline_specializer: &S,
65        key: S::Key,
66    ) -> CachedRenderPipelineId {
67        *self.cache.entry(key.clone()).or_insert_with(|| {
68            let descriptor = pipeline_specializer.specialize(key);
69            cache.queue_render_pipeline(descriptor)
70        })
71    }
72}
73
74/// A trait that allows constructing different variants of a compute pipeline from a key.
75///
76/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key
77/// contains no data then you don't need to specialize. For example, if you are using the
78/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,
79/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and
80/// store its ID.
81///
82/// See [`SpecializedComputePipelines`] for more info.
83pub trait SpecializedComputePipeline {
84    /// The key that defines each "variant" of the compute pipeline.
85    type Key: Clone + Hash + PartialEq + Eq;
86
87    /// Construct a new compute pipeline based on the provided key.
88    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor;
89}
90
91/// A convenience cache for creating different variants of a compute pipeline based on some key.
92///
93/// Some compute pipelines may need to be configured differently depending on the exact situation.
94/// This cache allows constructing different compute pipelines for each situation based on a key,
95/// making it easy to A) construct the necessary pipelines, and B) reuse already constructed
96/// pipelines.
97///
98/// Note: This is intended for modifying your pipeline descriptor on the basis of a key. If your key
99/// contains no data then you don't need to specialize. For example, if you are using the
100/// [`AsBindGroup`](crate::render_resource::AsBindGroup) without the `#[bind_group_data]` attribute,
101/// you don't need to specialize. Instead, create the pipeline directly from [`PipelineCache`] and
102/// store its ID.
103#[derive(Resource)]
104pub struct SpecializedComputePipelines<S: SpecializedComputePipeline> {
105    cache: HashMap<S::Key, CachedComputePipelineId>,
106}
107
108impl<S: SpecializedComputePipeline> Default for SpecializedComputePipelines<S> {
109    fn default() -> Self {
110        Self { cache: default() }
111    }
112}
113
114impl<S: SpecializedComputePipeline> SpecializedComputePipelines<S> {
115    /// Get or create a specialized instance of the pipeline corresponding to `key`.
116    pub fn specialize(
117        &mut self,
118        cache: &PipelineCache,
119        specialize_pipeline: &S,
120        key: S::Key,
121    ) -> CachedComputePipelineId {
122        *self.cache.entry(key.clone()).or_insert_with(|| {
123            let descriptor = specialize_pipeline.specialize(key);
124            cache.queue_compute_pipeline(descriptor)
125        })
126    }
127}
128
129/// A trait that allows constructing different variants of a render pipeline from a key and the
130/// particular mesh's vertex buffer layout.
131///
132/// See [`SpecializedMeshPipelines`] for more info.
133pub trait SpecializedMeshPipeline {
134    /// The key that defines each "variant" of the render pipeline.
135    type Key: Clone + Hash + PartialEq + Eq;
136
137    /// Construct a new render pipeline based on the provided key and vertex layout.
138    ///
139    /// The returned pipeline descriptor should have a single vertex buffer, which is derived from
140    /// `layout`.
141    fn specialize(
142        &self,
143        key: Self::Key,
144        layout: &MeshVertexBufferLayoutRef,
145    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError>;
146}
147
148/// A cache of different variants of a render pipeline based on a key and the particular mesh's
149/// vertex buffer layout.
150#[derive(Resource)]
151pub struct SpecializedMeshPipelines<S: SpecializedMeshPipeline> {
152    mesh_layout_cache: HashMap<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>,
153    vertex_layout_cache: VertexLayoutCache<S>,
154}
155
156type VertexLayoutCache<S> = HashMap<
157    VertexBufferLayout,
158    HashMap<<S as SpecializedMeshPipeline>::Key, CachedRenderPipelineId>,
159>;
160
161impl<S: SpecializedMeshPipeline> Default for SpecializedMeshPipelines<S> {
162    fn default() -> Self {
163        Self {
164            mesh_layout_cache: Default::default(),
165            vertex_layout_cache: Default::default(),
166        }
167    }
168}
169
170impl<S: SpecializedMeshPipeline> SpecializedMeshPipelines<S> {
171    /// Construct a new render pipeline based on the provided key and the mesh's vertex buffer
172    /// layout.
173    #[inline]
174    pub fn specialize(
175        &mut self,
176        cache: &PipelineCache,
177        pipeline_specializer: &S,
178        key: S::Key,
179        layout: &MeshVertexBufferLayoutRef,
180    ) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError> {
181        return match self.mesh_layout_cache.entry((layout.clone(), key.clone())) {
182            Entry::Occupied(entry) => Ok(*entry.into_mut()),
183            Entry::Vacant(entry) => specialize_slow(
184                &mut self.vertex_layout_cache,
185                cache,
186                pipeline_specializer,
187                key,
188                layout,
189                entry,
190            ),
191        };
192
193        #[cold]
194        fn specialize_slow<S>(
195            vertex_layout_cache: &mut VertexLayoutCache<S>,
196            cache: &PipelineCache,
197            specialize_pipeline: &S,
198            key: S::Key,
199            layout: &MeshVertexBufferLayoutRef,
200            entry: VacantEntry<
201                (MeshVertexBufferLayoutRef, S::Key),
202                CachedRenderPipelineId,
203                FixedHasher,
204            >,
205        ) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError>
206        where
207            S: SpecializedMeshPipeline,
208        {
209            let descriptor = specialize_pipeline
210                .specialize(key.clone(), layout)
211                .map_err(|mut err| {
212                    {
213                        let SpecializedMeshPipelineError::MissingVertexAttribute(err) = &mut err;
214                        err.pipeline_type = Some(core::any::type_name::<S>());
215                    }
216                    err
217                })?;
218            // Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout
219            // We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them
220            let layout_map = match vertex_layout_cache
221                .raw_entry_mut()
222                .from_key(&descriptor.vertex.buffers[0])
223            {
224                RawEntryMut::Occupied(entry) => entry.into_mut(),
225                RawEntryMut::Vacant(entry) => {
226                    entry
227                        .insert(descriptor.vertex.buffers[0].clone(), Default::default())
228                        .1
229                }
230            };
231            Ok(*entry.insert(match layout_map.entry(key) {
232                Entry::Occupied(entry) => {
233                    if cfg!(debug_assertions) {
234                        let stored_descriptor = cache.get_render_pipeline_descriptor(*entry.get());
235                        if stored_descriptor != &descriptor {
236                            error!(
237                                "The cached pipeline descriptor for {} is not \
238                                    equal to the generated descriptor for the given key. \
239                                    This means the SpecializePipeline implementation uses \
240                                    unused' MeshVertexBufferLayout information to specialize \
241                                    the pipeline. This is not allowed because it would invalidate \
242                                    the pipeline cache.",
243                                core::any::type_name::<S>()
244                            );
245                        }
246                    }
247                    *entry.into_mut()
248                }
249                Entry::Vacant(entry) => *entry.insert(cache.queue_render_pipeline(descriptor)),
250            }))
251        }
252    }
253}
254
255#[derive(Error, Debug)]
256pub enum SpecializedMeshPipelineError {
257    #[error(transparent)]
258    MissingVertexAttribute(#[from] MissingVertexAttributeError),
259}