Path: blob/main/crates/bevy_pbr/src/extended_material.rs
6598 views
use alloc::borrow::Cow;12use bevy_asset::Asset;3use bevy_ecs::system::SystemParamItem;4use bevy_mesh::MeshVertexBufferLayoutRef;5use bevy_platform::{collections::HashSet, hash::FixedHasher};6use bevy_reflect::{impl_type_path, Reflect};7use bevy_render::{8alpha::AlphaMode,9render_resource::{10AsBindGroup, AsBindGroupError, BindGroupLayout, BindGroupLayoutEntry, BindlessDescriptor,11BindlessResourceType, BindlessSlabResourceLimit, RenderPipelineDescriptor,12SpecializedMeshPipelineError, UnpreparedBindGroup,13},14renderer::RenderDevice,15};16use bevy_shader::ShaderRef;1718use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshPipelineKey};1920pub struct MaterialExtensionPipeline {21pub mesh_pipeline: MeshPipeline,22}2324pub struct MaterialExtensionKey<E: MaterialExtension> {25pub mesh_key: MeshPipelineKey,26pub bind_group_data: E::Data,27}2829/// A subset of the `Material` trait for defining extensions to a base `Material`, such as the builtin `StandardMaterial`.30///31/// A user type implementing the trait should be used as the `E` generic param in an `ExtendedMaterial` struct.32pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized {33/// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the base material mesh vertex shader34/// will be used.35fn vertex_shader() -> ShaderRef {36ShaderRef::Default37}3839/// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the base material mesh fragment shader40/// will be used.41fn fragment_shader() -> ShaderRef {42ShaderRef::Default43}4445// Returns this material’s AlphaMode. If None is returned, the base material alpha mode will be used.46fn alpha_mode() -> Option<AlphaMode> {47None48}4950/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the base material prepass vertex shader51/// will be used.52fn prepass_vertex_shader() -> ShaderRef {53ShaderRef::Default54}5556/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material prepass fragment shader57/// will be used.58fn prepass_fragment_shader() -> ShaderRef {59ShaderRef::Default60}6162/// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the base material deferred vertex shader63/// will be used.64fn deferred_vertex_shader() -> ShaderRef {65ShaderRef::Default66}6768/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material deferred fragment shader69/// will be used.70fn deferred_fragment_shader() -> ShaderRef {71ShaderRef::Default72}7374/// Returns this material's [`crate::meshlet::MeshletMesh`] fragment shader. If [`ShaderRef::Default`] is returned,75/// the default meshlet mesh fragment shader will be used.76#[cfg(feature = "meshlet")]77fn meshlet_mesh_fragment_shader() -> ShaderRef {78ShaderRef::Default79}8081/// Returns this material's [`crate::meshlet::MeshletMesh`] prepass fragment shader. If [`ShaderRef::Default`] is returned,82/// the default meshlet mesh prepass fragment shader will be used.83#[cfg(feature = "meshlet")]84fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {85ShaderRef::Default86}8788/// Returns this material's [`crate::meshlet::MeshletMesh`] deferred fragment shader. If [`ShaderRef::Default`] is returned,89/// the default meshlet mesh deferred fragment shader will be used.90#[cfg(feature = "meshlet")]91fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {92ShaderRef::Default93}9495/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's96/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input.97/// Specialization for the base material is applied before this function is called.98#[expect(99unused_variables,100reason = "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."101)]102#[inline]103fn specialize(104pipeline: &MaterialExtensionPipeline,105descriptor: &mut RenderPipelineDescriptor,106layout: &MeshVertexBufferLayoutRef,107key: MaterialExtensionKey<Self>,108) -> Result<(), SpecializedMeshPipelineError> {109Ok(())110}111}112113/// A material that extends a base [`Material`] with additional shaders and data.114///115/// The data from both materials will be combined and made available to the shader116/// so that shader functions built for the base material (and referencing the base material117/// bindings) will work as expected, and custom alterations based on custom data can also be used.118///119/// If the extension `E` returns a non-default result from `vertex_shader()` it will be used in place of the base120/// material's vertex shader.121///122/// If the extension `E` returns a non-default result from `fragment_shader()` it will be used in place of the base123/// fragment shader.124///125/// When used with `StandardMaterial` as the base, all the standard material fields are126/// present, so the `pbr_fragment` shader functions can be called from the extension shader (see127/// the `extended_material` example).128#[derive(Asset, Clone, Debug, Reflect)]129#[reflect(type_path = false)]130#[reflect(Clone)]131pub struct ExtendedMaterial<B: Material, E: MaterialExtension> {132pub base: B,133pub extension: E,134}135136impl<B, E> Default for ExtendedMaterial<B, E>137where138B: Material + Default,139E: MaterialExtension + Default,140{141fn default() -> Self {142Self {143base: B::default(),144extension: E::default(),145}146}147}148149#[derive(Copy, Clone, PartialEq, Eq, Hash)]150#[repr(C, packed)]151pub struct MaterialExtensionBindGroupData<B, E> {152pub base: B,153pub extension: E,154}155156// We don't use the `TypePath` derive here due to a bug where `#[reflect(type_path = false)]`157// causes the `TypePath` derive to not generate an implementation.158impl_type_path!((in bevy_pbr::extended_material) ExtendedMaterial<B: Material, E: MaterialExtension>);159160impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {161type Data = MaterialExtensionBindGroupData<B::Data, E::Data>;162type Param = (<B as AsBindGroup>::Param, <E as AsBindGroup>::Param);163164fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {165// We only enable bindless if both the base material and its extension166// are bindless. If we do enable bindless, we choose the smaller of the167// two slab size limits.168match (B::bindless_slot_count()?, E::bindless_slot_count()?) {169(BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Auto) => {170Some(BindlessSlabResourceLimit::Auto)171}172(BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Custom(limit))173| (BindlessSlabResourceLimit::Custom(limit), BindlessSlabResourceLimit::Auto) => {174Some(BindlessSlabResourceLimit::Custom(limit))175}176(177BindlessSlabResourceLimit::Custom(base_limit),178BindlessSlabResourceLimit::Custom(extended_limit),179) => Some(BindlessSlabResourceLimit::Custom(180base_limit.min(extended_limit),181)),182}183}184185fn bind_group_data(&self) -> Self::Data {186MaterialExtensionBindGroupData {187base: self.base.bind_group_data(),188extension: self.extension.bind_group_data(),189}190}191192fn unprepared_bind_group(193&self,194layout: &BindGroupLayout,195render_device: &RenderDevice,196(base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>,197mut force_non_bindless: bool,198) -> Result<UnpreparedBindGroup, AsBindGroupError> {199force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none();200201// add together the bindings of the base material and the user material202let UnpreparedBindGroup { mut bindings } = B::unprepared_bind_group(203&self.base,204layout,205render_device,206base_param,207force_non_bindless,208)?;209let extended_bindgroup = E::unprepared_bind_group(210&self.extension,211layout,212render_device,213extended_param,214force_non_bindless,215)?;216217bindings.extend(extended_bindgroup.bindings.0);218219Ok(UnpreparedBindGroup { bindings })220}221222fn bind_group_layout_entries(223render_device: &RenderDevice,224mut force_non_bindless: bool,225) -> Vec<BindGroupLayoutEntry>226where227Self: Sized,228{229force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none();230231// Add together the bindings of the standard material and the user232// material, skipping duplicate bindings. Duplicate bindings will occur233// when bindless mode is on, because of the common bindless resource234// arrays, and we need to eliminate the duplicates or `wgpu` will235// complain.236let mut entries = vec![];237let mut seen_bindings = HashSet::<_>::with_hasher(FixedHasher);238for entry in B::bind_group_layout_entries(render_device, force_non_bindless)239.into_iter()240.chain(E::bind_group_layout_entries(render_device, force_non_bindless).into_iter())241{242if seen_bindings.insert(entry.binding) {243entries.push(entry);244}245}246entries247}248249fn bindless_descriptor() -> Option<BindlessDescriptor> {250// We're going to combine the two bindless descriptors.251let base_bindless_descriptor = B::bindless_descriptor()?;252let extended_bindless_descriptor = E::bindless_descriptor()?;253254// Combining the buffers and index tables is straightforward.255256let mut buffers = base_bindless_descriptor.buffers.to_vec();257let mut index_tables = base_bindless_descriptor.index_tables.to_vec();258259buffers.extend(extended_bindless_descriptor.buffers.iter().cloned());260index_tables.extend(extended_bindless_descriptor.index_tables.iter().cloned());261262// Combining the resources is a little trickier because the resource263// array is indexed by bindless index, so we have to merge the two264// arrays, not just concatenate them.265let max_bindless_index = base_bindless_descriptor266.resources267.len()268.max(extended_bindless_descriptor.resources.len());269let mut resources = Vec::with_capacity(max_bindless_index);270for bindless_index in 0..max_bindless_index {271// In the event of a conflicting bindless index, we choose the272// base's binding.273match base_bindless_descriptor.resources.get(bindless_index) {274None | Some(&BindlessResourceType::None) => resources.push(275extended_bindless_descriptor276.resources277.get(bindless_index)278.copied()279.unwrap_or(BindlessResourceType::None),280),281Some(&resource_type) => resources.push(resource_type),282}283}284285Some(BindlessDescriptor {286resources: Cow::Owned(resources),287buffers: Cow::Owned(buffers),288index_tables: Cow::Owned(index_tables),289})290}291}292293impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {294fn vertex_shader() -> ShaderRef {295match E::vertex_shader() {296ShaderRef::Default => B::vertex_shader(),297specified => specified,298}299}300301fn fragment_shader() -> ShaderRef {302match E::fragment_shader() {303ShaderRef::Default => B::fragment_shader(),304specified => specified,305}306}307308fn alpha_mode(&self) -> AlphaMode {309match E::alpha_mode() {310Some(specified) => specified,311None => B::alpha_mode(&self.base),312}313}314315fn opaque_render_method(&self) -> crate::OpaqueRendererMethod {316B::opaque_render_method(&self.base)317}318319fn depth_bias(&self) -> f32 {320B::depth_bias(&self.base)321}322323fn reads_view_transmission_texture(&self) -> bool {324B::reads_view_transmission_texture(&self.base)325}326327fn prepass_vertex_shader() -> ShaderRef {328match E::prepass_vertex_shader() {329ShaderRef::Default => B::prepass_vertex_shader(),330specified => specified,331}332}333334fn prepass_fragment_shader() -> ShaderRef {335match E::prepass_fragment_shader() {336ShaderRef::Default => B::prepass_fragment_shader(),337specified => specified,338}339}340341fn deferred_vertex_shader() -> ShaderRef {342match E::deferred_vertex_shader() {343ShaderRef::Default => B::deferred_vertex_shader(),344specified => specified,345}346}347348fn deferred_fragment_shader() -> ShaderRef {349match E::deferred_fragment_shader() {350ShaderRef::Default => B::deferred_fragment_shader(),351specified => specified,352}353}354355#[cfg(feature = "meshlet")]356fn meshlet_mesh_fragment_shader() -> ShaderRef {357match E::meshlet_mesh_fragment_shader() {358ShaderRef::Default => B::meshlet_mesh_fragment_shader(),359specified => specified,360}361}362363#[cfg(feature = "meshlet")]364fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {365match E::meshlet_mesh_prepass_fragment_shader() {366ShaderRef::Default => B::meshlet_mesh_prepass_fragment_shader(),367specified => specified,368}369}370371#[cfg(feature = "meshlet")]372fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {373match E::meshlet_mesh_deferred_fragment_shader() {374ShaderRef::Default => B::meshlet_mesh_deferred_fragment_shader(),375specified => specified,376}377}378379fn specialize(380pipeline: &MaterialPipeline,381descriptor: &mut RenderPipelineDescriptor,382layout: &MeshVertexBufferLayoutRef,383key: MaterialPipelineKey<Self>,384) -> Result<(), SpecializedMeshPipelineError> {385// Call the base material's specialize function386let base_key = MaterialPipelineKey::<B> {387mesh_key: key.mesh_key,388bind_group_data: key.bind_group_data.base,389};390B::specialize(pipeline, descriptor, layout, base_key)?;391392// Call the extended material's specialize function afterwards393E::specialize(394&MaterialExtensionPipeline {395mesh_pipeline: pipeline.mesh_pipeline.clone(),396},397descriptor,398layout,399MaterialExtensionKey {400mesh_key: key.mesh_key,401bind_group_data: key.bind_group_data.extension,402},403)404}405}406407408