Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/render_resource/pipeline_specializer.rs
6596 views
1
use crate::render_resource::{
2
CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache,
3
RenderPipelineDescriptor,
4
};
5
use bevy_ecs::resource::Resource;
6
use bevy_mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError, VertexBufferLayout};
7
use bevy_platform::{
8
collections::{
9
hash_map::{Entry, RawEntryMut, VacantEntry},
10
HashMap,
11
},
12
hash::FixedHasher,
13
};
14
use bevy_utils::default;
15
use core::{fmt::Debug, hash::Hash};
16
use thiserror::Error;
17
use 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.
28
pub 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)]
49
pub struct SpecializedRenderPipelines<S: SpecializedRenderPipeline> {
50
cache: HashMap<S::Key, CachedRenderPipelineId>,
51
}
52
53
impl<S: SpecializedRenderPipeline> Default for SpecializedRenderPipelines<S> {
54
fn default() -> Self {
55
Self { cache: default() }
56
}
57
}
58
59
impl<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.
83
pub 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)]
104
pub struct SpecializedComputePipelines<S: SpecializedComputePipeline> {
105
cache: HashMap<S::Key, CachedComputePipelineId>,
106
}
107
108
impl<S: SpecializedComputePipeline> Default for SpecializedComputePipelines<S> {
109
fn default() -> Self {
110
Self { cache: default() }
111
}
112
}
113
114
impl<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.
133
pub 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)]
151
pub struct SpecializedMeshPipelines<S: SpecializedMeshPipeline> {
152
mesh_layout_cache: HashMap<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>,
153
vertex_layout_cache: VertexLayoutCache<S>,
154
}
155
156
type VertexLayoutCache<S> = HashMap<
157
VertexBufferLayout,
158
HashMap<<S as SpecializedMeshPipeline>::Key, CachedRenderPipelineId>,
159
>;
160
161
impl<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
170
impl<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)]
256
pub enum SpecializedMeshPipelineError {
257
#[error(transparent)]
258
MissingVertexAttribute(#[from] MissingVertexAttributeError),
259
}
260
261