Skip to main content

bevy_render/render_resource/
pipeline_specializer.rs

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