Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/material.rs
9358 views
1
use crate::material_bind_groups::{
2
FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId,
3
};
4
use crate::*;
5
use alloc::sync::Arc;
6
use bevy_asset::prelude::AssetChanged;
7
use bevy_asset::{Asset, AssetEventSystems, AssetId, AssetServer, UntypedAssetId};
8
use bevy_camera::visibility::ViewVisibility;
9
use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred};
10
use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass};
11
use bevy_core_pipeline::{
12
core_3d::{AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, Transparent3d},
13
prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey},
14
tonemapping::Tonemapping,
15
};
16
use bevy_derive::{Deref, DerefMut};
17
use bevy_ecs::change_detection::Tick;
18
use bevy_ecs::system::{SystemChangeTick, SystemParam};
19
use bevy_ecs::{
20
prelude::*,
21
system::{
22
lifetimeless::{SRes, SResMut},
23
SystemParamItem, SystemState,
24
},
25
};
26
use bevy_material::{
27
key::{ErasedMaterialKey, ErasedMaterialPipelineKey, ErasedMeshPipelineKey},
28
labels::{DrawFunctionLabel, InternedShaderLabel, ShaderLabel},
29
MaterialProperties, OpaqueRendererMethod, RenderPhaseType,
30
};
31
use bevy_mesh::{
32
mark_3d_meshes_as_changed_if_their_assets_changed, Mesh3d, MeshVertexBufferLayoutRef,
33
};
34
use bevy_platform::collections::hash_map::Entry;
35
use bevy_platform::collections::{HashMap, HashSet};
36
use bevy_platform::hash::FixedHasher;
37
use bevy_reflect::std_traits::ReflectDefault;
38
use bevy_reflect::Reflect;
39
use bevy_render::camera::extract_cameras;
40
use bevy_render::erased_render_asset::{
41
ErasedRenderAsset, ErasedRenderAssetPlugin, ErasedRenderAssets, PrepareAssetError,
42
};
43
use bevy_render::render_asset::{prepare_assets, RenderAssets};
44
use bevy_render::renderer::RenderQueue;
45
use bevy_render::RenderStartup;
46
use bevy_render::{
47
batching::gpu_preprocessing::GpuPreprocessingSupport,
48
extract_resource::ExtractResource,
49
mesh::RenderMesh,
50
prelude::*,
51
render_phase::*,
52
render_resource::*,
53
renderer::RenderDevice,
54
sync_world::MainEntity,
55
view::{ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity},
56
Extract,
57
};
58
use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap};
59
use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities};
60
use bevy_shader::ShaderDefVal;
61
use bevy_utils::Parallel;
62
use core::{
63
any::{Any, TypeId},
64
hash::Hash,
65
marker::PhantomData,
66
};
67
use smallvec::SmallVec;
68
use tracing::error;
69
70
pub const MATERIAL_BIND_GROUP_INDEX: usize = 3;
71
72
/// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`]
73
/// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level
74
/// way to render [`Mesh3d`] entities with custom shader logic.
75
///
76
/// Materials must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders.
77
/// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details.
78
///
79
/// # Example
80
///
81
/// Here is a simple [`Material`] implementation. The [`AsBindGroup`] derive has many features. To see what else is available,
82
/// check out the [`AsBindGroup`] documentation.
83
///
84
/// ```
85
/// # use bevy_pbr::{Material, MeshMaterial3d};
86
/// # use bevy_ecs::prelude::*;
87
/// # use bevy_image::Image;
88
/// # use bevy_reflect::TypePath;
89
/// # use bevy_mesh::{Mesh, Mesh3d};
90
/// # use bevy_render::render_resource::AsBindGroup;
91
/// # use bevy_shader::ShaderRef;
92
/// # use bevy_color::LinearRgba;
93
/// # use bevy_color::palettes::basic::RED;
94
/// # use bevy_asset::{Handle, AssetServer, Assets, Asset};
95
/// # use bevy_math::primitives::Capsule3d;
96
/// #
97
/// #[derive(AsBindGroup, Debug, Clone, Asset, TypePath)]
98
/// pub struct CustomMaterial {
99
/// // Uniform bindings must implement `ShaderType`, which will be used to convert the value to
100
/// // its shader-compatible equivalent. Most core math types already implement `ShaderType`.
101
/// #[uniform(0)]
102
/// color: LinearRgba,
103
/// // Images can be bound as textures in shaders. If the Image's sampler is also needed, just
104
/// // add the sampler attribute with a different binding index.
105
/// #[texture(1)]
106
/// #[sampler(2)]
107
/// color_texture: Handle<Image>,
108
/// }
109
///
110
/// // All functions on `Material` have default impls. You only need to implement the
111
/// // functions that are relevant for your material.
112
/// impl Material for CustomMaterial {
113
/// fn fragment_shader() -> ShaderRef {
114
/// "shaders/custom_material.wgsl".into()
115
/// }
116
/// }
117
///
118
/// // Spawn an entity with a mesh using `CustomMaterial`.
119
/// fn setup(
120
/// mut commands: Commands,
121
/// mut meshes: ResMut<Assets<Mesh>>,
122
/// mut materials: ResMut<Assets<CustomMaterial>>,
123
/// asset_server: Res<AssetServer>
124
/// ) {
125
/// commands.spawn((
126
/// Mesh3d(meshes.add(Capsule3d::default())),
127
/// MeshMaterial3d(materials.add(CustomMaterial {
128
/// color: RED.into(),
129
/// color_texture: asset_server.load("some_image.png"),
130
/// })),
131
/// ));
132
/// }
133
/// ```
134
///
135
/// In WGSL shaders, the material's binding would look like this:
136
///
137
/// ```wgsl
138
/// @group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> color: vec4<f32>;
139
/// @group(#{MATERIAL_BIND_GROUP}) @binding(1) var color_texture: texture_2d<f32>;
140
/// @group(#{MATERIAL_BIND_GROUP}) @binding(2) var color_sampler: sampler;
141
/// ```
142
pub trait Material: Asset + AsBindGroup + Clone + Sized {
143
/// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader
144
/// will be used.
145
fn vertex_shader() -> ShaderRef {
146
ShaderRef::Default
147
}
148
149
/// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader
150
/// will be used.
151
fn fragment_shader() -> ShaderRef {
152
ShaderRef::Default
153
}
154
155
/// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`].
156
#[inline]
157
fn alpha_mode(&self) -> AlphaMode {
158
AlphaMode::Opaque
159
}
160
161
/// Returns if this material should be rendered by the deferred or forward renderer.
162
/// for `AlphaMode::Opaque` or `AlphaMode::Mask` materials.
163
/// If `OpaqueRendererMethod::Auto`, it will default to what is selected in the `DefaultOpaqueRendererMethod` resource.
164
#[inline]
165
fn opaque_render_method(&self) -> OpaqueRendererMethod {
166
OpaqueRendererMethod::Forward
167
}
168
169
#[inline]
170
/// Add a bias to the view depth of the mesh which can be used to force a specific render order.
171
/// for meshes with similar depth, to avoid z-fighting.
172
/// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
173
fn depth_bias(&self) -> f32 {
174
0.0
175
}
176
177
#[inline]
178
/// Returns whether the material would like to read from [`ViewTransmissionTexture`].
179
///
180
/// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
181
/// rendering to take place in a separate [`Transmissive3d`] pass.
182
fn reads_view_transmission_texture(&self) -> bool {
183
false
184
}
185
186
/// Controls if the prepass is enabled for the Material.
187
/// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs.
188
#[inline]
189
fn enable_prepass() -> bool {
190
true
191
}
192
193
/// Controls if shadows are enabled for the Material.
194
#[inline]
195
fn enable_shadows() -> bool {
196
true
197
}
198
199
/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader
200
/// will be used.
201
///
202
/// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps
203
/// required for shadow mapping.
204
fn prepass_vertex_shader() -> ShaderRef {
205
ShaderRef::Default
206
}
207
208
/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the default prepass fragment shader
209
/// will be used.
210
///
211
/// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps
212
/// required for shadow mapping.
213
fn prepass_fragment_shader() -> ShaderRef {
214
ShaderRef::Default
215
}
216
217
/// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the default deferred vertex shader
218
/// will be used.
219
fn deferred_vertex_shader() -> ShaderRef {
220
ShaderRef::Default
221
}
222
223
/// Returns this material's deferred fragment shader. If [`ShaderRef::Default`] is returned, the default deferred fragment shader
224
/// will be used.
225
fn deferred_fragment_shader() -> ShaderRef {
226
ShaderRef::Default
227
}
228
229
/// Returns this material's [`crate::meshlet::MeshletMesh`] fragment shader. If [`ShaderRef::Default`] is returned,
230
/// the default meshlet mesh fragment shader will be used.
231
///
232
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
233
///
234
/// See [`crate::meshlet::MeshletMesh`] for limitations.
235
#[cfg(feature = "meshlet")]
236
fn meshlet_mesh_fragment_shader() -> ShaderRef {
237
ShaderRef::Default
238
}
239
240
/// Returns this material's [`crate::meshlet::MeshletMesh`] prepass fragment shader. If [`ShaderRef::Default`] is returned,
241
/// the default meshlet mesh prepass fragment shader will be used.
242
///
243
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
244
///
245
/// See [`crate::meshlet::MeshletMesh`] for limitations.
246
#[cfg(feature = "meshlet")]
247
fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
248
ShaderRef::Default
249
}
250
251
/// Returns this material's [`crate::meshlet::MeshletMesh`] deferred fragment shader. If [`ShaderRef::Default`] is returned,
252
/// the default meshlet mesh deferred fragment shader will be used.
253
///
254
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
255
///
256
/// See [`crate::meshlet::MeshletMesh`] for limitations.
257
#[cfg(feature = "meshlet")]
258
fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
259
ShaderRef::Default
260
}
261
262
/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
263
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input.
264
#[expect(
265
unused_variables,
266
reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion."
267
)]
268
#[inline]
269
fn specialize(
270
pipeline: &MaterialPipeline,
271
descriptor: &mut RenderPipelineDescriptor,
272
layout: &MeshVertexBufferLayoutRef,
273
key: MaterialPipelineKey<Self>,
274
) -> Result<(), SpecializedMeshPipelineError> {
275
Ok(())
276
}
277
}
278
279
#[derive(Default)]
280
pub struct MaterialsPlugin {
281
/// Debugging flags that can optionally be set when constructing the renderer.
282
pub debug_flags: RenderDebugFlags,
283
}
284
285
impl Plugin for MaterialsPlugin {
286
fn build(&self, app: &mut App) {
287
app.add_plugins((PrepassPipelinePlugin, PrepassPlugin::new(self.debug_flags)));
288
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
289
render_app
290
.init_resource::<EntitySpecializationTicks>()
291
.init_resource::<SpecializedMaterialPipelineCache>()
292
.init_resource::<SpecializedMeshPipelines<MaterialPipelineSpecializer>>()
293
.init_resource::<LightKeyCache>()
294
.init_resource::<LightSpecializationTicks>()
295
.init_resource::<SpecializedShadowMaterialPipelineCache>()
296
.init_resource::<DrawFunctions<Shadow>>()
297
.init_resource::<RenderMaterialInstances>()
298
.init_resource::<MaterialBindGroupAllocators>()
299
.add_render_command::<Shadow, DrawPrepass>()
300
.add_render_command::<Shadow, DrawDepthOnlyPrepass>()
301
.add_render_command::<Transparent3d, DrawMaterial>()
302
.add_render_command::<Opaque3d, DrawMaterial>()
303
.add_render_command::<AlphaMask3d, DrawMaterial>()
304
.add_systems(RenderStartup, init_material_pipeline)
305
.add_systems(
306
Render,
307
(
308
specialize_material_meshes
309
.in_set(RenderSystems::PrepareMeshes)
310
.after(prepare_assets::<RenderMesh>)
311
.after(collect_meshes_for_gpu_building)
312
.after(set_mesh_motion_vector_flags),
313
queue_material_meshes.in_set(RenderSystems::QueueMeshes),
314
),
315
)
316
.add_systems(
317
Render,
318
(
319
prepare_material_bind_groups,
320
write_material_bind_group_buffers,
321
)
322
.chain()
323
.in_set(RenderSystems::PrepareBindGroups),
324
)
325
.add_systems(
326
Render,
327
(
328
check_views_lights_need_specialization.in_set(RenderSystems::PrepareAssets),
329
// specialize_shadows also needs to run after prepare_assets::<PreparedMaterial>,
330
// which is fine since ManageViews is after PrepareAssets
331
specialize_shadows
332
.in_set(RenderSystems::ManageViews)
333
.after(prepare_lights),
334
queue_shadows.in_set(RenderSystems::QueueMeshes),
335
),
336
);
337
}
338
}
339
}
340
341
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`]
342
/// asset type.
343
pub struct MaterialPlugin<M: Material> {
344
/// Debugging flags that can optionally be set when constructing the renderer.
345
pub debug_flags: RenderDebugFlags,
346
pub _marker: PhantomData<M>,
347
}
348
349
impl<M: Material> Default for MaterialPlugin<M> {
350
fn default() -> Self {
351
Self {
352
debug_flags: RenderDebugFlags::default(),
353
_marker: Default::default(),
354
}
355
}
356
}
357
358
impl<M: Material> Plugin for MaterialPlugin<M>
359
where
360
M::Data: PartialEq + Eq + Hash + Clone,
361
{
362
fn build(&self, app: &mut App) {
363
app.init_asset::<M>()
364
.register_type::<MeshMaterial3d<M>>()
365
.init_resource::<EntitiesNeedingSpecialization<M>>()
366
.add_plugins((ErasedRenderAssetPlugin::<MeshMaterial3d<M>>::default(),))
367
.add_systems(
368
PostUpdate,
369
(
370
mark_meshes_as_changed_if_their_materials_changed::<M>.ambiguous_with_all(),
371
check_entities_needing_specialization::<M>.after(AssetEventSystems),
372
)
373
.after(mark_3d_meshes_as_changed_if_their_assets_changed),
374
);
375
376
if M::enable_shadows() {
377
app.add_systems(
378
PostUpdate,
379
check_light_entities_needing_specialization::<M>
380
.after(check_entities_needing_specialization::<M>),
381
);
382
}
383
384
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
385
render_app
386
.add_systems(RenderStartup, add_material_bind_group_allocator::<M>)
387
.add_systems(
388
ExtractSchedule,
389
(
390
extract_mesh_materials::<M>.in_set(MaterialExtractionSystems),
391
early_sweep_material_instances::<M>
392
.after(MaterialExtractionSystems)
393
.before(late_sweep_material_instances),
394
// See the comments in
395
// `sweep_entities_needing_specialization` for an
396
// explanation of why the systems are ordered this way.
397
extract_entities_needs_specialization::<M>
398
.in_set(MaterialExtractEntitiesNeedingSpecializationSystems),
399
sweep_entities_needing_specialization::<M>
400
.after(MaterialExtractEntitiesNeedingSpecializationSystems)
401
.after(MaterialExtractionSystems)
402
.after(extract_cameras)
403
.before(late_sweep_material_instances),
404
),
405
);
406
}
407
}
408
}
409
410
fn add_material_bind_group_allocator<M: Material>(
411
render_device: Res<RenderDevice>,
412
mut bind_group_allocators: ResMut<MaterialBindGroupAllocators>,
413
) {
414
bind_group_allocators.insert(
415
TypeId::of::<M>(),
416
MaterialBindGroupAllocator::new(
417
&render_device,
418
M::label(),
419
material_uses_bindless_resources::<M>(&render_device)
420
.then(|| M::bindless_descriptor())
421
.flatten(),
422
M::bind_group_layout_descriptor(&render_device),
423
M::bindless_slot_count(),
424
),
425
);
426
}
427
428
/// A dummy [`AssetId`] that we use as a placeholder whenever a mesh doesn't
429
/// have a material.
430
///
431
/// See the comments in [`RenderMaterialInstances::mesh_material`] for more
432
/// information.
433
pub(crate) static DUMMY_MESH_MATERIAL: AssetId<StandardMaterial> =
434
AssetId::<StandardMaterial>::invalid();
435
436
/// A key uniquely identifying a specialized [`MaterialPipeline`].
437
pub struct MaterialPipelineKey<M: Material> {
438
pub mesh_key: MeshPipelineKey,
439
pub bind_group_data: M::Data,
440
}
441
442
/// Render pipeline data for a given [`Material`].
443
#[derive(Resource, Clone)]
444
pub struct MaterialPipeline {
445
pub mesh_pipeline: MeshPipeline,
446
}
447
448
pub struct MaterialPipelineSpecializer {
449
pub(crate) pipeline: MaterialPipeline,
450
pub(crate) properties: Arc<MaterialProperties>,
451
}
452
453
impl SpecializedMeshPipeline for MaterialPipelineSpecializer {
454
type Key = ErasedMaterialPipelineKey;
455
456
fn specialize(
457
&self,
458
key: Self::Key,
459
layout: &MeshVertexBufferLayoutRef,
460
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
461
let concrete_mesh_key: MeshPipelineKey = key.mesh_key.downcast();
462
let mut descriptor = self
463
.pipeline
464
.mesh_pipeline
465
.specialize(concrete_mesh_key, layout)?;
466
467
descriptor.vertex.shader_defs.push(ShaderDefVal::UInt(
468
"MATERIAL_BIND_GROUP".into(),
469
MATERIAL_BIND_GROUP_INDEX as u32,
470
));
471
if let Some(ref mut fragment) = descriptor.fragment {
472
fragment.shader_defs.push(ShaderDefVal::UInt(
473
"MATERIAL_BIND_GROUP".into(),
474
MATERIAL_BIND_GROUP_INDEX as u32,
475
));
476
};
477
if let Some(vertex_shader) = self.properties.get_shader(MaterialVertexShader) {
478
descriptor.vertex.shader = vertex_shader.clone();
479
}
480
481
if let Some(fragment_shader) = self.properties.get_shader(MaterialFragmentShader) {
482
descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
483
}
484
485
descriptor
486
.layout
487
.insert(3, self.properties.material_layout.as_ref().unwrap().clone());
488
489
if let Some(specialize) = self.properties.user_specialize {
490
specialize(&self.pipeline as &dyn Any, &mut descriptor, layout, key)?;
491
}
492
493
// If bindless mode is on, add a `BINDLESS` define.
494
if self.properties.bindless {
495
descriptor.vertex.shader_defs.push("BINDLESS".into());
496
if let Some(ref mut fragment) = descriptor.fragment {
497
fragment.shader_defs.push("BINDLESS".into());
498
}
499
}
500
501
Ok(descriptor)
502
}
503
}
504
505
pub fn init_material_pipeline(mut commands: Commands, mesh_pipeline: Res<MeshPipeline>) {
506
commands.insert_resource(MaterialPipeline {
507
mesh_pipeline: mesh_pipeline.clone(),
508
});
509
}
510
511
pub type DrawMaterial = (
512
SetItemPipeline,
513
SetMeshViewBindGroup<0>,
514
SetMeshViewBindingArrayBindGroup<1>,
515
SetMeshBindGroup<2>,
516
SetMaterialBindGroup<MATERIAL_BIND_GROUP_INDEX>,
517
DrawMesh,
518
);
519
520
/// Sets the bind group for a given [`Material`] at the configured `I` index.
521
pub struct SetMaterialBindGroup<const I: usize>;
522
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMaterialBindGroup<I> {
523
type Param = (
524
SRes<ErasedRenderAssets<PreparedMaterial>>,
525
SRes<RenderMaterialInstances>,
526
SRes<MaterialBindGroupAllocators>,
527
);
528
type ViewQuery = ();
529
type ItemQuery = ();
530
531
#[inline]
532
fn render<'w>(
533
item: &P,
534
_view: (),
535
_item_query: Option<()>,
536
(materials, material_instances, material_bind_group_allocator): SystemParamItem<
537
'w,
538
'_,
539
Self::Param,
540
>,
541
pass: &mut TrackedRenderPass<'w>,
542
) -> RenderCommandResult {
543
let materials = materials.into_inner();
544
let material_instances = material_instances.into_inner();
545
let material_bind_group_allocators = material_bind_group_allocator.into_inner();
546
547
let Some(material_instance) = material_instances.instances.get(&item.main_entity()) else {
548
return RenderCommandResult::Skip;
549
};
550
let Some(material_bind_group_allocator) =
551
material_bind_group_allocators.get(&material_instance.asset_id.type_id())
552
else {
553
return RenderCommandResult::Skip;
554
};
555
let Some(material) = materials.get(material_instance.asset_id) else {
556
return RenderCommandResult::Skip;
557
};
558
let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group)
559
else {
560
return RenderCommandResult::Skip;
561
};
562
let Some(bind_group) = material_bind_group.bind_group() else {
563
return RenderCommandResult::Skip;
564
};
565
pass.set_bind_group(I, bind_group, &[]);
566
RenderCommandResult::Success
567
}
568
}
569
570
/// Stores all extracted instances of all [`Material`]s in the render world.
571
#[derive(Resource, Default)]
572
pub struct RenderMaterialInstances {
573
/// Maps from each entity in the main world to the
574
/// [`RenderMaterialInstance`] associated with it.
575
pub instances: MainEntityHashMap<RenderMaterialInstance>,
576
/// A monotonically-increasing counter, which we use to sweep
577
/// [`RenderMaterialInstances::instances`] when the entities and/or required
578
/// components are removed.
579
pub current_change_tick: Tick,
580
}
581
582
impl RenderMaterialInstances {
583
/// Returns the mesh material ID for the entity with the given mesh, or a
584
/// dummy mesh material ID if the mesh has no material ID.
585
///
586
/// Meshes almost always have materials, but in very specific circumstances
587
/// involving custom pipelines they won't. (See the
588
/// `specialized_mesh_pipelines` example.)
589
pub(crate) fn mesh_material(&self, entity: MainEntity) -> UntypedAssetId {
590
match self.instances.get(&entity) {
591
Some(render_instance) => render_instance.asset_id,
592
None => DUMMY_MESH_MATERIAL.into(),
593
}
594
}
595
}
596
597
/// The material associated with a single mesh instance in the main world.
598
///
599
/// Note that this uses an [`UntypedAssetId`] and isn't generic over the
600
/// material type, for simplicity.
601
pub struct RenderMaterialInstance {
602
/// The material asset.
603
pub asset_id: UntypedAssetId,
604
/// The [`RenderMaterialInstances::current_change_tick`] at which this
605
/// material instance was last modified.
606
pub last_change_tick: Tick,
607
}
608
609
/// A [`SystemSet`] that contains all `extract_mesh_materials` systems.
610
#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
611
pub struct MaterialExtractionSystems;
612
613
/// A [`SystemSet`] that contains all `extract_entities_needs_specialization`
614
/// systems.
615
#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
616
pub struct MaterialExtractEntitiesNeedingSpecializationSystems;
617
618
pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey {
619
match alpha_mode {
620
// Premultiplied and Add share the same pipeline key
621
// They're made distinct in the PBR shader, via `premultiply_alpha()`
622
AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA,
623
AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA,
624
AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY,
625
AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD,
626
AlphaMode::AlphaToCoverage => match *msaa {
627
Msaa::Off => MeshPipelineKey::MAY_DISCARD,
628
_ => MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE,
629
},
630
_ => MeshPipelineKey::NONE,
631
}
632
}
633
634
pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey {
635
match tonemapping {
636
Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE,
637
Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD,
638
Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE,
639
Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED,
640
Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX,
641
Tonemapping::SomewhatBoringDisplayTransform => {
642
MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
643
}
644
Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
645
Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
646
}
647
}
648
649
/// A system that ensures that
650
/// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts meshes
651
/// whose materials changed.
652
///
653
/// As [`crate::render::mesh::collect_meshes_for_gpu_building`] only considers
654
/// meshes that were newly extracted, and it writes information from the
655
/// [`RenderMaterialInstances`] into the
656
/// [`crate::render::mesh::MeshInputUniform`], we must tell
657
/// [`crate::render::mesh::extract_meshes_for_gpu_building`] to re-extract a
658
/// mesh if its material changed. Otherwise, the material binding information in
659
/// the [`crate::render::mesh::MeshInputUniform`] might not be updated properly.
660
/// The easiest way to ensure that
661
/// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts a mesh
662
/// is to mark its [`Mesh3d`] as changed, so that's what this system does.
663
fn mark_meshes_as_changed_if_their_materials_changed<M>(
664
mut changed_meshes_query: Query<
665
&mut Mesh3d,
666
Or<(Changed<MeshMaterial3d<M>>, AssetChanged<MeshMaterial3d<M>>)>,
667
>,
668
) where
669
M: Material,
670
{
671
for mut mesh in &mut changed_meshes_query {
672
mesh.set_changed();
673
}
674
}
675
676
/// Fills the [`RenderMaterialInstances`] resources from the meshes in the
677
/// scene.
678
fn extract_mesh_materials<M: Material>(
679
mut material_instances: ResMut<RenderMaterialInstances>,
680
changed_meshes_query: Extract<
681
Query<
682
(Entity, &ViewVisibility, &MeshMaterial3d<M>),
683
Or<(Changed<ViewVisibility>, Changed<MeshMaterial3d<M>>)>,
684
>,
685
>,
686
) {
687
let last_change_tick = material_instances.current_change_tick;
688
689
for (entity, view_visibility, material) in &changed_meshes_query {
690
if view_visibility.get() {
691
material_instances.instances.insert(
692
entity.into(),
693
RenderMaterialInstance {
694
asset_id: material.id().untyped(),
695
last_change_tick,
696
},
697
);
698
} else {
699
material_instances
700
.instances
701
.remove(&MainEntity::from(entity));
702
}
703
}
704
}
705
706
/// Removes mesh materials from [`RenderMaterialInstances`] when their
707
/// [`MeshMaterial3d`] components are removed.
708
///
709
/// This is tricky because we have to deal with the case in which a material of
710
/// type A was removed and replaced with a material of type B in the same frame
711
/// (which is actually somewhat common of an operation). In this case, even
712
/// though an entry will be present in `RemovedComponents<MeshMaterial3d<A>>`,
713
/// we must not remove the entry in `RenderMaterialInstances` which corresponds
714
/// to material B. To handle this case, we use change ticks to avoid removing
715
/// the entry if it was updated this frame.
716
///
717
/// This is the first of two sweep phases. Because this phase runs once per
718
/// material type, we need a second phase in order to guarantee that we only
719
/// bump [`RenderMaterialInstances::current_change_tick`] once.
720
fn early_sweep_material_instances<M>(
721
mut material_instances: ResMut<RenderMaterialInstances>,
722
mut removed_materials_query: Extract<RemovedComponents<MeshMaterial3d<M>>>,
723
) where
724
M: Material,
725
{
726
let last_change_tick = material_instances.current_change_tick;
727
728
for entity in removed_materials_query.read() {
729
if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
730
// Only sweep the entry if it wasn't updated this frame.
731
if occupied_entry.get().last_change_tick != last_change_tick {
732
occupied_entry.remove();
733
}
734
}
735
}
736
}
737
738
/// Removes mesh materials from [`RenderMaterialInstances`] when their
739
/// [`ViewVisibility`] components are removed.
740
///
741
/// This runs after all invocations of `early_sweep_material_instances` and is
742
/// responsible for bumping [`RenderMaterialInstances::current_change_tick`] in
743
/// preparation for a new frame.
744
pub fn late_sweep_material_instances(
745
mut material_instances: ResMut<RenderMaterialInstances>,
746
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
747
) {
748
let last_change_tick = material_instances.current_change_tick;
749
750
for entity in removed_meshes_query.read() {
751
if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
752
// Only sweep the entry if it wasn't updated this frame. It's
753
// possible that a `ViewVisibility` component was removed and
754
// re-added in the same frame.
755
if occupied_entry.get().last_change_tick != last_change_tick {
756
occupied_entry.remove();
757
}
758
}
759
}
760
761
material_instances
762
.current_change_tick
763
.set(last_change_tick.get() + 1);
764
}
765
766
pub fn extract_entities_needs_specialization<M>(
767
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
768
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
769
render_material_instances: Res<RenderMaterialInstances>,
770
ticks: SystemChangeTick,
771
) where
772
M: Material,
773
{
774
for entity in entities_needing_specialization.iter() {
775
// Update the entity's specialization tick with this run's tick
776
entity_specialization_ticks.insert(
777
(*entity).into(),
778
EntitySpecializationTickPair {
779
system_tick: ticks.this_run(),
780
material_instances_tick: render_material_instances.current_change_tick,
781
},
782
);
783
}
784
}
785
786
/// A system that runs after all instances of
787
/// [`extract_entities_needs_specialization`] in order to delete specialization
788
/// ticks for entities that are no longer renderable.
789
///
790
/// We delete entities from the [`EntitySpecializationTicks`] table *after*
791
/// updating it with newly-discovered renderable entities in order to handle the
792
/// case in which a single entity changes material types. If we naïvely removed
793
/// entities from that table when their [`MeshMaterial3d<M>`] components were
794
/// removed, and an entity changed material types, we might end up adding a new
795
/// set of [`EntitySpecializationTickPair`] for the new material and then
796
/// deleting it upon detecting the removed component for the old material.
797
/// Deferring [`sweep_entities_needing_specialization`] to the end allows us to
798
/// detect the case in which another material type updated the entity
799
/// specialization ticks this frame and avoid deleting it if so.
800
pub fn sweep_entities_needing_specialization<M>(
801
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks>,
802
mut removed_mesh_material_components: Extract<RemovedComponents<MeshMaterial3d<M>>>,
803
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
804
mut specialized_prepass_material_pipeline_cache: Option<
805
ResMut<SpecializedPrepassMaterialPipelineCache>,
806
>,
807
mut specialized_shadow_material_pipeline_cache: Option<
808
ResMut<SpecializedShadowMaterialPipelineCache>,
809
>,
810
render_material_instances: Res<RenderMaterialInstances>,
811
views: Query<&ExtractedView>,
812
) where
813
M: Material,
814
{
815
// Clean up any despawned entities, we do this first in case the removed material was re-added
816
// the same frame, thus will appear both in the removed components list and have been added to
817
// the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter
818
//
819
// Additionally, we need to make sure that we are careful about materials
820
// that could have changed type, e.g. from a `StandardMaterial` to a
821
// `CustomMaterial`, as this will also appear in the removed components
822
// list. As such, we make sure that this system runs after
823
// `extract_entities_needs_specialization` so that the entity specialization
824
// tick bookkeeping has already been done, and we can check if the entity's
825
// tick was updated this frame.
826
for entity in removed_mesh_material_components.read() {
827
// If the entity's specialization tick was updated this frame, that
828
// means that that entity changed materials this frame. Don't remove the
829
// entity from the table in that case.
830
if entity_specialization_ticks
831
.get(&MainEntity::from(entity))
832
.is_some_and(|ticks| {
833
ticks.material_instances_tick == render_material_instances.current_change_tick
834
})
835
{
836
continue;
837
}
838
839
entity_specialization_ticks.remove(&MainEntity::from(entity));
840
for view in views {
841
if let Some(cache) =
842
specialized_material_pipeline_cache.get_mut(&view.retained_view_entity)
843
{
844
cache.remove(&MainEntity::from(entity));
845
}
846
if let Some(cache) = specialized_prepass_material_pipeline_cache
847
.as_mut()
848
.and_then(|c| c.get_mut(&view.retained_view_entity))
849
{
850
cache.remove(&MainEntity::from(entity));
851
}
852
if let Some(cache) = specialized_shadow_material_pipeline_cache
853
.as_mut()
854
.and_then(|c| c.get_mut(&view.retained_view_entity))
855
{
856
cache.remove(&MainEntity::from(entity));
857
}
858
}
859
}
860
}
861
862
#[derive(Resource, Deref, DerefMut, Clone, Debug)]
863
pub struct EntitiesNeedingSpecialization<M> {
864
#[deref]
865
pub entities: Vec<Entity>,
866
_marker: PhantomData<M>,
867
}
868
869
impl<M> Default for EntitiesNeedingSpecialization<M> {
870
fn default() -> Self {
871
Self {
872
entities: Default::default(),
873
_marker: Default::default(),
874
}
875
}
876
}
877
878
/// Stores ticks specifying the last time Bevy specialized the pipelines of each
879
/// entity.
880
///
881
/// Every entity that has a mesh and material must be present in this table,
882
/// even if that mesh isn't visible.
883
#[derive(Resource, Deref, DerefMut, Default, Clone, Debug)]
884
pub struct EntitySpecializationTicks {
885
/// A mapping from each main entity to ticks that specify the last time this
886
/// entity's pipeline was specialized.
887
///
888
/// Every entity that has a mesh and material must be present in this table,
889
/// even if that mesh isn't visible.
890
#[deref]
891
pub entities: MainEntityHashMap<EntitySpecializationTickPair>,
892
}
893
894
/// Ticks that specify the last time an entity's pipeline was specialized.
895
///
896
/// We need two different types of ticks here for a subtle reason. First, we
897
/// need the [`Self::system_tick`], which maps to Bevy's [`SystemChangeTick`],
898
/// because that's what we use in `specialize_material_meshes` to check
899
/// whether pipelines need specialization. But we also need
900
/// [`Self::material_instances_tick`], which maps to the
901
/// [`RenderMaterialInstances::current_change_tick`]. That's because the latter
902
/// only changes once per frame, which is a guarantee we need to handle the
903
/// following case:
904
///
905
/// 1. The app removes material A from a mesh and replaces it with material B.
906
/// Both A and B are of different [`Material`] types entirely.
907
///
908
/// 2. [`extract_entities_needs_specialization`] runs for material B and marks
909
/// the mesh as up to date by recording the current tick.
910
///
911
/// 3. [`sweep_entities_needing_specialization`] runs for material A and checks
912
/// to ensure it's safe to remove the [`EntitySpecializationTickPair`] for the mesh
913
/// from the [`EntitySpecializationTicks`]. To do this, it needs to know
914
/// whether [`extract_entities_needs_specialization`] for some *different*
915
/// material (in this case, material B) ran earlier in the frame and updated the
916
/// change tick, and to skip removing the [`EntitySpecializationTickPair`] if so.
917
/// It can't reliably use the [`Self::system_tick`] to determine this because
918
/// the [`SystemChangeTick`] can be updated multiple times in the same frame.
919
/// Instead, it needs a type of tick that's updated only once per frame, after
920
/// all materials' versions of [`sweep_entities_needing_specialization`] have
921
/// run. The [`RenderMaterialInstances`] tick satisfies this criterion, and so
922
/// that's what [`sweep_entities_needing_specialization`] uses.
923
#[derive(Clone, Copy, Debug)]
924
pub struct EntitySpecializationTickPair {
925
/// The standard Bevy system tick.
926
pub system_tick: Tick,
927
/// The tick in [`RenderMaterialInstances`], which is updated in
928
/// `late_sweep_material_instances`.
929
pub material_instances_tick: Tick,
930
}
931
932
/// Stores the [`SpecializedMaterialViewPipelineCache`] for each view.
933
#[derive(Resource, Deref, DerefMut, Default)]
934
pub struct SpecializedMaterialPipelineCache {
935
// view entity -> view pipeline cache
936
#[deref]
937
map: HashMap<RetainedViewEntity, SpecializedMaterialViewPipelineCache>,
938
}
939
940
/// Stores the cached render pipeline ID for each entity in a single view, as
941
/// well as the last time it was changed.
942
#[derive(Deref, DerefMut, Default)]
943
pub struct SpecializedMaterialViewPipelineCache {
944
// material entity -> (tick, pipeline_id)
945
#[deref]
946
map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
947
}
948
949
pub fn check_entities_needing_specialization<M>(
950
needs_specialization: Query<
951
Entity,
952
(
953
Or<(
954
Changed<Mesh3d>,
955
AssetChanged<Mesh3d>,
956
Changed<MeshMaterial3d<M>>,
957
AssetChanged<MeshMaterial3d<M>>,
958
)>,
959
With<MeshMaterial3d<M>>,
960
),
961
>,
962
mut par_local: Local<Parallel<Vec<Entity>>>,
963
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
964
) where
965
M: Material,
966
{
967
entities_needing_specialization.clear();
968
969
needs_specialization
970
.par_iter()
971
.for_each(|entity| par_local.borrow_local_mut().push(entity));
972
973
par_local.drain_into(&mut entities_needing_specialization);
974
}
975
976
pub(crate) struct SpecializationWorkItem {
977
visible_entity: MainEntity,
978
retained_view_entity: RetainedViewEntity,
979
mesh_key: MeshPipelineKey,
980
layout: MeshVertexBufferLayoutRef,
981
properties: Arc<MaterialProperties>,
982
material_type_id: TypeId,
983
}
984
985
#[derive(SystemParam)]
986
pub(crate) struct SpecializeMaterialMeshesSystemParam<'w, 's> {
987
render_meshes: Res<'w, RenderAssets<RenderMesh>>,
988
render_materials: Res<'w, ErasedRenderAssets<PreparedMaterial>>,
989
render_mesh_instances: Res<'w, RenderMeshInstances>,
990
render_material_instances: Res<'w, RenderMaterialInstances>,
991
render_lightmaps: Res<'w, RenderLightmaps>,
992
render_visibility_ranges: Res<'w, RenderVisibilityRanges>,
993
opaque_render_phases: Res<'w, ViewBinnedRenderPhases<Opaque3d>>,
994
alpha_mask_render_phases: Res<'w, ViewBinnedRenderPhases<AlphaMask3d>>,
995
transmissive_render_phases: Res<'w, ViewSortedRenderPhases<Transmissive3d>>,
996
transparent_render_phases: Res<'w, ViewSortedRenderPhases<Transparent3d>>,
997
views: Query<'w, 's, (&'static ExtractedView, &'static RenderVisibleEntities)>,
998
view_key_cache: Res<'w, ViewKeyCache>,
999
entity_specialization_ticks: Res<'w, EntitySpecializationTicks>,
1000
view_specialization_ticks: Res<'w, ViewSpecializationTicks>,
1001
specialized_material_pipeline_cache: Res<'w, SpecializedMaterialPipelineCache>,
1002
this_run: SystemChangeTick,
1003
}
1004
1005
pub(crate) fn specialize_material_meshes(
1006
world: &mut World,
1007
state: &mut SystemState<SpecializeMaterialMeshesSystemParam>,
1008
mut work_items: Local<Vec<SpecializationWorkItem>>,
1009
mut all_views: Local<HashSet<RetainedViewEntity, FixedHasher>>,
1010
) {
1011
work_items.clear();
1012
all_views.clear();
1013
1014
let this_run;
1015
1016
{
1017
let SpecializeMaterialMeshesSystemParam {
1018
render_meshes,
1019
render_materials,
1020
render_mesh_instances,
1021
render_material_instances,
1022
render_lightmaps,
1023
render_visibility_ranges,
1024
opaque_render_phases,
1025
alpha_mask_render_phases,
1026
transmissive_render_phases,
1027
transparent_render_phases,
1028
views,
1029
view_key_cache,
1030
entity_specialization_ticks,
1031
view_specialization_ticks,
1032
specialized_material_pipeline_cache,
1033
this_run: system_change_tick,
1034
} = state.get(world);
1035
1036
this_run = system_change_tick.this_run();
1037
1038
for (view, visible_entities) in &views {
1039
all_views.insert(view.retained_view_entity);
1040
1041
if !transparent_render_phases.contains_key(&view.retained_view_entity)
1042
&& !opaque_render_phases.contains_key(&view.retained_view_entity)
1043
&& !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
1044
&& !transmissive_render_phases.contains_key(&view.retained_view_entity)
1045
{
1046
continue;
1047
}
1048
1049
let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
1050
continue;
1051
};
1052
1053
let view_tick = view_specialization_ticks
1054
.get(&view.retained_view_entity)
1055
.unwrap();
1056
let view_specialized_material_pipeline_cache =
1057
specialized_material_pipeline_cache.get(&view.retained_view_entity);
1058
1059
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
1060
let Some(material_instance) =
1061
render_material_instances.instances.get(visible_entity)
1062
else {
1063
continue;
1064
};
1065
let Some(mesh_instance) =
1066
render_mesh_instances.render_mesh_queue_data(*visible_entity)
1067
else {
1068
continue;
1069
};
1070
let entity_tick = entity_specialization_ticks
1071
.get(visible_entity)
1072
.unwrap()
1073
.system_tick;
1074
let last_specialized_tick = view_specialized_material_pipeline_cache
1075
.and_then(|cache| cache.get(visible_entity))
1076
.map(|(tick, _)| *tick);
1077
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
1078
view_tick.is_newer_than(tick, this_run)
1079
|| entity_tick.is_newer_than(tick, this_run)
1080
});
1081
if !needs_specialization {
1082
continue;
1083
}
1084
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
1085
continue;
1086
};
1087
let Some(material) = render_materials.get(material_instance.asset_id) else {
1088
continue;
1089
};
1090
1091
let mut mesh_pipeline_key_bits: MeshPipelineKey =
1092
material.properties.mesh_pipeline_key_bits.downcast();
1093
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(
1094
material.properties.alpha_mode,
1095
&Msaa::from_samples(view_key.msaa_samples()),
1096
));
1097
let mut mesh_key = *view_key
1098
| MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
1099
| mesh_pipeline_key_bits;
1100
1101
if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
1102
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1103
1104
if lightmap.bicubic_sampling {
1105
mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
1106
}
1107
}
1108
1109
if render_visibility_ranges
1110
.entity_has_crossfading_visibility_ranges(*visible_entity)
1111
{
1112
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
1113
}
1114
1115
if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
1116
if mesh_instance
1117
.flags
1118
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
1119
{
1120
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
1121
}
1122
if mesh_instance
1123
.flags
1124
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
1125
{
1126
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
1127
}
1128
}
1129
1130
work_items.push(SpecializationWorkItem {
1131
visible_entity: *visible_entity,
1132
retained_view_entity: view.retained_view_entity,
1133
mesh_key,
1134
layout: mesh.layout.clone(),
1135
properties: material.properties.clone(),
1136
material_type_id: material_instance.asset_id.type_id(),
1137
});
1138
}
1139
}
1140
}
1141
1142
for item in work_items.drain(..) {
1143
let key = ErasedMaterialPipelineKey {
1144
type_id: item.material_type_id,
1145
mesh_key: ErasedMeshPipelineKey::new(item.mesh_key),
1146
material_key: item.properties.material_key.clone(),
1147
};
1148
1149
let Some(base_specialize) = item.properties.base_specialize else {
1150
continue;
1151
};
1152
match base_specialize(world, key, &item.layout, &item.properties) {
1153
Ok(pipeline_id) => {
1154
world
1155
.resource_mut::<SpecializedMaterialPipelineCache>()
1156
.entry(item.retained_view_entity)
1157
.or_default()
1158
.insert(item.visible_entity, (this_run, pipeline_id));
1159
}
1160
Err(err) => error!("{}", err),
1161
}
1162
}
1163
1164
world
1165
.resource_mut::<SpecializedMaterialPipelineCache>()
1166
.retain(|view, _| all_views.contains(view));
1167
}
1168
1169
/// For each view, iterates over all the meshes visible from that view and adds
1170
/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate.
1171
pub fn queue_material_meshes(
1172
render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
1173
render_mesh_instances: Res<RenderMeshInstances>,
1174
render_material_instances: Res<RenderMaterialInstances>,
1175
mesh_allocator: Res<MeshAllocator>,
1176
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1177
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
1178
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
1179
mut transmissive_render_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
1180
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
1181
views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1182
specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache>,
1183
) {
1184
for (view, visible_entities) in &views {
1185
let (
1186
Some(opaque_phase),
1187
Some(alpha_mask_phase),
1188
Some(transmissive_phase),
1189
Some(transparent_phase),
1190
) = (
1191
opaque_render_phases.get_mut(&view.retained_view_entity),
1192
alpha_mask_render_phases.get_mut(&view.retained_view_entity),
1193
transmissive_render_phases.get_mut(&view.retained_view_entity),
1194
transparent_render_phases.get_mut(&view.retained_view_entity),
1195
)
1196
else {
1197
continue;
1198
};
1199
1200
let Some(view_specialized_material_pipeline_cache) =
1201
specialized_material_pipeline_cache.get(&view.retained_view_entity)
1202
else {
1203
continue;
1204
};
1205
1206
let rangefinder = view.rangefinder3d();
1207
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
1208
let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
1209
.get(visible_entity)
1210
.map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
1211
else {
1212
continue;
1213
};
1214
1215
// Skip the entity if it's cached in a bin and up to date.
1216
if opaque_phase.validate_cached_entity(*visible_entity, current_change_tick)
1217
|| alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick)
1218
{
1219
continue;
1220
}
1221
1222
let Some(material_instance) = render_material_instances.instances.get(visible_entity)
1223
else {
1224
continue;
1225
};
1226
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1227
else {
1228
continue;
1229
};
1230
let Some(material) = render_materials.get(material_instance.asset_id) else {
1231
continue;
1232
};
1233
1234
// Fetch the slabs that this mesh resides in.
1235
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1236
1237
match material.properties.render_phase_type {
1238
RenderPhaseType::Transmissive => {
1239
let distance = rangefinder.distance(&mesh_instance.center)
1240
+ material.properties.depth_bias;
1241
let Some(draw_function) = material
1242
.properties
1243
.get_draw_function(MainPassTransmissiveDrawFunction)
1244
else {
1245
continue;
1246
};
1247
transmissive_phase.add(Transmissive3d {
1248
entity: (*render_entity, *visible_entity),
1249
draw_function,
1250
pipeline: pipeline_id,
1251
distance,
1252
batch_range: 0..1,
1253
extra_index: PhaseItemExtraIndex::None,
1254
indexed: index_slab.is_some(),
1255
});
1256
}
1257
RenderPhaseType::Opaque => {
1258
if material.properties.render_method == OpaqueRendererMethod::Deferred {
1259
// Even though we aren't going to insert the entity into
1260
// a bin, we still want to update its cache entry. That
1261
// way, we know we don't need to re-examine it in future
1262
// frames.
1263
opaque_phase.update_cache(*visible_entity, None, current_change_tick);
1264
continue;
1265
}
1266
let Some(draw_function) = material
1267
.properties
1268
.get_draw_function(MainPassOpaqueDrawFunction)
1269
else {
1270
continue;
1271
};
1272
let batch_set_key = Opaque3dBatchSetKey {
1273
pipeline: pipeline_id,
1274
draw_function,
1275
material_bind_group_index: Some(material.binding.group.0),
1276
vertex_slab: vertex_slab.unwrap_or_default(),
1277
index_slab,
1278
lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index),
1279
};
1280
let bin_key = Opaque3dBinKey {
1281
asset_id: mesh_instance.mesh_asset_id.into(),
1282
};
1283
opaque_phase.add(
1284
batch_set_key,
1285
bin_key,
1286
(*render_entity, *visible_entity),
1287
mesh_instance.current_uniform_index,
1288
BinnedRenderPhaseType::mesh(
1289
mesh_instance.should_batch(),
1290
&gpu_preprocessing_support,
1291
),
1292
current_change_tick,
1293
);
1294
}
1295
// Alpha mask
1296
RenderPhaseType::AlphaMask => {
1297
let Some(draw_function) = material
1298
.properties
1299
.get_draw_function(MainPassAlphaMaskDrawFunction)
1300
else {
1301
continue;
1302
};
1303
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
1304
draw_function,
1305
pipeline: pipeline_id,
1306
material_bind_group_index: Some(material.binding.group.0),
1307
vertex_slab: vertex_slab.unwrap_or_default(),
1308
index_slab,
1309
};
1310
let bin_key = OpaqueNoLightmap3dBinKey {
1311
asset_id: mesh_instance.mesh_asset_id.into(),
1312
};
1313
alpha_mask_phase.add(
1314
batch_set_key,
1315
bin_key,
1316
(*render_entity, *visible_entity),
1317
mesh_instance.current_uniform_index,
1318
BinnedRenderPhaseType::mesh(
1319
mesh_instance.should_batch(),
1320
&gpu_preprocessing_support,
1321
),
1322
current_change_tick,
1323
);
1324
}
1325
RenderPhaseType::Transparent => {
1326
let distance = rangefinder.distance(&mesh_instance.center)
1327
+ material.properties.depth_bias;
1328
let Some(draw_function) = material
1329
.properties
1330
.get_draw_function(MainPassTransparentDrawFunction)
1331
else {
1332
continue;
1333
};
1334
transparent_phase.add(Transparent3d {
1335
entity: (*render_entity, *visible_entity),
1336
draw_function,
1337
pipeline: pipeline_id,
1338
distance,
1339
batch_range: 0..1,
1340
extra_index: PhaseItemExtraIndex::None,
1341
indexed: index_slab.is_some(),
1342
});
1343
}
1344
}
1345
}
1346
}
1347
}
1348
1349
/// Default render method used for opaque materials.
1350
#[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)]
1351
#[reflect(Resource, Default, Debug, Clone)]
1352
pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod);
1353
1354
impl DefaultOpaqueRendererMethod {
1355
pub fn forward() -> Self {
1356
DefaultOpaqueRendererMethod(OpaqueRendererMethod::Forward)
1357
}
1358
1359
pub fn deferred() -> Self {
1360
DefaultOpaqueRendererMethod(OpaqueRendererMethod::Deferred)
1361
}
1362
1363
pub fn set_to_forward(&mut self) {
1364
self.0 = OpaqueRendererMethod::Forward;
1365
}
1366
1367
pub fn set_to_deferred(&mut self) {
1368
self.0 = OpaqueRendererMethod::Deferred;
1369
}
1370
}
1371
1372
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1373
pub struct MaterialVertexShader;
1374
1375
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1376
pub struct MaterialFragmentShader;
1377
1378
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1379
pub struct PrepassVertexShader;
1380
1381
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1382
pub struct PrepassFragmentShader;
1383
1384
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1385
pub struct DeferredVertexShader;
1386
1387
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1388
pub struct DeferredFragmentShader;
1389
1390
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1391
pub struct MeshletFragmentShader;
1392
1393
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1394
pub struct MeshletPrepassFragmentShader;
1395
1396
#[derive(ShaderLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1397
pub struct MeshletDeferredFragmentShader;
1398
1399
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1400
pub struct MainPassOpaqueDrawFunction;
1401
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1402
pub struct MainPassAlphaMaskDrawFunction;
1403
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1404
pub struct MainPassTransmissiveDrawFunction;
1405
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1406
pub struct MainPassTransparentDrawFunction;
1407
1408
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1409
pub struct PrepassOpaqueDrawFunction;
1410
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1411
pub struct PrepassAlphaMaskDrawFunction;
1412
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1413
pub struct PrepassOpaqueDepthOnlyDrawFunction;
1414
1415
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1416
pub struct DeferredOpaqueDrawFunction;
1417
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1418
pub struct DeferredAlphaMaskDrawFunction;
1419
1420
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1421
pub struct ShadowsDrawFunction;
1422
#[derive(DrawFunctionLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
1423
pub struct ShadowsDepthOnlyDrawFunction;
1424
1425
/// A resource that maps each untyped material ID to its binding.
1426
///
1427
/// This duplicates information in `RenderAssets<M>`, but it doesn't have the
1428
/// `M` type parameter, so it can be used in untyped contexts like
1429
/// [`crate::render::mesh::collect_meshes_for_gpu_building`].
1430
#[derive(Resource, Default, Deref, DerefMut)]
1431
pub struct RenderMaterialBindings(HashMap<UntypedAssetId, MaterialBindingId>);
1432
1433
/// Data prepared for a [`Material`] instance.
1434
pub struct PreparedMaterial {
1435
pub binding: MaterialBindingId,
1436
pub properties: Arc<MaterialProperties>,
1437
}
1438
1439
pub fn base_specialize(
1440
world: &mut World,
1441
key: ErasedMaterialPipelineKey,
1442
layout: &MeshVertexBufferLayoutRef,
1443
properties: &Arc<MaterialProperties>,
1444
) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError> {
1445
world.resource_scope(
1446
|world, mut pipelines: Mut<SpecializedMeshPipelines<MaterialPipelineSpecializer>>| {
1447
let mesh_pipeline = world.resource::<MeshPipeline>().clone();
1448
let pipeline_cache = world.resource::<PipelineCache>();
1449
1450
let specializer = MaterialPipelineSpecializer {
1451
pipeline: MaterialPipeline { mesh_pipeline },
1452
properties: properties.clone(),
1453
};
1454
1455
pipelines.specialize(pipeline_cache, &specializer, key, layout)
1456
},
1457
)
1458
}
1459
fn prepass_specialize(
1460
world: &mut World,
1461
key: ErasedMaterialPipelineKey,
1462
layout: &MeshVertexBufferLayoutRef,
1463
properties: &Arc<MaterialProperties>,
1464
) -> Result<CachedRenderPipelineId, SpecializedMeshPipelineError> {
1465
world.resource_scope(
1466
|world, mut pipelines: Mut<SpecializedMeshPipelines<PrepassPipelineSpecializer>>| {
1467
let prepass_pipeline = world.resource::<PrepassPipeline>().clone();
1468
let pipeline_cache = world.resource::<PipelineCache>();
1469
1470
let specializer = PrepassPipelineSpecializer {
1471
pipeline: prepass_pipeline,
1472
properties: properties.clone(),
1473
};
1474
1475
pipelines.specialize(pipeline_cache, &specializer, key, layout)
1476
},
1477
)
1478
}
1479
1480
fn user_specialize<M: Material>(
1481
pipeline: &dyn Any,
1482
descriptor: &mut RenderPipelineDescriptor,
1483
mesh_layout: &MeshVertexBufferLayoutRef,
1484
erased_key: ErasedMaterialPipelineKey,
1485
) -> Result<(), SpecializedMeshPipelineError>
1486
where
1487
M::Data: Hash + Clone,
1488
{
1489
let pipeline = pipeline.downcast_ref::<MaterialPipeline>().unwrap();
1490
let material_key = erased_key.material_key.to_key();
1491
let mesh_key: MeshPipelineKey = erased_key.mesh_key.downcast();
1492
M::specialize(
1493
pipeline,
1494
descriptor,
1495
mesh_layout,
1496
MaterialPipelineKey {
1497
mesh_key,
1498
bind_group_data: material_key,
1499
},
1500
)
1501
}
1502
1503
// orphan rules T_T
1504
impl<M: Material> ErasedRenderAsset for MeshMaterial3d<M>
1505
where
1506
M::Data: PartialEq + Eq + Hash + Clone,
1507
{
1508
type SourceAsset = M;
1509
type ErasedAsset = PreparedMaterial;
1510
1511
type Param = (
1512
SRes<RenderDevice>,
1513
SRes<PipelineCache>,
1514
SRes<DefaultOpaqueRendererMethod>,
1515
SResMut<MaterialBindGroupAllocators>,
1516
SResMut<RenderMaterialBindings>,
1517
SRes<DrawFunctions<Opaque3d>>,
1518
SRes<DrawFunctions<AlphaMask3d>>,
1519
SRes<DrawFunctions<Transmissive3d>>,
1520
SRes<DrawFunctions<Transparent3d>>,
1521
SRes<DrawFunctions<Opaque3dPrepass>>,
1522
SRes<DrawFunctions<AlphaMask3dPrepass>>,
1523
SRes<DrawFunctions<Opaque3dDeferred>>,
1524
SRes<DrawFunctions<AlphaMask3dDeferred>>,
1525
SRes<DrawFunctions<Shadow>>,
1526
SRes<AssetServer>,
1527
M::Param,
1528
);
1529
1530
fn prepare_asset(
1531
material: Self::SourceAsset,
1532
material_id: AssetId<Self::SourceAsset>,
1533
(
1534
render_device,
1535
pipeline_cache,
1536
default_opaque_render_method,
1537
bind_group_allocators,
1538
render_material_bindings,
1539
opaque_draw_functions,
1540
alpha_mask_draw_functions,
1541
transmissive_draw_functions,
1542
transparent_draw_functions,
1543
opaque_prepass_draw_functions,
1544
alpha_mask_prepass_draw_functions,
1545
opaque_deferred_draw_functions,
1546
alpha_mask_deferred_draw_functions,
1547
shadow_draw_functions,
1548
asset_server,
1549
material_param,
1550
): &mut SystemParamItem<Self::Param>,
1551
) -> Result<Self::ErasedAsset, PrepareAssetError<Self::SourceAsset>> {
1552
let material_layout = M::bind_group_layout_descriptor(render_device);
1553
let actual_material_layout = pipeline_cache.get_bind_group_layout(&material_layout);
1554
1555
let binding = match material.unprepared_bind_group(
1556
&actual_material_layout,
1557
render_device,
1558
material_param,
1559
false,
1560
) {
1561
Ok(unprepared) => {
1562
let bind_group_allocator =
1563
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1564
// Allocate or update the material.
1565
match render_material_bindings.entry(material_id.into()) {
1566
Entry::Occupied(mut occupied_entry) => {
1567
// TODO: Have a fast path that doesn't require
1568
// recreating the bind group if only buffer contents
1569
// change. For now, we just delete and recreate the bind
1570
// group.
1571
bind_group_allocator.free(*occupied_entry.get());
1572
let new_binding =
1573
bind_group_allocator.allocate_unprepared(unprepared, &material_layout);
1574
*occupied_entry.get_mut() = new_binding;
1575
new_binding
1576
}
1577
Entry::Vacant(vacant_entry) => *vacant_entry.insert(
1578
bind_group_allocator.allocate_unprepared(unprepared, &material_layout),
1579
),
1580
}
1581
}
1582
Err(AsBindGroupError::RetryNextUpdate) => {
1583
return Err(PrepareAssetError::RetryNextUpdate(material))
1584
}
1585
Err(AsBindGroupError::CreateBindGroupDirectly) => {
1586
match material.as_bind_group(
1587
&material_layout,
1588
render_device,
1589
pipeline_cache,
1590
material_param,
1591
) {
1592
Ok(prepared_bind_group) => {
1593
let bind_group_allocator =
1594
bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1595
// Store the resulting bind group directly in the slot.
1596
let material_binding_id =
1597
bind_group_allocator.allocate_prepared(prepared_bind_group);
1598
render_material_bindings.insert(material_id.into(), material_binding_id);
1599
material_binding_id
1600
}
1601
Err(AsBindGroupError::RetryNextUpdate) => {
1602
return Err(PrepareAssetError::RetryNextUpdate(material))
1603
}
1604
Err(other) => return Err(PrepareAssetError::AsBindGroupError(other)),
1605
}
1606
}
1607
Err(other) => return Err(PrepareAssetError::AsBindGroupError(other)),
1608
};
1609
1610
let shadows_enabled = M::enable_shadows();
1611
let prepass_enabled = M::enable_prepass();
1612
1613
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial>();
1614
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial>();
1615
let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial>();
1616
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial>();
1617
let draw_opaque_prepass = opaque_prepass_draw_functions.read().id::<DrawPrepass>();
1618
let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions.read().id::<DrawPrepass>();
1619
let draw_opaque_prepass_depth_only = opaque_prepass_draw_functions
1620
.read()
1621
.id::<DrawDepthOnlyPrepass>();
1622
let draw_opaque_deferred = opaque_deferred_draw_functions.read().id::<DrawPrepass>();
1623
let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions
1624
.read()
1625
.id::<DrawPrepass>();
1626
let draw_shadows = shadow_draw_functions.read().id::<DrawPrepass>();
1627
let draw_shadows_depth_only = shadow_draw_functions.read().id::<DrawDepthOnlyPrepass>();
1628
1629
let draw_functions = SmallVec::from_iter([
1630
(MainPassOpaqueDrawFunction.intern(), draw_opaque_pbr),
1631
(MainPassAlphaMaskDrawFunction.intern(), draw_alpha_mask_pbr),
1632
(
1633
MainPassTransmissiveDrawFunction.intern(),
1634
draw_transmissive_pbr,
1635
),
1636
(
1637
MainPassTransparentDrawFunction.intern(),
1638
draw_transparent_pbr,
1639
),
1640
(PrepassOpaqueDrawFunction.intern(), draw_opaque_prepass),
1641
(
1642
PrepassAlphaMaskDrawFunction.intern(),
1643
draw_alpha_mask_prepass,
1644
),
1645
(
1646
PrepassOpaqueDepthOnlyDrawFunction.intern(),
1647
draw_opaque_prepass_depth_only,
1648
),
1649
(DeferredOpaqueDrawFunction.intern(), draw_opaque_deferred),
1650
(
1651
DeferredAlphaMaskDrawFunction.intern(),
1652
draw_alpha_mask_deferred,
1653
),
1654
(ShadowsDrawFunction.intern(), draw_shadows),
1655
(
1656
ShadowsDepthOnlyDrawFunction.intern(),
1657
draw_shadows_depth_only,
1658
),
1659
]);
1660
1661
let render_method = match material.opaque_render_method() {
1662
OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,
1663
OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred,
1664
OpaqueRendererMethod::Auto => default_opaque_render_method.0,
1665
};
1666
1667
let mut mesh_pipeline_key_bits = MeshPipelineKey::empty();
1668
mesh_pipeline_key_bits.set(
1669
MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE,
1670
material.reads_view_transmission_texture(),
1671
);
1672
1673
let reads_view_transmission_texture =
1674
mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE);
1675
1676
let mesh_pipeline_key_bits = ErasedMeshPipelineKey::new(mesh_pipeline_key_bits);
1677
1678
let render_phase_type = match material.alpha_mode() {
1679
AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => {
1680
RenderPhaseType::Transparent
1681
}
1682
_ if reads_view_transmission_texture => RenderPhaseType::Transmissive,
1683
AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque,
1684
AlphaMode::Mask(_) => RenderPhaseType::AlphaMask,
1685
};
1686
1687
let mut shaders = SmallVec::new();
1688
let mut add_shader = |label: InternedShaderLabel, shader_ref: ShaderRef| {
1689
let mayber_shader = match shader_ref {
1690
ShaderRef::Default => None,
1691
ShaderRef::Handle(handle) => Some(handle),
1692
ShaderRef::Path(path) => Some(asset_server.load(path)),
1693
};
1694
if let Some(shader) = mayber_shader {
1695
shaders.push((label, shader));
1696
}
1697
};
1698
add_shader(MaterialVertexShader.intern(), M::vertex_shader());
1699
add_shader(MaterialFragmentShader.intern(), M::fragment_shader());
1700
add_shader(PrepassVertexShader.intern(), M::prepass_vertex_shader());
1701
add_shader(PrepassFragmentShader.intern(), M::prepass_fragment_shader());
1702
add_shader(DeferredVertexShader.intern(), M::deferred_vertex_shader());
1703
add_shader(
1704
DeferredFragmentShader.intern(),
1705
M::deferred_fragment_shader(),
1706
);
1707
1708
#[cfg(feature = "meshlet")]
1709
{
1710
add_shader(
1711
MeshletFragmentShader.intern(),
1712
M::meshlet_mesh_fragment_shader(),
1713
);
1714
add_shader(
1715
MeshletPrepassFragmentShader.intern(),
1716
M::meshlet_mesh_prepass_fragment_shader(),
1717
);
1718
add_shader(
1719
MeshletDeferredFragmentShader.intern(),
1720
M::meshlet_mesh_deferred_fragment_shader(),
1721
);
1722
}
1723
1724
let bindless = material_uses_bindless_resources::<M>(render_device);
1725
let bind_group_data = material.bind_group_data();
1726
let material_key = ErasedMaterialKey::new(bind_group_data);
1727
1728
Ok(PreparedMaterial {
1729
binding,
1730
properties: Arc::new(MaterialProperties {
1731
alpha_mode: material.alpha_mode(),
1732
depth_bias: material.depth_bias(),
1733
reads_view_transmission_texture,
1734
render_phase_type,
1735
render_method,
1736
mesh_pipeline_key_bits,
1737
material_layout: Some(material_layout),
1738
draw_functions,
1739
shaders,
1740
bindless,
1741
base_specialize: Some(base_specialize),
1742
prepass_specialize: Some(prepass_specialize),
1743
user_specialize: Some(user_specialize::<M>),
1744
material_key,
1745
shadows_enabled,
1746
prepass_enabled,
1747
}),
1748
})
1749
}
1750
1751
fn unload_asset(
1752
source_asset: AssetId<Self::SourceAsset>,
1753
(_, _, _, bind_group_allocators, render_material_bindings, ..): &mut SystemParamItem<
1754
Self::Param,
1755
>,
1756
) {
1757
let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())
1758
else {
1759
return;
1760
};
1761
let bind_group_allactor = bind_group_allocators.get_mut(&TypeId::of::<M>()).unwrap();
1762
bind_group_allactor.free(material_binding_id);
1763
}
1764
}
1765
1766
/// Creates and/or recreates any bind groups that contain materials that were
1767
/// modified this frame.
1768
pub fn prepare_material_bind_groups(
1769
mut allocators: ResMut<MaterialBindGroupAllocators>,
1770
render_device: Res<RenderDevice>,
1771
pipeline_cache: Res<PipelineCache>,
1772
fallback_image: Res<FallbackImage>,
1773
fallback_resources: Res<FallbackBindlessResources>,
1774
) {
1775
for (_, allocator) in allocators.iter_mut() {
1776
allocator.prepare_bind_groups(
1777
&render_device,
1778
&pipeline_cache,
1779
&fallback_resources,
1780
&fallback_image,
1781
);
1782
}
1783
}
1784
1785
/// Uploads the contents of all buffers that the [`MaterialBindGroupAllocator`]
1786
/// manages to the GPU.
1787
///
1788
/// Non-bindless allocators don't currently manage any buffers, so this method
1789
/// only has an effect for bindless allocators.
1790
pub fn write_material_bind_group_buffers(
1791
mut allocators: ResMut<MaterialBindGroupAllocators>,
1792
render_device: Res<RenderDevice>,
1793
render_queue: Res<RenderQueue>,
1794
) {
1795
for (_, allocator) in allocators.iter_mut() {
1796
allocator.write_buffers(&render_device, &render_queue);
1797
}
1798
}
1799
1800