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}