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