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