
# Objective Upgrade to `wgpu` version `25.0`. Depends on https://github.com/bevyengine/naga_oil/pull/121 ## Solution ### Problem The biggest issue we face upgrading is the following requirement: > To facilitate this change, there was an additional validation rule put in place: if there is a binding array in a bind group, you may not use dynamic offset buffers or uniform buffers in that bind group. This requirement comes from vulkan rules on UpdateAfterBind descriptors. This is a major difficulty for us, as there are a number of binding arrays that are used in the view bind group. Note, this requirement does not affect merely uniform buffors that use dynamic offset but the use of *any* uniform in a bind group that also has a binding array. ### Attempted fixes The easiest fix would be to change uniforms to be storage buffers whenever binding arrays are in use: ```wgsl #ifdef BINDING_ARRAYS_ARE_USED @group(0) @binding(0) var<uniform> view: View; @group(0) @binding(1) var<uniform> lights: types::Lights; #else @group(0) @binding(0) var<storage> view: array<View>; @group(0) @binding(1) var<storage> lights: array<types::Lights>; #endif ``` This requires passing the view index to the shader so that we know where to index into the buffer: ```wgsl struct PushConstants { view_index: u32, } var<push_constant> push_constants: PushConstants; ``` Using push constants is no problem because binding arrays are only usable on native anyway. However, this greatly complicates the ability to access `view` in shaders. For example: ```wgsl #ifdef BINDING_ARRAYS_ARE_USED mesh_view_bindings::view.view_from_world[0].z #else mesh_view_bindings::view[mesh_view_bindings::view_index].view_from_world[0].z #endif ``` Using this approach would work but would have the effect of polluting our shaders with ifdef spam basically *everywhere*. Why not use a function? Unfortunately, the following is not valid wgsl as it returns a binding directly from a function in the uniform path. ```wgsl fn get_view() -> View { #if BINDING_ARRAYS_ARE_USED let view_index = push_constants.view_index; let view = views[view_index]; #endif return view; } ``` This also poses problems for things like lights where we want to return a ptr to the light data. Returning ptrs from wgsl functions isn't allowed even if both bindings were buffers. The next attempt was to simply use indexed buffers everywhere, in both the binding array and non binding array path. This would be viable if push constants were available everywhere to pass the view index, but unfortunately they are not available on webgpu. This means either passing the view index in a storage buffer (not ideal for such a small amount of state) or using push constants sometimes and uniform buffers only on webgpu. However, this kind of conditional layout infects absolutely everything. Even if we were to accept just using storage buffer for the view index, there's also the additional problem that some dynamic offsets aren't actually per-view but per-use of a setting on a camera, which would require passing that uniform data on *every* camera regardless of whether that rendering feature is being used, which is also gross. As such, although it's gross, the simplest solution just to bump binding arrays into `@group(1)` and all other bindings up one bind group. This should still bring us under the device limit of 4 for most users. ### Next steps / looking towards the future I'd like to avoid needing split our view bind group into multiple parts. In the future, if `wgpu` were to add `@builtin(draw_index)`, we could build a list of draw state in gpu processing and avoid the need for any kind of state change at all (see https://github.com/gfx-rs/wgpu/issues/6823). This would also provide significantly more flexibility to handle things like offsets into other arrays that may not be per-view. ### Testing Tested a number of examples, there are probably more that are still broken. --------- Co-authored-by: François Mockers <mockersf@gmail.com> Co-authored-by: Elabajaba <Elabajaba@users.noreply.github.com>
1622 lines
64 KiB
Rust
1622 lines
64 KiB
Rust
use crate::material_bind_groups::{
|
|
FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId,
|
|
};
|
|
#[cfg(feature = "meshlet")]
|
|
use crate::meshlet::{
|
|
prepare_material_meshlet_meshes_main_opaque_pass, queue_material_meshlet_meshes,
|
|
InstanceManager,
|
|
};
|
|
use crate::*;
|
|
use bevy_asset::prelude::AssetChanged;
|
|
use bevy_asset::{Asset, AssetEventSystems, AssetId, AssetServer, UntypedAssetId};
|
|
use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred};
|
|
use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass};
|
|
use bevy_core_pipeline::{
|
|
core_3d::{
|
|
AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, ScreenSpaceTransmissionQuality,
|
|
Transmissive3d, Transparent3d,
|
|
},
|
|
prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey},
|
|
tonemapping::Tonemapping,
|
|
};
|
|
use bevy_derive::{Deref, DerefMut};
|
|
use bevy_ecs::component::Tick;
|
|
use bevy_ecs::system::SystemChangeTick;
|
|
use bevy_ecs::{
|
|
prelude::*,
|
|
system::{
|
|
lifetimeless::{SRes, SResMut},
|
|
SystemParamItem,
|
|
},
|
|
};
|
|
use bevy_platform::collections::hash_map::Entry;
|
|
use bevy_platform::collections::{HashMap, HashSet};
|
|
use bevy_platform::hash::FixedHasher;
|
|
use bevy_reflect::std_traits::ReflectDefault;
|
|
use bevy_reflect::Reflect;
|
|
use bevy_render::camera::extract_cameras;
|
|
use bevy_render::mesh::mark_3d_meshes_as_changed_if_their_assets_changed;
|
|
use bevy_render::render_asset::prepare_assets;
|
|
use bevy_render::renderer::RenderQueue;
|
|
use bevy_render::{
|
|
batching::gpu_preprocessing::GpuPreprocessingSupport,
|
|
extract_resource::ExtractResource,
|
|
mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
|
|
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
|
render_phase::*,
|
|
render_resource::*,
|
|
renderer::RenderDevice,
|
|
sync_world::MainEntity,
|
|
view::{ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity, ViewVisibility},
|
|
Extract,
|
|
};
|
|
use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap};
|
|
use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities};
|
|
use bevy_utils::Parallel;
|
|
use core::{hash::Hash, marker::PhantomData};
|
|
use tracing::error;
|
|
|
|
/// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`]
|
|
/// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level
|
|
/// way to render [`Mesh3d`] entities with custom shader logic.
|
|
///
|
|
/// Materials must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders.
|
|
/// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// Here is a simple [`Material`] implementation. The [`AsBindGroup`] derive has many features. To see what else is available,
|
|
/// check out the [`AsBindGroup`] documentation.
|
|
///
|
|
/// ```
|
|
/// # use bevy_pbr::{Material, MeshMaterial3d};
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_image::Image;
|
|
/// # use bevy_reflect::TypePath;
|
|
/// # use bevy_render::{mesh::{Mesh, Mesh3d}, render_resource::{AsBindGroup, ShaderRef}};
|
|
/// # use bevy_color::LinearRgba;
|
|
/// # use bevy_color::palettes::basic::RED;
|
|
/// # use bevy_asset::{Handle, AssetServer, Assets, Asset};
|
|
/// # use bevy_math::primitives::Capsule3d;
|
|
/// #
|
|
/// #[derive(AsBindGroup, Debug, Clone, Asset, TypePath)]
|
|
/// pub struct CustomMaterial {
|
|
/// // Uniform bindings must implement `ShaderType`, which will be used to convert the value to
|
|
/// // its shader-compatible equivalent. Most core math types already implement `ShaderType`.
|
|
/// #[uniform(0)]
|
|
/// color: LinearRgba,
|
|
/// // Images can be bound as textures in shaders. If the Image's sampler is also needed, just
|
|
/// // add the sampler attribute with a different binding index.
|
|
/// #[texture(1)]
|
|
/// #[sampler(2)]
|
|
/// color_texture: Handle<Image>,
|
|
/// }
|
|
///
|
|
/// // All functions on `Material` have default impls. You only need to implement the
|
|
/// // functions that are relevant for your material.
|
|
/// impl Material for CustomMaterial {
|
|
/// fn fragment_shader() -> ShaderRef {
|
|
/// "shaders/custom_material.wgsl".into()
|
|
/// }
|
|
/// }
|
|
///
|
|
/// // Spawn an entity with a mesh using `CustomMaterial`.
|
|
/// fn setup(
|
|
/// mut commands: Commands,
|
|
/// mut meshes: ResMut<Assets<Mesh>>,
|
|
/// mut materials: ResMut<Assets<CustomMaterial>>,
|
|
/// asset_server: Res<AssetServer>
|
|
/// ) {
|
|
/// commands.spawn((
|
|
/// Mesh3d(meshes.add(Capsule3d::default())),
|
|
/// MeshMaterial3d(materials.add(CustomMaterial {
|
|
/// color: RED.into(),
|
|
/// color_texture: asset_server.load("some_image.png"),
|
|
/// })),
|
|
/// ));
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// In WGSL shaders, the material's binding would look like this:
|
|
///
|
|
/// ```wgsl
|
|
/// @group(3) @binding(0) var<uniform> color: vec4<f32>;
|
|
/// @group(3) @binding(1) var color_texture: texture_2d<f32>;
|
|
/// @group(3) @binding(2) var color_sampler: sampler;
|
|
/// ```
|
|
pub trait Material: Asset + AsBindGroup + Clone + Sized {
|
|
/// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader
|
|
/// will be used.
|
|
fn vertex_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader
|
|
/// will be used.
|
|
fn fragment_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`].
|
|
#[inline]
|
|
fn alpha_mode(&self) -> AlphaMode {
|
|
AlphaMode::Opaque
|
|
}
|
|
|
|
/// Returns if this material should be rendered by the deferred or forward renderer.
|
|
/// for `AlphaMode::Opaque` or `AlphaMode::Mask` materials.
|
|
/// If `OpaqueRendererMethod::Auto`, it will default to what is selected in the `DefaultOpaqueRendererMethod` resource.
|
|
#[inline]
|
|
fn opaque_render_method(&self) -> OpaqueRendererMethod {
|
|
OpaqueRendererMethod::Forward
|
|
}
|
|
|
|
#[inline]
|
|
/// Add a bias to the view depth of the mesh which can be used to force a specific render order.
|
|
/// for meshes with similar depth, to avoid z-fighting.
|
|
/// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
|
|
fn depth_bias(&self) -> f32 {
|
|
0.0
|
|
}
|
|
|
|
#[inline]
|
|
/// Returns whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture).
|
|
///
|
|
/// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
|
|
/// rendering to take place in a separate [`Transmissive3d`] pass.
|
|
fn reads_view_transmission_texture(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader
|
|
/// will be used.
|
|
///
|
|
/// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps
|
|
/// required for shadow mapping.
|
|
fn prepass_vertex_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the default prepass fragment shader
|
|
/// will be used.
|
|
///
|
|
/// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps
|
|
/// required for shadow mapping.
|
|
fn prepass_fragment_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the default deferred vertex shader
|
|
/// will be used.
|
|
fn deferred_vertex_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Returns this material's deferred fragment shader. If [`ShaderRef::Default`] is returned, the default deferred fragment shader
|
|
/// will be used.
|
|
fn deferred_fragment_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Returns this material's [`crate::meshlet::MeshletMesh`] fragment shader. If [`ShaderRef::Default`] is returned,
|
|
/// the default meshlet mesh fragment shader will be used.
|
|
///
|
|
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
|
|
///
|
|
/// See [`crate::meshlet::MeshletMesh`] for limitations.
|
|
#[cfg(feature = "meshlet")]
|
|
fn meshlet_mesh_fragment_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Returns this material's [`crate::meshlet::MeshletMesh`] prepass fragment shader. If [`ShaderRef::Default`] is returned,
|
|
/// the default meshlet mesh prepass fragment shader will be used.
|
|
///
|
|
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
|
|
///
|
|
/// See [`crate::meshlet::MeshletMesh`] for limitations.
|
|
#[cfg(feature = "meshlet")]
|
|
fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Returns this material's [`crate::meshlet::MeshletMesh`] deferred fragment shader. If [`ShaderRef::Default`] is returned,
|
|
/// the default meshlet mesh deferred fragment shader will be used.
|
|
///
|
|
/// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s.
|
|
///
|
|
/// See [`crate::meshlet::MeshletMesh`] for limitations.
|
|
#[cfg(feature = "meshlet")]
|
|
fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
|
|
ShaderRef::Default
|
|
}
|
|
|
|
/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
|
|
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input.
|
|
#[expect(
|
|
unused_variables,
|
|
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."
|
|
)]
|
|
#[inline]
|
|
fn specialize(
|
|
pipeline: &MaterialPipeline<Self>,
|
|
descriptor: &mut RenderPipelineDescriptor,
|
|
layout: &MeshVertexBufferLayoutRef,
|
|
key: MaterialPipelineKey<Self>,
|
|
) -> Result<(), SpecializedMeshPipelineError> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`]
|
|
/// asset type.
|
|
pub struct MaterialPlugin<M: Material> {
|
|
/// Controls if the prepass is enabled for the Material.
|
|
/// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs.
|
|
///
|
|
/// When it is enabled, it will automatically add the [`PrepassPlugin`]
|
|
/// required to make the prepass work on this Material.
|
|
pub prepass_enabled: bool,
|
|
/// Controls if shadows are enabled for the Material.
|
|
pub shadows_enabled: bool,
|
|
/// Debugging flags that can optionally be set when constructing the renderer.
|
|
pub debug_flags: RenderDebugFlags,
|
|
pub _marker: PhantomData<M>,
|
|
}
|
|
|
|
impl<M: Material> Default for MaterialPlugin<M> {
|
|
fn default() -> Self {
|
|
Self {
|
|
prepass_enabled: true,
|
|
shadows_enabled: true,
|
|
debug_flags: RenderDebugFlags::default(),
|
|
_marker: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<M: Material> Plugin for MaterialPlugin<M>
|
|
where
|
|
M::Data: PartialEq + Eq + Hash + Clone,
|
|
{
|
|
fn build(&self, app: &mut App) {
|
|
app.init_asset::<M>()
|
|
.register_type::<MeshMaterial3d<M>>()
|
|
.init_resource::<EntitiesNeedingSpecialization<M>>()
|
|
.add_plugins((RenderAssetPlugin::<PreparedMaterial<M>>::default(),))
|
|
.add_systems(
|
|
PostUpdate,
|
|
(
|
|
mark_meshes_as_changed_if_their_materials_changed::<M>.ambiguous_with_all(),
|
|
check_entities_needing_specialization::<M>.after(AssetEventSystems),
|
|
)
|
|
.after(mark_3d_meshes_as_changed_if_their_assets_changed),
|
|
);
|
|
|
|
if self.shadows_enabled {
|
|
app.add_systems(
|
|
PostUpdate,
|
|
check_light_entities_needing_specialization::<M>
|
|
.after(check_entities_needing_specialization::<M>),
|
|
);
|
|
}
|
|
|
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
render_app
|
|
.init_resource::<EntitySpecializationTicks<M>>()
|
|
.init_resource::<SpecializedMaterialPipelineCache<M>>()
|
|
.init_resource::<DrawFunctions<Shadow>>()
|
|
.init_resource::<RenderMaterialInstances>()
|
|
.add_render_command::<Shadow, DrawPrepass<M>>()
|
|
.add_render_command::<Transmissive3d, DrawMaterial<M>>()
|
|
.add_render_command::<Transparent3d, DrawMaterial<M>>()
|
|
.add_render_command::<Opaque3d, DrawMaterial<M>>()
|
|
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
|
|
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
|
|
.add_systems(
|
|
ExtractSchedule,
|
|
(
|
|
extract_mesh_materials::<M>.in_set(MaterialExtractionSystems),
|
|
early_sweep_material_instances::<M>
|
|
.after(MaterialExtractionSystems)
|
|
.before(late_sweep_material_instances),
|
|
extract_entities_needs_specialization::<M>.after(extract_cameras),
|
|
),
|
|
)
|
|
.add_systems(
|
|
Render,
|
|
(
|
|
specialize_material_meshes::<M>
|
|
.in_set(RenderSystems::PrepareMeshes)
|
|
.after(prepare_assets::<PreparedMaterial<M>>)
|
|
.after(prepare_assets::<RenderMesh>)
|
|
.after(collect_meshes_for_gpu_building)
|
|
.after(set_mesh_motion_vector_flags),
|
|
queue_material_meshes::<M>
|
|
.in_set(RenderSystems::QueueMeshes)
|
|
.after(prepare_assets::<PreparedMaterial<M>>),
|
|
),
|
|
)
|
|
.add_systems(
|
|
Render,
|
|
(
|
|
prepare_material_bind_groups::<M>,
|
|
write_material_bind_group_buffers::<M>,
|
|
)
|
|
.chain()
|
|
.in_set(RenderSystems::PrepareBindGroups)
|
|
.after(prepare_assets::<PreparedMaterial<M>>),
|
|
);
|
|
|
|
if self.shadows_enabled {
|
|
render_app
|
|
.init_resource::<LightKeyCache>()
|
|
.init_resource::<LightSpecializationTicks>()
|
|
.init_resource::<SpecializedShadowMaterialPipelineCache<M>>()
|
|
.add_systems(
|
|
Render,
|
|
(
|
|
check_views_lights_need_specialization
|
|
.in_set(RenderSystems::PrepareAssets),
|
|
// specialize_shadows::<M> also needs to run after prepare_assets::<PreparedMaterial<M>>,
|
|
// which is fine since ManageViews is after PrepareAssets
|
|
specialize_shadows::<M>
|
|
.in_set(RenderSystems::ManageViews)
|
|
.after(prepare_lights),
|
|
queue_shadows::<M>
|
|
.in_set(RenderSystems::QueueMeshes)
|
|
.after(prepare_assets::<PreparedMaterial<M>>),
|
|
),
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "meshlet")]
|
|
render_app.add_systems(
|
|
Render,
|
|
queue_material_meshlet_meshes::<M>
|
|
.in_set(RenderSystems::QueueMeshes)
|
|
.run_if(resource_exists::<InstanceManager>),
|
|
);
|
|
|
|
#[cfg(feature = "meshlet")]
|
|
render_app.add_systems(
|
|
Render,
|
|
prepare_material_meshlet_meshes_main_opaque_pass::<M>
|
|
.in_set(RenderSystems::QueueMeshes)
|
|
.after(prepare_assets::<PreparedMaterial<M>>)
|
|
.before(queue_material_meshlet_meshes::<M>)
|
|
.run_if(resource_exists::<InstanceManager>),
|
|
);
|
|
}
|
|
|
|
if self.shadows_enabled || self.prepass_enabled {
|
|
// PrepassPipelinePlugin is required for shadow mapping and the optional PrepassPlugin
|
|
app.add_plugins(PrepassPipelinePlugin::<M>::default());
|
|
}
|
|
|
|
if self.prepass_enabled {
|
|
app.add_plugins(PrepassPlugin::<M>::new(self.debug_flags));
|
|
}
|
|
}
|
|
|
|
fn finish(&self, app: &mut App) {
|
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
render_app
|
|
.init_resource::<MaterialPipeline<M>>()
|
|
.init_resource::<MaterialBindGroupAllocator<M>>();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A dummy [`AssetId`] that we use as a placeholder whenever a mesh doesn't
|
|
/// have a material.
|
|
///
|
|
/// See the comments in [`RenderMaterialInstances::mesh_material`] for more
|
|
/// information.
|
|
pub(crate) static DUMMY_MESH_MATERIAL: AssetId<StandardMaterial> =
|
|
AssetId::<StandardMaterial>::invalid();
|
|
|
|
/// A key uniquely identifying a specialized [`MaterialPipeline`].
|
|
pub struct MaterialPipelineKey<M: Material> {
|
|
pub mesh_key: MeshPipelineKey,
|
|
pub bind_group_data: M::Data,
|
|
}
|
|
|
|
impl<M: Material> Eq for MaterialPipelineKey<M> where M::Data: PartialEq {}
|
|
|
|
impl<M: Material> PartialEq for MaterialPipelineKey<M>
|
|
where
|
|
M::Data: PartialEq,
|
|
{
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.mesh_key == other.mesh_key && self.bind_group_data == other.bind_group_data
|
|
}
|
|
}
|
|
|
|
impl<M: Material> Clone for MaterialPipelineKey<M>
|
|
where
|
|
M::Data: Clone,
|
|
{
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
mesh_key: self.mesh_key,
|
|
bind_group_data: self.bind_group_data.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<M: Material> Hash for MaterialPipelineKey<M>
|
|
where
|
|
M::Data: Hash,
|
|
{
|
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
self.mesh_key.hash(state);
|
|
self.bind_group_data.hash(state);
|
|
}
|
|
}
|
|
|
|
/// Render pipeline data for a given [`Material`].
|
|
#[derive(Resource)]
|
|
pub struct MaterialPipeline<M: Material> {
|
|
pub mesh_pipeline: MeshPipeline,
|
|
pub material_layout: BindGroupLayout,
|
|
pub vertex_shader: Option<Handle<Shader>>,
|
|
pub fragment_shader: Option<Handle<Shader>>,
|
|
/// Whether this material *actually* uses bindless resources, taking the
|
|
/// platform support (or lack thereof) of bindless resources into account.
|
|
pub bindless: bool,
|
|
pub marker: PhantomData<M>,
|
|
}
|
|
|
|
impl<M: Material> Clone for MaterialPipeline<M> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
mesh_pipeline: self.mesh_pipeline.clone(),
|
|
material_layout: self.material_layout.clone(),
|
|
vertex_shader: self.vertex_shader.clone(),
|
|
fragment_shader: self.fragment_shader.clone(),
|
|
bindless: self.bindless,
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<M: Material> SpecializedMeshPipeline for MaterialPipeline<M>
|
|
where
|
|
M::Data: PartialEq + Eq + Hash + Clone,
|
|
{
|
|
type Key = MaterialPipelineKey<M>;
|
|
|
|
fn specialize(
|
|
&self,
|
|
key: Self::Key,
|
|
layout: &MeshVertexBufferLayoutRef,
|
|
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
|
let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?;
|
|
if let Some(vertex_shader) = &self.vertex_shader {
|
|
descriptor.vertex.shader = vertex_shader.clone();
|
|
}
|
|
|
|
if let Some(fragment_shader) = &self.fragment_shader {
|
|
descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone();
|
|
}
|
|
|
|
descriptor.layout.insert(3, self.material_layout.clone());
|
|
|
|
M::specialize(self, &mut descriptor, layout, key)?;
|
|
|
|
// If bindless mode is on, add a `BINDLESS` define.
|
|
if self.bindless {
|
|
descriptor.vertex.shader_defs.push("BINDLESS".into());
|
|
if let Some(ref mut fragment) = descriptor.fragment {
|
|
fragment.shader_defs.push("BINDLESS".into());
|
|
}
|
|
}
|
|
|
|
Ok(descriptor)
|
|
}
|
|
}
|
|
|
|
impl<M: Material> FromWorld for MaterialPipeline<M> {
|
|
fn from_world(world: &mut World) -> Self {
|
|
let asset_server = world.resource::<AssetServer>();
|
|
let render_device = world.resource::<RenderDevice>();
|
|
|
|
MaterialPipeline {
|
|
mesh_pipeline: world.resource::<MeshPipeline>().clone(),
|
|
material_layout: M::bind_group_layout(render_device),
|
|
vertex_shader: match M::vertex_shader() {
|
|
ShaderRef::Default => None,
|
|
ShaderRef::Handle(handle) => Some(handle),
|
|
ShaderRef::Path(path) => Some(asset_server.load(path)),
|
|
},
|
|
fragment_shader: match M::fragment_shader() {
|
|
ShaderRef::Default => None,
|
|
ShaderRef::Handle(handle) => Some(handle),
|
|
ShaderRef::Path(path) => Some(asset_server.load(path)),
|
|
},
|
|
bindless: material_uses_bindless_resources::<M>(render_device),
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
type DrawMaterial<M> = (
|
|
SetItemPipeline,
|
|
SetMeshViewBindGroup<0>,
|
|
SetMeshViewBindingArrayBindGroup<1>,
|
|
SetMeshBindGroup<2>,
|
|
SetMaterialBindGroup<M, 3>,
|
|
DrawMesh,
|
|
);
|
|
|
|
/// Sets the bind group for a given [`Material`] at the configured `I` index.
|
|
pub struct SetMaterialBindGroup<M: Material, const I: usize>(PhantomData<M>);
|
|
impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterialBindGroup<M, I> {
|
|
type Param = (
|
|
SRes<RenderAssets<PreparedMaterial<M>>>,
|
|
SRes<RenderMaterialInstances>,
|
|
SRes<MaterialBindGroupAllocator<M>>,
|
|
);
|
|
type ViewQuery = ();
|
|
type ItemQuery = ();
|
|
|
|
#[inline]
|
|
fn render<'w>(
|
|
item: &P,
|
|
_view: (),
|
|
_item_query: Option<()>,
|
|
(materials, material_instances, material_bind_group_allocator): SystemParamItem<
|
|
'w,
|
|
'_,
|
|
Self::Param,
|
|
>,
|
|
pass: &mut TrackedRenderPass<'w>,
|
|
) -> RenderCommandResult {
|
|
let materials = materials.into_inner();
|
|
let material_instances = material_instances.into_inner();
|
|
let material_bind_group_allocator = material_bind_group_allocator.into_inner();
|
|
|
|
let Some(material_instance) = material_instances.instances.get(&item.main_entity()) else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
let Some(material) = materials.get(material_asset_id) else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group)
|
|
else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
let Some(bind_group) = material_bind_group.bind_group() else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
pass.set_bind_group(I, bind_group, &[]);
|
|
RenderCommandResult::Success
|
|
}
|
|
}
|
|
|
|
/// Stores all extracted instances of all [`Material`]s in the render world.
|
|
#[derive(Resource, Default)]
|
|
pub struct RenderMaterialInstances {
|
|
/// Maps from each entity in the main world to the
|
|
/// [`RenderMaterialInstance`] associated with it.
|
|
pub instances: MainEntityHashMap<RenderMaterialInstance>,
|
|
/// A monotonically-increasing counter, which we use to sweep
|
|
/// [`RenderMaterialInstances::instances`] when the entities and/or required
|
|
/// components are removed.
|
|
current_change_tick: Tick,
|
|
}
|
|
|
|
impl RenderMaterialInstances {
|
|
/// Returns the mesh material ID for the entity with the given mesh, or a
|
|
/// dummy mesh material ID if the mesh has no material ID.
|
|
///
|
|
/// Meshes almost always have materials, but in very specific circumstances
|
|
/// involving custom pipelines they won't. (See the
|
|
/// `specialized_mesh_pipelines` example.)
|
|
pub(crate) fn mesh_material(&self, entity: MainEntity) -> UntypedAssetId {
|
|
match self.instances.get(&entity) {
|
|
Some(render_instance) => render_instance.asset_id,
|
|
None => DUMMY_MESH_MATERIAL.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The material associated with a single mesh instance in the main world.
|
|
///
|
|
/// Note that this uses an [`UntypedAssetId`] and isn't generic over the
|
|
/// material type, for simplicity.
|
|
pub struct RenderMaterialInstance {
|
|
/// The material asset.
|
|
pub(crate) asset_id: UntypedAssetId,
|
|
/// The [`RenderMaterialInstances::current_change_tick`] at which this
|
|
/// material instance was last modified.
|
|
last_change_tick: Tick,
|
|
}
|
|
|
|
/// A [`SystemSet`] that contains all `extract_mesh_materials` systems.
|
|
#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)]
|
|
pub struct MaterialExtractionSystems;
|
|
|
|
/// Deprecated alias for [`MaterialExtractionSystems`].
|
|
#[deprecated(since = "0.17.0", note = "Renamed to `MaterialExtractionSystems`.")]
|
|
pub type ExtractMaterialsSet = MaterialExtractionSystems;
|
|
|
|
pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey {
|
|
match alpha_mode {
|
|
// Premultiplied and Add share the same pipeline key
|
|
// They're made distinct in the PBR shader, via `premultiply_alpha()`
|
|
AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA,
|
|
AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA,
|
|
AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY,
|
|
AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD,
|
|
AlphaMode::AlphaToCoverage => match *msaa {
|
|
Msaa::Off => MeshPipelineKey::MAY_DISCARD,
|
|
_ => MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE,
|
|
},
|
|
_ => MeshPipelineKey::NONE,
|
|
}
|
|
}
|
|
|
|
pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey {
|
|
match tonemapping {
|
|
Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE,
|
|
Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD,
|
|
Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE,
|
|
Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED,
|
|
Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX,
|
|
Tonemapping::SomewhatBoringDisplayTransform => {
|
|
MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
|
|
}
|
|
Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
|
|
Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
|
|
}
|
|
}
|
|
|
|
pub const fn screen_space_specular_transmission_pipeline_key(
|
|
screen_space_transmissive_blur_quality: ScreenSpaceTransmissionQuality,
|
|
) -> MeshPipelineKey {
|
|
match screen_space_transmissive_blur_quality {
|
|
ScreenSpaceTransmissionQuality::Low => {
|
|
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW
|
|
}
|
|
ScreenSpaceTransmissionQuality::Medium => {
|
|
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM
|
|
}
|
|
ScreenSpaceTransmissionQuality::High => {
|
|
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH
|
|
}
|
|
ScreenSpaceTransmissionQuality::Ultra => {
|
|
MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A system that ensures that
|
|
/// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts meshes
|
|
/// whose materials changed.
|
|
///
|
|
/// As [`crate::render::mesh::collect_meshes_for_gpu_building`] only considers
|
|
/// meshes that were newly extracted, and it writes information from the
|
|
/// [`RenderMaterialInstances`] into the
|
|
/// [`crate::render::mesh::MeshInputUniform`], we must tell
|
|
/// [`crate::render::mesh::extract_meshes_for_gpu_building`] to re-extract a
|
|
/// mesh if its material changed. Otherwise, the material binding information in
|
|
/// the [`crate::render::mesh::MeshInputUniform`] might not be updated properly.
|
|
/// The easiest way to ensure that
|
|
/// [`crate::render::mesh::extract_meshes_for_gpu_building`] re-extracts a mesh
|
|
/// is to mark its [`Mesh3d`] as changed, so that's what this system does.
|
|
fn mark_meshes_as_changed_if_their_materials_changed<M>(
|
|
mut changed_meshes_query: Query<
|
|
&mut Mesh3d,
|
|
Or<(Changed<MeshMaterial3d<M>>, AssetChanged<MeshMaterial3d<M>>)>,
|
|
>,
|
|
) where
|
|
M: Material,
|
|
{
|
|
for mut mesh in &mut changed_meshes_query {
|
|
mesh.set_changed();
|
|
}
|
|
}
|
|
|
|
/// Fills the [`RenderMaterialInstances`] resources from the meshes in the
|
|
/// scene.
|
|
fn extract_mesh_materials<M: Material>(
|
|
mut material_instances: ResMut<RenderMaterialInstances>,
|
|
changed_meshes_query: Extract<
|
|
Query<
|
|
(Entity, &ViewVisibility, &MeshMaterial3d<M>),
|
|
Or<(Changed<ViewVisibility>, Changed<MeshMaterial3d<M>>)>,
|
|
>,
|
|
>,
|
|
) {
|
|
let last_change_tick = material_instances.current_change_tick;
|
|
|
|
for (entity, view_visibility, material) in &changed_meshes_query {
|
|
if view_visibility.get() {
|
|
material_instances.instances.insert(
|
|
entity.into(),
|
|
RenderMaterialInstance {
|
|
asset_id: material.id().untyped(),
|
|
last_change_tick,
|
|
},
|
|
);
|
|
} else {
|
|
material_instances
|
|
.instances
|
|
.remove(&MainEntity::from(entity));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Removes mesh materials from [`RenderMaterialInstances`] when their
|
|
/// [`MeshMaterial3d`] components are removed.
|
|
///
|
|
/// This is tricky because we have to deal with the case in which a material of
|
|
/// type A was removed and replaced with a material of type B in the same frame
|
|
/// (which is actually somewhat common of an operation). In this case, even
|
|
/// though an entry will be present in `RemovedComponents<MeshMaterial3d<A>>`,
|
|
/// we must not remove the entry in `RenderMaterialInstances` which corresponds
|
|
/// to material B. To handle this case, we use change ticks to avoid removing
|
|
/// the entry if it was updated this frame.
|
|
///
|
|
/// This is the first of two sweep phases. Because this phase runs once per
|
|
/// material type, we need a second phase in order to guarantee that we only
|
|
/// bump [`RenderMaterialInstances::current_change_tick`] once.
|
|
fn early_sweep_material_instances<M>(
|
|
mut material_instances: ResMut<RenderMaterialInstances>,
|
|
mut removed_materials_query: Extract<RemovedComponents<MeshMaterial3d<M>>>,
|
|
) where
|
|
M: Material,
|
|
{
|
|
let last_change_tick = material_instances.current_change_tick;
|
|
|
|
for entity in removed_materials_query.read() {
|
|
if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
|
|
// Only sweep the entry if it wasn't updated this frame.
|
|
if occupied_entry.get().last_change_tick != last_change_tick {
|
|
occupied_entry.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Removes mesh materials from [`RenderMaterialInstances`] when their
|
|
/// [`ViewVisibility`] components are removed.
|
|
///
|
|
/// This runs after all invocations of [`early_sweep_material_instances`] and is
|
|
/// responsible for bumping [`RenderMaterialInstances::current_change_tick`] in
|
|
/// preparation for a new frame.
|
|
pub(crate) fn late_sweep_material_instances(
|
|
mut material_instances: ResMut<RenderMaterialInstances>,
|
|
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
|
|
) {
|
|
let last_change_tick = material_instances.current_change_tick;
|
|
|
|
for entity in removed_visibilities_query.read() {
|
|
if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
|
|
// Only sweep the entry if it wasn't updated this frame. It's
|
|
// possible that a `ViewVisibility` component was removed and
|
|
// re-added in the same frame.
|
|
if occupied_entry.get().last_change_tick != last_change_tick {
|
|
occupied_entry.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
material_instances
|
|
.current_change_tick
|
|
.set(last_change_tick.get() + 1);
|
|
}
|
|
|
|
pub fn extract_entities_needs_specialization<M>(
|
|
entities_needing_specialization: Extract<Res<EntitiesNeedingSpecialization<M>>>,
|
|
mut entity_specialization_ticks: ResMut<EntitySpecializationTicks<M>>,
|
|
mut removed_mesh_material_components: Extract<RemovedComponents<MeshMaterial3d<M>>>,
|
|
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache<M>>,
|
|
mut specialized_prepass_material_pipeline_cache: Option<
|
|
ResMut<SpecializedPrepassMaterialPipelineCache<M>>,
|
|
>,
|
|
mut specialized_shadow_material_pipeline_cache: Option<
|
|
ResMut<SpecializedShadowMaterialPipelineCache<M>>,
|
|
>,
|
|
views: Query<&ExtractedView>,
|
|
ticks: SystemChangeTick,
|
|
) where
|
|
M: Material,
|
|
{
|
|
// Clean up any despawned entities, we do this first in case the removed material was re-added
|
|
// the same frame, thus will appear both in the removed components list and have been added to
|
|
// the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter
|
|
for entity in removed_mesh_material_components.read() {
|
|
entity_specialization_ticks.remove(&MainEntity::from(entity));
|
|
for view in views {
|
|
if let Some(cache) =
|
|
specialized_material_pipeline_cache.get_mut(&view.retained_view_entity)
|
|
{
|
|
cache.remove(&MainEntity::from(entity));
|
|
}
|
|
if let Some(cache) = specialized_prepass_material_pipeline_cache
|
|
.as_mut()
|
|
.and_then(|c| c.get_mut(&view.retained_view_entity))
|
|
{
|
|
cache.remove(&MainEntity::from(entity));
|
|
}
|
|
if let Some(cache) = specialized_shadow_material_pipeline_cache
|
|
.as_mut()
|
|
.and_then(|c| c.get_mut(&view.retained_view_entity))
|
|
{
|
|
cache.remove(&MainEntity::from(entity));
|
|
}
|
|
}
|
|
}
|
|
|
|
for entity in entities_needing_specialization.iter() {
|
|
// Update the entity's specialization tick with this run's tick
|
|
entity_specialization_ticks.insert((*entity).into(), ticks.this_run());
|
|
}
|
|
}
|
|
|
|
#[derive(Resource, Deref, DerefMut, Clone, Debug)]
|
|
pub struct EntitiesNeedingSpecialization<M> {
|
|
#[deref]
|
|
pub entities: Vec<Entity>,
|
|
_marker: PhantomData<M>,
|
|
}
|
|
|
|
impl<M> Default for EntitiesNeedingSpecialization<M> {
|
|
fn default() -> Self {
|
|
Self {
|
|
entities: Default::default(),
|
|
_marker: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Resource, Deref, DerefMut, Clone, Debug)]
|
|
pub struct EntitySpecializationTicks<M> {
|
|
#[deref]
|
|
pub entities: MainEntityHashMap<Tick>,
|
|
_marker: PhantomData<M>,
|
|
}
|
|
|
|
impl<M> Default for EntitySpecializationTicks<M> {
|
|
fn default() -> Self {
|
|
Self {
|
|
entities: MainEntityHashMap::default(),
|
|
_marker: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Stores the [`SpecializedMaterialViewPipelineCache`] for each view.
|
|
#[derive(Resource, Deref, DerefMut)]
|
|
pub struct SpecializedMaterialPipelineCache<M> {
|
|
// view entity -> view pipeline cache
|
|
#[deref]
|
|
map: HashMap<RetainedViewEntity, SpecializedMaterialViewPipelineCache<M>>,
|
|
marker: PhantomData<M>,
|
|
}
|
|
|
|
/// Stores the cached render pipeline ID for each entity in a single view, as
|
|
/// well as the last time it was changed.
|
|
#[derive(Deref, DerefMut)]
|
|
pub struct SpecializedMaterialViewPipelineCache<M> {
|
|
// material entity -> (tick, pipeline_id)
|
|
#[deref]
|
|
map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
|
|
marker: PhantomData<M>,
|
|
}
|
|
|
|
impl<M> Default for SpecializedMaterialPipelineCache<M> {
|
|
fn default() -> Self {
|
|
Self {
|
|
map: HashMap::default(),
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<M> Default for SpecializedMaterialViewPipelineCache<M> {
|
|
fn default() -> Self {
|
|
Self {
|
|
map: MainEntityHashMap::default(),
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn check_entities_needing_specialization<M>(
|
|
needs_specialization: Query<
|
|
Entity,
|
|
(
|
|
Or<(
|
|
Changed<Mesh3d>,
|
|
AssetChanged<Mesh3d>,
|
|
Changed<MeshMaterial3d<M>>,
|
|
AssetChanged<MeshMaterial3d<M>>,
|
|
)>,
|
|
With<MeshMaterial3d<M>>,
|
|
),
|
|
>,
|
|
mut par_local: Local<Parallel<Vec<Entity>>>,
|
|
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
|
|
) where
|
|
M: Material,
|
|
{
|
|
entities_needing_specialization.clear();
|
|
|
|
needs_specialization
|
|
.par_iter()
|
|
.for_each(|entity| par_local.borrow_local_mut().push(entity));
|
|
|
|
par_local.drain_into(&mut entities_needing_specialization);
|
|
}
|
|
|
|
pub fn specialize_material_meshes<M: Material>(
|
|
render_meshes: Res<RenderAssets<RenderMesh>>,
|
|
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
|
render_mesh_instances: Res<RenderMeshInstances>,
|
|
render_material_instances: Res<RenderMaterialInstances>,
|
|
render_lightmaps: Res<RenderLightmaps>,
|
|
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
|
(
|
|
material_bind_group_allocator,
|
|
opaque_render_phases,
|
|
alpha_mask_render_phases,
|
|
transmissive_render_phases,
|
|
transparent_render_phases,
|
|
): (
|
|
Res<MaterialBindGroupAllocator<M>>,
|
|
Res<ViewBinnedRenderPhases<Opaque3d>>,
|
|
Res<ViewBinnedRenderPhases<AlphaMask3d>>,
|
|
Res<ViewSortedRenderPhases<Transmissive3d>>,
|
|
Res<ViewSortedRenderPhases<Transparent3d>>,
|
|
),
|
|
views: Query<(&ExtractedView, &RenderVisibleEntities)>,
|
|
view_key_cache: Res<ViewKeyCache>,
|
|
entity_specialization_ticks: Res<EntitySpecializationTicks<M>>,
|
|
view_specialization_ticks: Res<ViewSpecializationTicks>,
|
|
mut specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache<M>>,
|
|
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
|
|
pipeline: Res<MaterialPipeline<M>>,
|
|
pipeline_cache: Res<PipelineCache>,
|
|
ticks: SystemChangeTick,
|
|
) where
|
|
M::Data: PartialEq + Eq + Hash + Clone,
|
|
{
|
|
// Record the retained IDs of all shadow views so that we can expire old
|
|
// pipeline IDs.
|
|
let mut all_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
|
|
|
|
for (view, visible_entities) in &views {
|
|
all_views.insert(view.retained_view_entity);
|
|
|
|
if !transparent_render_phases.contains_key(&view.retained_view_entity)
|
|
&& !opaque_render_phases.contains_key(&view.retained_view_entity)
|
|
&& !alpha_mask_render_phases.contains_key(&view.retained_view_entity)
|
|
&& !transmissive_render_phases.contains_key(&view.retained_view_entity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else {
|
|
continue;
|
|
};
|
|
|
|
let view_tick = view_specialization_ticks
|
|
.get(&view.retained_view_entity)
|
|
.unwrap();
|
|
let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
|
|
.entry(view.retained_view_entity)
|
|
.or_default();
|
|
|
|
for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
|
let Some(material_instance) = render_material_instances.instances.get(visible_entity)
|
|
else {
|
|
continue;
|
|
};
|
|
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
|
continue;
|
|
};
|
|
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
|
|
else {
|
|
continue;
|
|
};
|
|
let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
|
|
let last_specialized_tick = view_specialized_material_pipeline_cache
|
|
.get(visible_entity)
|
|
.map(|(tick, _)| *tick);
|
|
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
|
|
view_tick.is_newer_than(tick, ticks.this_run())
|
|
|| entity_tick.is_newer_than(tick, ticks.this_run())
|
|
});
|
|
if !needs_specialization {
|
|
continue;
|
|
}
|
|
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
|
|
continue;
|
|
};
|
|
let Some(material) = render_materials.get(material_asset_id) else {
|
|
continue;
|
|
};
|
|
let Some(material_bind_group) =
|
|
material_bind_group_allocator.get(material.binding.group)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits;
|
|
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(
|
|
material.properties.alpha_mode,
|
|
&Msaa::from_samples(view_key.msaa_samples()),
|
|
));
|
|
let mut mesh_key = *view_key
|
|
| MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
|
|
| mesh_pipeline_key_bits;
|
|
|
|
if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
|
|
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
|
|
|
if lightmap.bicubic_sampling {
|
|
mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
|
|
}
|
|
}
|
|
|
|
if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
|
|
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
|
|
}
|
|
|
|
if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
|
|
// If the previous frame have skins or morph targets, note that.
|
|
if mesh_instance
|
|
.flags
|
|
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
|
|
{
|
|
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
|
|
}
|
|
if mesh_instance
|
|
.flags
|
|
.contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
|
|
{
|
|
mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
|
|
}
|
|
}
|
|
|
|
let key = MaterialPipelineKey {
|
|
mesh_key,
|
|
bind_group_data: material_bind_group
|
|
.get_extra_data(material.binding.slot)
|
|
.clone(),
|
|
};
|
|
let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, key, &mesh.layout);
|
|
let pipeline_id = match pipeline_id {
|
|
Ok(id) => id,
|
|
Err(err) => {
|
|
error!("{}", err);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
view_specialized_material_pipeline_cache
|
|
.insert(*visible_entity, (ticks.this_run(), pipeline_id));
|
|
}
|
|
}
|
|
|
|
// Delete specialized pipelines belonging to views that have expired.
|
|
specialized_material_pipeline_cache
|
|
.retain(|retained_view_entity, _| all_views.contains(retained_view_entity));
|
|
}
|
|
|
|
/// For each view, iterates over all the meshes visible from that view and adds
|
|
/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate.
|
|
pub fn queue_material_meshes<M: Material>(
|
|
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
|
render_mesh_instances: Res<RenderMeshInstances>,
|
|
render_material_instances: Res<RenderMaterialInstances>,
|
|
mesh_allocator: Res<MeshAllocator>,
|
|
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
|
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
|
|
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
|
|
mut transmissive_render_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
|
|
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
|
|
views: Query<(&ExtractedView, &RenderVisibleEntities)>,
|
|
specialized_material_pipeline_cache: ResMut<SpecializedMaterialPipelineCache<M>>,
|
|
) where
|
|
M::Data: PartialEq + Eq + Hash + Clone,
|
|
{
|
|
for (view, visible_entities) in &views {
|
|
let (
|
|
Some(opaque_phase),
|
|
Some(alpha_mask_phase),
|
|
Some(transmissive_phase),
|
|
Some(transparent_phase),
|
|
) = (
|
|
opaque_render_phases.get_mut(&view.retained_view_entity),
|
|
alpha_mask_render_phases.get_mut(&view.retained_view_entity),
|
|
transmissive_render_phases.get_mut(&view.retained_view_entity),
|
|
transparent_render_phases.get_mut(&view.retained_view_entity),
|
|
)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
let Some(view_specialized_material_pipeline_cache) =
|
|
specialized_material_pipeline_cache.get(&view.retained_view_entity)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
let rangefinder = view.rangefinder3d();
|
|
for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
|
|
let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache
|
|
.get(visible_entity)
|
|
.map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id))
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
// Skip the entity if it's cached in a bin and up to date.
|
|
if opaque_phase.validate_cached_entity(*visible_entity, current_change_tick)
|
|
|| alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
let Some(material_instance) = render_material_instances.instances.get(visible_entity)
|
|
else {
|
|
continue;
|
|
};
|
|
let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
|
|
continue;
|
|
};
|
|
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
|
|
else {
|
|
continue;
|
|
};
|
|
let Some(material) = render_materials.get(material_asset_id) else {
|
|
continue;
|
|
};
|
|
|
|
// Fetch the slabs that this mesh resides in.
|
|
let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
|
|
|
|
match material.properties.render_phase_type {
|
|
RenderPhaseType::Transmissive => {
|
|
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
|
+ material.properties.depth_bias;
|
|
transmissive_phase.add(Transmissive3d {
|
|
entity: (*render_entity, *visible_entity),
|
|
draw_function: material.properties.draw_function_id,
|
|
pipeline: pipeline_id,
|
|
distance,
|
|
batch_range: 0..1,
|
|
extra_index: PhaseItemExtraIndex::None,
|
|
indexed: index_slab.is_some(),
|
|
});
|
|
}
|
|
RenderPhaseType::Opaque => {
|
|
if material.properties.render_method == OpaqueRendererMethod::Deferred {
|
|
// Even though we aren't going to insert the entity into
|
|
// a bin, we still want to update its cache entry. That
|
|
// way, we know we don't need to re-examine it in future
|
|
// frames.
|
|
opaque_phase.update_cache(*visible_entity, None, current_change_tick);
|
|
continue;
|
|
}
|
|
let batch_set_key = Opaque3dBatchSetKey {
|
|
pipeline: pipeline_id,
|
|
draw_function: material.properties.draw_function_id,
|
|
material_bind_group_index: Some(material.binding.group.0),
|
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
|
index_slab,
|
|
lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index),
|
|
};
|
|
let bin_key = Opaque3dBinKey {
|
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
|
};
|
|
opaque_phase.add(
|
|
batch_set_key,
|
|
bin_key,
|
|
(*render_entity, *visible_entity),
|
|
mesh_instance.current_uniform_index,
|
|
BinnedRenderPhaseType::mesh(
|
|
mesh_instance.should_batch(),
|
|
&gpu_preprocessing_support,
|
|
),
|
|
current_change_tick,
|
|
);
|
|
}
|
|
// Alpha mask
|
|
RenderPhaseType::AlphaMask => {
|
|
let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
|
|
draw_function: material.properties.draw_function_id,
|
|
pipeline: pipeline_id,
|
|
material_bind_group_index: Some(material.binding.group.0),
|
|
vertex_slab: vertex_slab.unwrap_or_default(),
|
|
index_slab,
|
|
};
|
|
let bin_key = OpaqueNoLightmap3dBinKey {
|
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
|
};
|
|
alpha_mask_phase.add(
|
|
batch_set_key,
|
|
bin_key,
|
|
(*render_entity, *visible_entity),
|
|
mesh_instance.current_uniform_index,
|
|
BinnedRenderPhaseType::mesh(
|
|
mesh_instance.should_batch(),
|
|
&gpu_preprocessing_support,
|
|
),
|
|
current_change_tick,
|
|
);
|
|
}
|
|
RenderPhaseType::Transparent => {
|
|
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
|
+ material.properties.depth_bias;
|
|
transparent_phase.add(Transparent3d {
|
|
entity: (*render_entity, *visible_entity),
|
|
draw_function: material.properties.draw_function_id,
|
|
pipeline: pipeline_id,
|
|
distance,
|
|
batch_range: 0..1,
|
|
extra_index: PhaseItemExtraIndex::None,
|
|
indexed: index_slab.is_some(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Default render method used for opaque materials.
|
|
#[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)]
|
|
#[reflect(Resource, Default, Debug, Clone)]
|
|
pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod);
|
|
|
|
impl DefaultOpaqueRendererMethod {
|
|
pub fn forward() -> Self {
|
|
DefaultOpaqueRendererMethod(OpaqueRendererMethod::Forward)
|
|
}
|
|
|
|
pub fn deferred() -> Self {
|
|
DefaultOpaqueRendererMethod(OpaqueRendererMethod::Deferred)
|
|
}
|
|
|
|
pub fn set_to_forward(&mut self) {
|
|
self.0 = OpaqueRendererMethod::Forward;
|
|
}
|
|
|
|
pub fn set_to_deferred(&mut self) {
|
|
self.0 = OpaqueRendererMethod::Deferred;
|
|
}
|
|
}
|
|
|
|
/// Render method used for opaque materials.
|
|
///
|
|
/// The forward rendering main pass draws each mesh entity and shades it according to its
|
|
/// corresponding material and the lights that affect it. Some render features like Screen Space
|
|
/// Ambient Occlusion require running depth and normal prepasses, that are 'deferred'-like
|
|
/// prepasses over all mesh entities to populate depth and normal textures. This means that when
|
|
/// using render features that require running prepasses, multiple passes over all visible geometry
|
|
/// are required. This can be slow if there is a lot of geometry that cannot be batched into few
|
|
/// draws.
|
|
///
|
|
/// Deferred rendering runs a prepass to gather not only geometric information like depth and
|
|
/// normals, but also all the material properties like base color, emissive color, reflectance,
|
|
/// metalness, etc, and writes them into a deferred 'g-buffer' texture. The deferred main pass is
|
|
/// then a fullscreen pass that reads data from these textures and executes shading. This allows
|
|
/// for one pass over geometry, but is at the cost of not being able to use MSAA, and has heavier
|
|
/// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices.
|
|
///
|
|
/// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used.
|
|
#[derive(Default, Clone, Copy, Debug, PartialEq, Reflect)]
|
|
#[reflect(Default, Clone, PartialEq)]
|
|
pub enum OpaqueRendererMethod {
|
|
#[default]
|
|
Forward,
|
|
Deferred,
|
|
Auto,
|
|
}
|
|
|
|
/// Common [`Material`] properties, calculated for a specific material instance.
|
|
pub struct MaterialProperties {
|
|
/// Is this material should be rendered by the deferred renderer when.
|
|
/// [`AlphaMode::Opaque`] or [`AlphaMode::Mask`]
|
|
pub render_method: OpaqueRendererMethod,
|
|
/// The [`AlphaMode`] of this material.
|
|
pub alpha_mode: AlphaMode,
|
|
/// The bits in the [`MeshPipelineKey`] for this material.
|
|
///
|
|
/// These are precalculated so that we can just "or" them together in
|
|
/// [`queue_material_meshes`].
|
|
pub mesh_pipeline_key_bits: MeshPipelineKey,
|
|
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
|
|
/// for meshes with equal depth, to avoid z-fighting.
|
|
/// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
|
|
pub depth_bias: f32,
|
|
/// Whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture).
|
|
///
|
|
/// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires
|
|
/// rendering to take place in a separate [`Transmissive3d`] pass.
|
|
pub reads_view_transmission_texture: bool,
|
|
pub render_phase_type: RenderPhaseType,
|
|
pub draw_function_id: DrawFunctionId,
|
|
pub prepass_draw_function_id: Option<DrawFunctionId>,
|
|
pub deferred_draw_function_id: Option<DrawFunctionId>,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub enum RenderPhaseType {
|
|
Opaque,
|
|
AlphaMask,
|
|
Transmissive,
|
|
Transparent,
|
|
}
|
|
|
|
/// A resource that maps each untyped material ID to its binding.
|
|
///
|
|
/// This duplicates information in `RenderAssets<M>`, but it doesn't have the
|
|
/// `M` type parameter, so it can be used in untyped contexts like
|
|
/// [`crate::render::mesh::collect_meshes_for_gpu_building`].
|
|
#[derive(Resource, Default, Deref, DerefMut)]
|
|
pub struct RenderMaterialBindings(HashMap<UntypedAssetId, MaterialBindingId>);
|
|
|
|
/// Data prepared for a [`Material`] instance.
|
|
pub struct PreparedMaterial<M: Material> {
|
|
pub binding: MaterialBindingId,
|
|
pub properties: MaterialProperties,
|
|
pub phantom: PhantomData<M>,
|
|
}
|
|
|
|
impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
|
type SourceAsset = M;
|
|
|
|
type Param = (
|
|
SRes<RenderDevice>,
|
|
SRes<MaterialPipeline<M>>,
|
|
SRes<DefaultOpaqueRendererMethod>,
|
|
SResMut<MaterialBindGroupAllocator<M>>,
|
|
SResMut<RenderMaterialBindings>,
|
|
SRes<DrawFunctions<Opaque3d>>,
|
|
SRes<DrawFunctions<AlphaMask3d>>,
|
|
SRes<DrawFunctions<Transmissive3d>>,
|
|
SRes<DrawFunctions<Transparent3d>>,
|
|
SRes<DrawFunctions<Opaque3dPrepass>>,
|
|
SRes<DrawFunctions<AlphaMask3dPrepass>>,
|
|
SRes<DrawFunctions<Opaque3dDeferred>>,
|
|
SRes<DrawFunctions<AlphaMask3dDeferred>>,
|
|
M::Param,
|
|
);
|
|
|
|
fn prepare_asset(
|
|
material: Self::SourceAsset,
|
|
material_id: AssetId<Self::SourceAsset>,
|
|
(
|
|
render_device,
|
|
pipeline,
|
|
default_opaque_render_method,
|
|
bind_group_allocator,
|
|
render_material_bindings,
|
|
opaque_draw_functions,
|
|
alpha_mask_draw_functions,
|
|
transmissive_draw_functions,
|
|
transparent_draw_functions,
|
|
opaque_prepass_draw_functions,
|
|
alpha_mask_prepass_draw_functions,
|
|
opaque_deferred_draw_functions,
|
|
alpha_mask_deferred_draw_functions,
|
|
material_param,
|
|
): &mut SystemParamItem<Self::Param>,
|
|
_: Option<&Self>,
|
|
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
|
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial<M>>();
|
|
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial<M>>();
|
|
let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial<M>>();
|
|
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial<M>>();
|
|
let draw_opaque_prepass = opaque_prepass_draw_functions
|
|
.read()
|
|
.get_id::<DrawPrepass<M>>();
|
|
let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions
|
|
.read()
|
|
.get_id::<DrawPrepass<M>>();
|
|
let draw_opaque_deferred = opaque_deferred_draw_functions
|
|
.read()
|
|
.get_id::<DrawPrepass<M>>();
|
|
let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions
|
|
.read()
|
|
.get_id::<DrawPrepass<M>>();
|
|
|
|
let render_method = match material.opaque_render_method() {
|
|
OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,
|
|
OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred,
|
|
OpaqueRendererMethod::Auto => default_opaque_render_method.0,
|
|
};
|
|
|
|
let mut mesh_pipeline_key_bits = MeshPipelineKey::empty();
|
|
mesh_pipeline_key_bits.set(
|
|
MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE,
|
|
material.reads_view_transmission_texture(),
|
|
);
|
|
|
|
let reads_view_transmission_texture =
|
|
mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE);
|
|
|
|
let render_phase_type = match material.alpha_mode() {
|
|
AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => {
|
|
RenderPhaseType::Transparent
|
|
}
|
|
_ if reads_view_transmission_texture => RenderPhaseType::Transmissive,
|
|
AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque,
|
|
AlphaMode::Mask(_) => RenderPhaseType::AlphaMask,
|
|
};
|
|
|
|
let draw_function_id = match render_phase_type {
|
|
RenderPhaseType::Opaque => draw_opaque_pbr,
|
|
RenderPhaseType::AlphaMask => draw_alpha_mask_pbr,
|
|
RenderPhaseType::Transmissive => draw_transmissive_pbr,
|
|
RenderPhaseType::Transparent => draw_transparent_pbr,
|
|
};
|
|
let prepass_draw_function_id = match render_phase_type {
|
|
RenderPhaseType::Opaque => draw_opaque_prepass,
|
|
RenderPhaseType::AlphaMask => draw_alpha_mask_prepass,
|
|
_ => None,
|
|
};
|
|
let deferred_draw_function_id = match render_phase_type {
|
|
RenderPhaseType::Opaque => draw_opaque_deferred,
|
|
RenderPhaseType::AlphaMask => draw_alpha_mask_deferred,
|
|
_ => None,
|
|
};
|
|
|
|
match material.unprepared_bind_group(
|
|
&pipeline.material_layout,
|
|
render_device,
|
|
material_param,
|
|
false,
|
|
) {
|
|
Ok(unprepared) => {
|
|
// Allocate or update the material.
|
|
let binding = match render_material_bindings.entry(material_id.into()) {
|
|
Entry::Occupied(mut occupied_entry) => {
|
|
// TODO: Have a fast path that doesn't require
|
|
// recreating the bind group if only buffer contents
|
|
// change. For now, we just delete and recreate the bind
|
|
// group.
|
|
bind_group_allocator.free(*occupied_entry.get());
|
|
let new_binding = bind_group_allocator
|
|
.allocate_unprepared(unprepared, &pipeline.material_layout);
|
|
*occupied_entry.get_mut() = new_binding;
|
|
new_binding
|
|
}
|
|
Entry::Vacant(vacant_entry) => *vacant_entry.insert(
|
|
bind_group_allocator
|
|
.allocate_unprepared(unprepared, &pipeline.material_layout),
|
|
),
|
|
};
|
|
|
|
Ok(PreparedMaterial {
|
|
binding,
|
|
properties: MaterialProperties {
|
|
alpha_mode: material.alpha_mode(),
|
|
depth_bias: material.depth_bias(),
|
|
reads_view_transmission_texture,
|
|
render_phase_type,
|
|
draw_function_id,
|
|
prepass_draw_function_id,
|
|
render_method,
|
|
mesh_pipeline_key_bits,
|
|
deferred_draw_function_id,
|
|
},
|
|
phantom: PhantomData,
|
|
})
|
|
}
|
|
|
|
Err(AsBindGroupError::RetryNextUpdate) => {
|
|
Err(PrepareAssetError::RetryNextUpdate(material))
|
|
}
|
|
|
|
Err(AsBindGroupError::CreateBindGroupDirectly) => {
|
|
// This material has opted out of automatic bind group creation
|
|
// and is requesting a fully-custom bind group. Invoke
|
|
// `as_bind_group` as requested, and store the resulting bind
|
|
// group in the slot.
|
|
match material.as_bind_group(
|
|
&pipeline.material_layout,
|
|
render_device,
|
|
material_param,
|
|
) {
|
|
Ok(prepared_bind_group) => {
|
|
// Store the resulting bind group directly in the slot.
|
|
let material_binding_id =
|
|
bind_group_allocator.allocate_prepared(prepared_bind_group);
|
|
render_material_bindings.insert(material_id.into(), material_binding_id);
|
|
|
|
Ok(PreparedMaterial {
|
|
binding: material_binding_id,
|
|
properties: MaterialProperties {
|
|
alpha_mode: material.alpha_mode(),
|
|
depth_bias: material.depth_bias(),
|
|
reads_view_transmission_texture,
|
|
render_phase_type,
|
|
draw_function_id,
|
|
prepass_draw_function_id,
|
|
render_method,
|
|
mesh_pipeline_key_bits,
|
|
deferred_draw_function_id,
|
|
},
|
|
phantom: PhantomData,
|
|
})
|
|
}
|
|
|
|
Err(AsBindGroupError::RetryNextUpdate) => {
|
|
Err(PrepareAssetError::RetryNextUpdate(material))
|
|
}
|
|
|
|
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
|
|
}
|
|
}
|
|
|
|
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
|
|
}
|
|
}
|
|
|
|
fn unload_asset(
|
|
source_asset: AssetId<Self::SourceAsset>,
|
|
(_, _, _, bind_group_allocator, render_material_bindings, ..): &mut SystemParamItem<
|
|
Self::Param,
|
|
>,
|
|
) {
|
|
let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped())
|
|
else {
|
|
return;
|
|
};
|
|
bind_group_allocator.free(material_binding_id);
|
|
}
|
|
}
|
|
|
|
#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)]
|
|
pub struct MaterialBindGroupId(pub Option<BindGroupId>);
|
|
|
|
impl MaterialBindGroupId {
|
|
pub fn new(id: BindGroupId) -> Self {
|
|
Self(Some(id))
|
|
}
|
|
}
|
|
|
|
impl From<BindGroup> for MaterialBindGroupId {
|
|
fn from(value: BindGroup) -> Self {
|
|
Self::new(value.id())
|
|
}
|
|
}
|
|
|
|
/// Creates and/or recreates any bind groups that contain materials that were
|
|
/// modified this frame.
|
|
pub fn prepare_material_bind_groups<M>(
|
|
mut allocator: ResMut<MaterialBindGroupAllocator<M>>,
|
|
render_device: Res<RenderDevice>,
|
|
fallback_image: Res<FallbackImage>,
|
|
fallback_resources: Res<FallbackBindlessResources>,
|
|
) where
|
|
M: Material,
|
|
{
|
|
allocator.prepare_bind_groups(&render_device, &fallback_resources, &fallback_image);
|
|
}
|
|
|
|
/// Uploads the contents of all buffers that the [`MaterialBindGroupAllocator`]
|
|
/// manages to the GPU.
|
|
///
|
|
/// Non-bindless allocators don't currently manage any buffers, so this method
|
|
/// only has an effect for bindless allocators.
|
|
pub fn write_material_bind_group_buffers<M>(
|
|
mut allocator: ResMut<MaterialBindGroupAllocator<M>>,
|
|
render_device: Res<RenderDevice>,
|
|
render_queue: Res<RenderQueue>,
|
|
) where
|
|
M: Material,
|
|
{
|
|
allocator.write_buffers(&render_device, &render_queue);
|
|
}
|