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
9390 views
1
use bevy_material::descriptor::{
2
CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor,
3
RenderPipelineDescriptor,
4
};
5
6
use crate::render_resource::PipelineCache;
7
use bevy_ecs::resource::Resource;
8
use bevy_log::error;
9
use bevy_material::specialize::SpecializedMeshPipelineError;
10
use bevy_mesh::{MeshVertexBufferLayoutRef, VertexBufferLayout};
11
use bevy_platform::{
12
collections::{
13
hash_map::{Entry, RawEntryMut, VacantEntry},
14
HashMap,
15
},
16
hash::FixedHasher,
17
};
18
use bevy_utils::default;
19
use 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.
30
pub 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)]
51
pub struct SpecializedRenderPipelines<S: SpecializedRenderPipeline> {
52
cache: HashMap<S::Key, CachedRenderPipelineId>,
53
}
54
55
impl<S: SpecializedRenderPipeline> Default for SpecializedRenderPipelines<S> {
56
fn default() -> Self {
57
Self { cache: default() }
58
}
59
}
60
61
impl<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.
85
pub 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)]
106
pub struct SpecializedComputePipelines<S: SpecializedComputePipeline> {
107
cache: HashMap<S::Key, CachedComputePipelineId>,
108
}
109
110
impl<S: SpecializedComputePipeline> Default for SpecializedComputePipelines<S> {
111
fn default() -> Self {
112
Self { cache: default() }
113
}
114
}
115
116
impl<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.
135
pub 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)]
153
pub struct SpecializedMeshPipelines<S: SpecializedMeshPipeline> {
154
mesh_layout_cache: HashMap<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>,
155
vertex_layout_cache: VertexLayoutCache<S>,
156
}
157
158
type VertexLayoutCache<S> = HashMap<
159
VertexBufferLayout,
160
HashMap<<S as SpecializedMeshPipeline>::Key, CachedRenderPipelineId>,
161
>;
162
163
impl<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
172
impl<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
}
256
257