mirror of
https://github.com/bevyengine/bevy
synced 2024-12-20 01:53:12 +00:00
dd812b3e49
# Objective In the Render World, there are a number of collections that are derived from Main World entities and are used to drive rendering. The most notable are: - `VisibleEntities`, which is generated in the `check_visibility` system and contains visible entities for a view. - `ExtractedInstances`, which maps entity ids to asset ids. In the old model, these collections were trivially kept in sync -- any extracted phase item could look itself up because the render entity id was guaranteed to always match the corresponding main world id. After #15320, this became much more complicated, and was leading to a number of subtle bugs in the Render World. The main rendering systems, i.e. `queue_material_meshes` and `queue_material2d_meshes`, follow a similar pattern: ```rust for visible_entity in visible_entities.iter::<With<Mesh2d>>() { let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { continue; }; // Look some more stuff up and specialize the pipeline... let bin_key = Opaque2dBinKey { pipeline: pipeline_id, draw_function: draw_opaque_2d, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material_2d.get_bind_group_id().0, }; opaque_phase.add( bin_key, *visible_entity, BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching), ); } ``` In this case, `visible_entities` and `render_mesh_instances` are both collections that are created and keyed by Main World entity ids, and so this lookup happens to work by coincidence. However, there is a major unintentional bug here: namely, because `visible_entities` is a collection of Main World ids, the phase item being queued is created with a Main World id rather than its correct Render World id. This happens to not break mesh rendering because the render commands used for drawing meshes do not access the `ItemQuery` parameter, but demonstrates the confusion that is now possible: our UI phase items are correctly being queued with Render World ids while our meshes aren't. Additionally, this makes it very easy and error prone to use the wrong entity id to look up things like assets. For example, if instead we ignored visibility checks and queued our meshes via a query, we'd have to be extra careful to use `&MainEntity` instead of the natural `Entity`. ## Solution Make all collections that are derived from Main World data use `MainEntity` as their key, to ensure type safety and avoid accidentally looking up data with the wrong entity id: ```rust pub type MainEntityHashMap<V> = hashbrown::HashMap<MainEntity, V, EntityHash>; ``` Additionally, we make all `PhaseItem` be able to provide both their Main and Render World ids, to allow render phase implementors maximum flexibility as to what id should be used to look up data. You can think of this like tracking at the type level whether something in the Render World should use it's "primary key", i.e. entity id, or needs to use a foreign key, i.e. `MainEntity`. ## Testing ##### TODO: This will require extensive testing to make sure things didn't break! Additionally, some extraction logic has become more complicated and needs to be checked for regressions. ## Migration Guide With the advent of the retained render world, collections that contain references to `Entity` that are extracted into the render world have been changed to contain `MainEntity` in order to prevent errors where a render world entity id is used to look up an item by accident. Custom rendering code may need to be changed to query for `&MainEntity` in order to look up the correct item from such a collection. Additionally, users who implement their own extraction logic for collections of main world entity should strongly consider extracting into a different collection that uses `MainEntity` as a key. Additionally, render phases now require specifying both the `Entity` and `MainEntity` for a given `PhaseItem`. Custom render phases should ensure `MainEntity` is available when queuing a phase item.
1072 lines
41 KiB
Rust
1072 lines
41 KiB
Rust
use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight};
|
|
#[cfg(feature = "meshlet")]
|
|
use crate::meshlet::{
|
|
prepare_material_meshlet_meshes_main_opaque_pass, queue_material_meshlet_meshes,
|
|
InstanceManager,
|
|
};
|
|
use crate::*;
|
|
use bevy_asset::{Asset, AssetId, AssetServer};
|
|
use bevy_core_pipeline::{
|
|
core_3d::{
|
|
AlphaMask3d, Camera3d, Opaque3d, Opaque3dBinKey, ScreenSpaceTransmissionQuality,
|
|
Transmissive3d, Transparent3d,
|
|
},
|
|
oit::OrderIndependentTransparencySettings,
|
|
prepass::{
|
|
DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, OpaqueNoLightmap3dBinKey,
|
|
},
|
|
tonemapping::{DebandDither, Tonemapping},
|
|
};
|
|
use bevy_derive::{Deref, DerefMut};
|
|
use bevy_ecs::{
|
|
prelude::*,
|
|
system::{lifetimeless::SRes, SystemParamItem},
|
|
};
|
|
use bevy_reflect::std_traits::ReflectDefault;
|
|
use bevy_reflect::Reflect;
|
|
use bevy_render::sync_world::MainEntityHashMap;
|
|
use bevy_render::view::RenderVisibleEntities;
|
|
use bevy_render::{
|
|
camera::TemporalJitter,
|
|
extract_resource::ExtractResource,
|
|
mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
|
|
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
|
render_phase::*,
|
|
render_resource::*,
|
|
renderer::RenderDevice,
|
|
view::{ExtractedView, Msaa, RenderVisibilityRanges, ViewVisibility},
|
|
Extract,
|
|
};
|
|
use bevy_utils::tracing::error;
|
|
use core::{
|
|
hash::Hash,
|
|
marker::PhantomData,
|
|
num::NonZero,
|
|
sync::atomic::{AtomicU32, Ordering},
|
|
};
|
|
|
|
/// 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_reflect::TypePath;
|
|
/// # use bevy_render::{mesh::{Mesh, Mesh3d}, render_resource::{AsBindGroup, ShaderRef}, texture::Image};
|
|
/// # 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(2) @binding(0) var<uniform> color: vec4<f32>;
|
|
/// @group(2) @binding(1) var color_texture: texture_2d<f32>;
|
|
/// @group(2) @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.
|
|
#[allow(unused_variables)]
|
|
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.
|
|
#[allow(unused_variables)]
|
|
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.
|
|
#[allow(unused_variables)]
|
|
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.
|
|
#[allow(unused_variables)]
|
|
#[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.
|
|
#[allow(unused_variables)]
|
|
#[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.
|
|
#[allow(unused_variables)]
|
|
#[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.
|
|
#[allow(unused_variables)]
|
|
#[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,
|
|
pub _marker: PhantomData<M>,
|
|
}
|
|
|
|
impl<M: Material> Default for MaterialPlugin<M> {
|
|
fn default() -> Self {
|
|
Self {
|
|
prepass_enabled: true,
|
|
shadows_enabled: true,
|
|
_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>>()
|
|
.register_type::<HasMaterial3d>()
|
|
.add_plugins(RenderAssetPlugin::<PreparedMaterial<M>>::default());
|
|
|
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
render_app
|
|
.init_resource::<DrawFunctions<Shadow>>()
|
|
.init_resource::<RenderMaterialInstances<M>>()
|
|
.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,
|
|
(clear_material_instances::<M>, extract_mesh_materials::<M>).chain(),
|
|
)
|
|
.add_systems(
|
|
Render,
|
|
queue_material_meshes::<M>
|
|
.in_set(RenderSet::QueueMeshes)
|
|
.after(prepare_assets::<PreparedMaterial<M>>),
|
|
);
|
|
|
|
if self.shadows_enabled {
|
|
render_app.add_systems(
|
|
Render,
|
|
queue_shadows::<M>
|
|
.in_set(RenderSet::QueueMeshes)
|
|
.after(prepare_assets::<PreparedMaterial<M>>),
|
|
);
|
|
}
|
|
|
|
#[cfg(feature = "meshlet")]
|
|
render_app.add_systems(
|
|
Render,
|
|
queue_material_meshlet_meshes::<M>
|
|
.in_set(RenderSet::QueueMeshes)
|
|
.run_if(resource_exists::<InstanceManager>),
|
|
);
|
|
|
|
#[cfg(feature = "meshlet")]
|
|
render_app.add_systems(
|
|
Render,
|
|
prepare_material_meshlet_meshes_main_opaque_pass::<M>
|
|
.in_set(RenderSet::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>::default());
|
|
}
|
|
}
|
|
|
|
fn finish(&self, app: &mut App) {
|
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
render_app.init_resource::<MaterialPipeline<M>>();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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>>,
|
|
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(),
|
|
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(2, self.material_layout.clone());
|
|
|
|
M::specialize(self, &mut descriptor, layout, key)?;
|
|
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)),
|
|
},
|
|
marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
type DrawMaterial<M> = (
|
|
SetItemPipeline,
|
|
SetMeshViewBindGroup<0>,
|
|
SetMeshBindGroup<1>,
|
|
SetMaterialBindGroup<M, 2>,
|
|
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<M>>,
|
|
);
|
|
type ViewQuery = ();
|
|
type ItemQuery = ();
|
|
|
|
#[inline]
|
|
fn render<'w>(
|
|
item: &P,
|
|
_view: (),
|
|
_item_query: Option<()>,
|
|
(materials, material_instances): SystemParamItem<'w, '_, Self::Param>,
|
|
pass: &mut TrackedRenderPass<'w>,
|
|
) -> RenderCommandResult {
|
|
let materials = materials.into_inner();
|
|
let material_instances = material_instances.into_inner();
|
|
|
|
let Some(material_asset_id) = material_instances.get(&item.main_entity()) else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
let Some(material) = materials.get(*material_asset_id) else {
|
|
return RenderCommandResult::Skip;
|
|
};
|
|
pass.set_bind_group(I, &material.bind_group, &[]);
|
|
RenderCommandResult::Success
|
|
}
|
|
}
|
|
|
|
/// Stores all extracted instances of a [`Material`] in the render world.
|
|
#[derive(Resource, Deref, DerefMut)]
|
|
pub struct RenderMaterialInstances<M: Material>(pub MainEntityHashMap<AssetId<M>>);
|
|
|
|
impl<M: Material> Default for RenderMaterialInstances<M> {
|
|
fn default() -> Self {
|
|
Self(Default::default())
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn clear_material_instances<M: Material>(
|
|
mut material_instances: ResMut<RenderMaterialInstances<M>>,
|
|
) {
|
|
material_instances.clear();
|
|
}
|
|
|
|
fn extract_mesh_materials<M: Material>(
|
|
mut material_instances: ResMut<RenderMaterialInstances<M>>,
|
|
query: Extract<Query<(Entity, &ViewVisibility, &MeshMaterial3d<M>)>>,
|
|
) {
|
|
for (entity, view_visibility, material) in &query {
|
|
if view_visibility.get() {
|
|
material_instances.insert(entity.into(), material.id());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Extracts default materials for 3D meshes with no [`MeshMaterial3d`].
|
|
pub(super) fn extract_default_materials(
|
|
mut material_instances: ResMut<RenderMaterialInstances<StandardMaterial>>,
|
|
query: Extract<Query<(Entity, &ViewVisibility), (With<Mesh3d>, Without<HasMaterial3d>)>>,
|
|
) {
|
|
for (entity, view_visibility) in &query {
|
|
if view_visibility.get() {
|
|
material_instances.insert(entity.into(), AssetId::default());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// For each view, iterates over all the meshes visible from that view and adds
|
|
/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn queue_material_meshes<M: Material>(
|
|
(
|
|
opaque_draw_functions,
|
|
alpha_mask_draw_functions,
|
|
transmissive_draw_functions,
|
|
transparent_draw_functions,
|
|
): (
|
|
Res<DrawFunctions<Opaque3d>>,
|
|
Res<DrawFunctions<AlphaMask3d>>,
|
|
Res<DrawFunctions<Transmissive3d>>,
|
|
Res<DrawFunctions<Transparent3d>>,
|
|
),
|
|
material_pipeline: Res<MaterialPipeline<M>>,
|
|
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
|
|
pipeline_cache: Res<PipelineCache>,
|
|
render_meshes: Res<RenderAssets<RenderMesh>>,
|
|
render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
|
|
render_mesh_instances: Res<RenderMeshInstances>,
|
|
render_material_instances: Res<RenderMaterialInstances<M>>,
|
|
render_lightmaps: Res<RenderLightmaps>,
|
|
render_visibility_ranges: Res<RenderVisibilityRanges>,
|
|
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<(
|
|
Entity,
|
|
&ExtractedView,
|
|
&RenderVisibleEntities,
|
|
&Msaa,
|
|
Option<&Tonemapping>,
|
|
Option<&DebandDither>,
|
|
Option<&ShadowFilteringMethod>,
|
|
Has<ScreenSpaceAmbientOcclusion>,
|
|
(
|
|
Has<NormalPrepass>,
|
|
Has<DepthPrepass>,
|
|
Has<MotionVectorPrepass>,
|
|
Has<DeferredPrepass>,
|
|
),
|
|
Option<&Camera3d>,
|
|
Has<TemporalJitter>,
|
|
Option<&Projection>,
|
|
(
|
|
Has<RenderViewLightProbes<EnvironmentMapLight>>,
|
|
Has<RenderViewLightProbes<IrradianceVolume>>,
|
|
),
|
|
Has<OrderIndependentTransparencySettings>,
|
|
)>,
|
|
) where
|
|
M::Data: PartialEq + Eq + Hash + Clone,
|
|
{
|
|
for (
|
|
view_entity,
|
|
view,
|
|
visible_entities,
|
|
msaa,
|
|
tonemapping,
|
|
dither,
|
|
shadow_filter_method,
|
|
ssao,
|
|
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
|
|
camera_3d,
|
|
temporal_jitter,
|
|
projection,
|
|
(has_environment_maps, has_irradiance_volumes),
|
|
has_oit,
|
|
) in &views
|
|
{
|
|
let (
|
|
Some(opaque_phase),
|
|
Some(alpha_mask_phase),
|
|
Some(transmissive_phase),
|
|
Some(transparent_phase),
|
|
) = (
|
|
opaque_render_phases.get_mut(&view_entity),
|
|
alpha_mask_render_phases.get_mut(&view_entity),
|
|
transmissive_render_phases.get_mut(&view_entity),
|
|
transparent_render_phases.get_mut(&view_entity),
|
|
)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
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 mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
|
|
| MeshPipelineKey::from_hdr(view.hdr);
|
|
|
|
if normal_prepass {
|
|
view_key |= MeshPipelineKey::NORMAL_PREPASS;
|
|
}
|
|
|
|
if depth_prepass {
|
|
view_key |= MeshPipelineKey::DEPTH_PREPASS;
|
|
}
|
|
|
|
if motion_vector_prepass {
|
|
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
|
|
}
|
|
|
|
if deferred_prepass {
|
|
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
|
|
}
|
|
|
|
if temporal_jitter {
|
|
view_key |= MeshPipelineKey::TEMPORAL_JITTER;
|
|
}
|
|
|
|
if has_environment_maps {
|
|
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
|
|
}
|
|
|
|
if has_irradiance_volumes {
|
|
view_key |= MeshPipelineKey::IRRADIANCE_VOLUME;
|
|
}
|
|
|
|
if has_oit {
|
|
view_key |= MeshPipelineKey::OIT_ENABLED;
|
|
}
|
|
|
|
if let Some(projection) = projection {
|
|
view_key |= match projection {
|
|
Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
|
|
Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC,
|
|
};
|
|
}
|
|
|
|
match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
|
|
ShadowFilteringMethod::Hardware2x2 => {
|
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
|
|
}
|
|
ShadowFilteringMethod::Gaussian => {
|
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
|
|
}
|
|
ShadowFilteringMethod::Temporal => {
|
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
|
|
}
|
|
}
|
|
|
|
if !view.hdr {
|
|
if let Some(tonemapping) = tonemapping {
|
|
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
|
|
view_key |= tonemapping_pipeline_key(*tonemapping);
|
|
}
|
|
if let Some(DebandDither::Enabled) = dither {
|
|
view_key |= MeshPipelineKey::DEBAND_DITHER;
|
|
}
|
|
}
|
|
if ssao {
|
|
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
|
|
}
|
|
if let Some(camera_3d) = camera_3d {
|
|
view_key |= screen_space_specular_transmission_pipeline_key(
|
|
camera_3d.screen_space_specular_transmission_quality,
|
|
);
|
|
}
|
|
|
|
let rangefinder = view.rangefinder3d();
|
|
for (render_entity, visible_entity) in visible_entities.iter::<With<Mesh3d>>() {
|
|
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
|
|
continue;
|
|
};
|
|
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
|
|
else {
|
|
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 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,
|
|
));
|
|
let mut mesh_key = view_key
|
|
| MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
|
|
| mesh_pipeline_key_bits;
|
|
|
|
let lightmap_image = render_lightmaps
|
|
.render_lightmaps
|
|
.get(visible_entity)
|
|
.map(|lightmap| lightmap.image);
|
|
if lightmap_image.is_some() {
|
|
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
|
}
|
|
|
|
if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
|
|
mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
|
|
}
|
|
|
|
if 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 pipeline_id = pipelines.specialize(
|
|
&pipeline_cache,
|
|
&material_pipeline,
|
|
MaterialPipelineKey {
|
|
mesh_key,
|
|
bind_group_data: material.key.clone(),
|
|
},
|
|
&mesh.layout,
|
|
);
|
|
let pipeline_id = match pipeline_id {
|
|
Ok(id) => id,
|
|
Err(err) => {
|
|
error!("{}", err);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
mesh_instance
|
|
.material_bind_group_id
|
|
.set(material.get_bind_group_id());
|
|
|
|
match mesh_key
|
|
.intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD)
|
|
{
|
|
MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => {
|
|
if material.properties.reads_view_transmission_texture {
|
|
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
|
+ material.properties.depth_bias;
|
|
transmissive_phase.add(Transmissive3d {
|
|
entity: (*render_entity, *visible_entity),
|
|
draw_function: draw_transmissive_pbr,
|
|
pipeline: pipeline_id,
|
|
distance,
|
|
batch_range: 0..1,
|
|
extra_index: PhaseItemExtraIndex::NONE,
|
|
});
|
|
} else if material.properties.render_method == OpaqueRendererMethod::Forward {
|
|
let bin_key = Opaque3dBinKey {
|
|
draw_function: draw_opaque_pbr,
|
|
pipeline: pipeline_id,
|
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
|
material_bind_group_id: material.get_bind_group_id().0,
|
|
lightmap_image,
|
|
};
|
|
opaque_phase.add(
|
|
bin_key,
|
|
(*render_entity, *visible_entity),
|
|
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
|
|
);
|
|
}
|
|
}
|
|
// Alpha mask
|
|
MeshPipelineKey::MAY_DISCARD => {
|
|
if material.properties.reads_view_transmission_texture {
|
|
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
|
+ material.properties.depth_bias;
|
|
transmissive_phase.add(Transmissive3d {
|
|
entity: (*render_entity, *visible_entity),
|
|
draw_function: draw_transmissive_pbr,
|
|
pipeline: pipeline_id,
|
|
distance,
|
|
batch_range: 0..1,
|
|
extra_index: PhaseItemExtraIndex::NONE,
|
|
});
|
|
} else if material.properties.render_method == OpaqueRendererMethod::Forward {
|
|
let bin_key = OpaqueNoLightmap3dBinKey {
|
|
draw_function: draw_alpha_mask_pbr,
|
|
pipeline: pipeline_id,
|
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
|
material_bind_group_id: material.get_bind_group_id().0,
|
|
};
|
|
alpha_mask_phase.add(
|
|
bin_key,
|
|
(*render_entity, *visible_entity),
|
|
BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
|
|
);
|
|
}
|
|
}
|
|
_ => {
|
|
let distance = rangefinder.distance_translation(&mesh_instance.translation)
|
|
+ material.properties.depth_bias;
|
|
transparent_phase.add(Transparent3d {
|
|
entity: (*render_entity, *visible_entity),
|
|
draw_function: draw_transparent_pbr,
|
|
pipeline: pipeline_id,
|
|
distance,
|
|
batch_range: 0..1,
|
|
extra_index: PhaseItemExtraIndex::NONE,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Default render method used for opaque materials.
|
|
#[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)]
|
|
#[reflect(Resource, Default, Debug)]
|
|
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)]
|
|
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,
|
|
}
|
|
|
|
/// Data prepared for a [`Material`] instance.
|
|
pub struct PreparedMaterial<T: Material> {
|
|
pub bindings: Vec<(u32, OwnedBindingResource)>,
|
|
pub bind_group: BindGroup,
|
|
pub key: T::Data,
|
|
pub properties: MaterialProperties,
|
|
}
|
|
|
|
impl<M: Material> RenderAsset for PreparedMaterial<M> {
|
|
type SourceAsset = M;
|
|
|
|
type Param = (
|
|
SRes<RenderDevice>,
|
|
SRes<MaterialPipeline<M>>,
|
|
SRes<DefaultOpaqueRendererMethod>,
|
|
M::Param,
|
|
);
|
|
|
|
fn prepare_asset(
|
|
material: Self::SourceAsset,
|
|
(render_device, pipeline, default_opaque_render_method, ref mut material_param): &mut SystemParamItem<Self::Param>,
|
|
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
|
match material.as_bind_group(&pipeline.material_layout, render_device, material_param) {
|
|
Ok(prepared) => {
|
|
let 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(),
|
|
);
|
|
|
|
Ok(PreparedMaterial {
|
|
bindings: prepared.bindings,
|
|
bind_group: prepared.bind_group,
|
|
key: prepared.data,
|
|
properties: MaterialProperties {
|
|
alpha_mode: material.alpha_mode(),
|
|
depth_bias: material.depth_bias(),
|
|
reads_view_transmission_texture: mesh_pipeline_key_bits
|
|
.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE),
|
|
render_method: method,
|
|
mesh_pipeline_key_bits,
|
|
},
|
|
})
|
|
}
|
|
Err(AsBindGroupError::RetryNextUpdate) => {
|
|
Err(PrepareAssetError::RetryNextUpdate(material))
|
|
}
|
|
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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())
|
|
}
|
|
}
|
|
|
|
/// An atomic version of [`MaterialBindGroupId`] that can be read from and written to
|
|
/// safely from multiple threads.
|
|
#[derive(Default)]
|
|
pub struct AtomicMaterialBindGroupId(AtomicU32);
|
|
|
|
impl AtomicMaterialBindGroupId {
|
|
/// Stores a value atomically. Uses [`Ordering::Relaxed`] so there is zero guarantee of ordering
|
|
/// relative to other operations.
|
|
///
|
|
/// See also: [`AtomicU32::store`].
|
|
pub fn set(&self, id: MaterialBindGroupId) {
|
|
let id = if let Some(id) = id.0 {
|
|
NonZero::<u32>::from(id).get()
|
|
} else {
|
|
0
|
|
};
|
|
self.0.store(id, Ordering::Relaxed);
|
|
}
|
|
|
|
/// Loads a value atomically. Uses [`Ordering::Relaxed`] so there is zero guarantee of ordering
|
|
/// relative to other operations.
|
|
///
|
|
/// See also: [`AtomicU32::load`].
|
|
pub fn get(&self) -> MaterialBindGroupId {
|
|
MaterialBindGroupId(
|
|
NonZero::<u32>::new(self.0.load(Ordering::Relaxed)).map(BindGroupId::from),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<T: Material> PreparedMaterial<T> {
|
|
pub fn get_bind_group_id(&self) -> MaterialBindGroupId {
|
|
MaterialBindGroupId(Some(self.bind_group.id()))
|
|
}
|
|
}
|