Path: blob/main/crates/bevy_pbr/src/extended_material.rs
9460 views
use alloc::borrow::Cow;12use bevy_asset::Asset;3use bevy_ecs::system::SystemParamItem;4use bevy_material::{AlphaMode, OpaqueRendererMethod};5use bevy_mesh::MeshVertexBufferLayoutRef;6use bevy_platform::{collections::HashSet, hash::FixedHasher};7use bevy_reflect::{impl_type_path, Reflect};8use bevy_render::{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/// Controls if the prepass is enabled for the Material.51/// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs.52#[inline]53fn enable_prepass() -> bool {54true55}5657/// Controls if shadows are enabled for the Material.58#[inline]59fn enable_shadows() -> bool {60true61}6263/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the base material prepass vertex shader64/// will be used.65fn prepass_vertex_shader() -> ShaderRef {66ShaderRef::Default67}6869/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material prepass fragment shader70/// will be used.71fn prepass_fragment_shader() -> ShaderRef {72ShaderRef::Default73}7475/// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the base material deferred vertex shader76/// will be used.77fn deferred_vertex_shader() -> ShaderRef {78ShaderRef::Default79}8081/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material deferred fragment shader82/// will be used.83fn deferred_fragment_shader() -> ShaderRef {84ShaderRef::Default85}8687/// Returns this material's [`crate::meshlet::MeshletMesh`] fragment shader. If [`ShaderRef::Default`] is returned,88/// the default meshlet mesh fragment shader will be used.89#[cfg(feature = "meshlet")]90fn meshlet_mesh_fragment_shader() -> ShaderRef {91ShaderRef::Default92}9394/// Returns this material's [`crate::meshlet::MeshletMesh`] prepass fragment shader. If [`ShaderRef::Default`] is returned,95/// the default meshlet mesh prepass fragment shader will be used.96#[cfg(feature = "meshlet")]97fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {98ShaderRef::Default99}100101/// Returns this material's [`crate::meshlet::MeshletMesh`] deferred fragment shader. If [`ShaderRef::Default`] is returned,102/// the default meshlet mesh deferred fragment shader will be used.103#[cfg(feature = "meshlet")]104fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {105ShaderRef::Default106}107108/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's109/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input.110/// Specialization for the base material is applied before this function is called.111#[expect(112unused_variables,113reason = "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."114)]115#[inline]116fn specialize(117pipeline: &MaterialExtensionPipeline,118descriptor: &mut RenderPipelineDescriptor,119layout: &MeshVertexBufferLayoutRef,120key: MaterialExtensionKey<Self>,121) -> Result<(), SpecializedMeshPipelineError> {122Ok(())123}124}125126/// A material that extends a base [`Material`] with additional shaders and data.127///128/// The data from both materials will be combined and made available to the shader129/// so that shader functions built for the base material (and referencing the base material130/// bindings) will work as expected, and custom alterations based on custom data can also be used.131///132/// If the extension `E` returns a non-default result from `vertex_shader()` it will be used in place of the base133/// material's vertex shader.134///135/// If the extension `E` returns a non-default result from `fragment_shader()` it will be used in place of the base136/// fragment shader.137///138/// When used with `StandardMaterial` as the base, all the standard material fields are139/// present, so the `pbr_fragment` shader functions can be called from the extension shader (see140/// the `extended_material` example).141#[derive(Asset, Clone, Debug, Reflect)]142#[reflect(type_path = false)]143#[reflect(Clone)]144pub struct ExtendedMaterial<B: Material, E: MaterialExtension> {145pub base: B,146pub extension: E,147}148149impl<B, E> Default for ExtendedMaterial<B, E>150where151B: Material + Default,152E: MaterialExtension + Default,153{154fn default() -> Self {155Self {156base: B::default(),157extension: E::default(),158}159}160}161162#[derive(Copy, Clone, PartialEq, Eq, Hash)]163#[repr(C, packed)]164pub struct MaterialExtensionBindGroupData<B, E> {165pub base: B,166pub extension: E,167}168169// We don't use the `TypePath` derive here due to a bug where `#[reflect(type_path = false)]`170// causes the `TypePath` derive to not generate an implementation.171impl_type_path!((in bevy_pbr::extended_material) ExtendedMaterial<B: Material, E: MaterialExtension>);172173impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {174type Data = MaterialExtensionBindGroupData<B::Data, E::Data>;175type Param = (<B as AsBindGroup>::Param, <E as AsBindGroup>::Param);176177fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {178// We only enable bindless if both the base material and its extension179// are bindless. If we do enable bindless, we choose the smaller of the180// two slab size limits.181match (B::bindless_slot_count()?, E::bindless_slot_count()?) {182(BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Auto) => {183Some(BindlessSlabResourceLimit::Auto)184}185(BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Custom(limit))186| (BindlessSlabResourceLimit::Custom(limit), BindlessSlabResourceLimit::Auto) => {187Some(BindlessSlabResourceLimit::Custom(limit))188}189(190BindlessSlabResourceLimit::Custom(base_limit),191BindlessSlabResourceLimit::Custom(extended_limit),192) => Some(BindlessSlabResourceLimit::Custom(193base_limit.min(extended_limit),194)),195}196}197198fn bindless_supported(render_device: &RenderDevice) -> bool {199B::bindless_supported(render_device) && E::bindless_supported(render_device)200}201202fn label() -> &'static str {203E::label()204}205206fn bind_group_data(&self) -> Self::Data {207MaterialExtensionBindGroupData {208base: self.base.bind_group_data(),209extension: self.extension.bind_group_data(),210}211}212213fn unprepared_bind_group(214&self,215layout: &BindGroupLayout,216render_device: &RenderDevice,217(base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>,218mut force_non_bindless: bool,219) -> Result<UnpreparedBindGroup, AsBindGroupError> {220force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none();221222// add together the bindings of the base material and the extension223let UnpreparedBindGroup { mut bindings } = B::unprepared_bind_group(224&self.base,225layout,226render_device,227base_param,228force_non_bindless,229)?;230let UnpreparedBindGroup {231bindings: extension_bindings,232} = E::unprepared_bind_group(233&self.extension,234layout,235render_device,236extended_param,237force_non_bindless,238)?;239240bindings.extend(extension_bindings.0);241242Ok(UnpreparedBindGroup { bindings })243}244245fn bind_group_layout_entries(246render_device: &RenderDevice,247mut force_non_bindless: bool,248) -> Vec<BindGroupLayoutEntry>249where250Self: Sized,251{252force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none();253254// Add together the bindings of the standard material and the user255// material, skipping duplicate bindings. Duplicate bindings will occur256// when bindless mode is on, because of the common bindless resource257// arrays, and we need to eliminate the duplicates or `wgpu` will258// complain.259let base_entries = B::bind_group_layout_entries(render_device, force_non_bindless);260let extension_entries = E::bind_group_layout_entries(render_device, force_non_bindless);261262let mut seen_bindings = HashSet::<u32>::with_hasher(FixedHasher);263264base_entries265.into_iter()266.chain(extension_entries)267.filter(|entry| seen_bindings.insert(entry.binding))268.collect()269}270271fn bindless_descriptor() -> Option<BindlessDescriptor> {272// We're going to combine the two bindless descriptors.273let base_bindless_descriptor = B::bindless_descriptor()?;274let extended_bindless_descriptor = E::bindless_descriptor()?;275276// Combining the buffers and index tables is straightforward.277278let mut buffers = base_bindless_descriptor.buffers.to_vec();279let mut index_tables = base_bindless_descriptor.index_tables.to_vec();280281buffers.extend(extended_bindless_descriptor.buffers.iter().cloned());282index_tables.extend(extended_bindless_descriptor.index_tables.iter().cloned());283284// Combining the resources is a little trickier because the resource285// array is indexed by bindless index, so we have to merge the two286// arrays, not just concatenate them.287let max_bindless_index = base_bindless_descriptor288.resources289.len()290.max(extended_bindless_descriptor.resources.len());291let mut resources = Vec::with_capacity(max_bindless_index);292for bindless_index in 0..max_bindless_index {293// In the event of a conflicting bindless index, we choose the294// base's binding.295match base_bindless_descriptor.resources.get(bindless_index) {296None | Some(&BindlessResourceType::None) => resources.push(297extended_bindless_descriptor298.resources299.get(bindless_index)300.copied()301.unwrap_or(BindlessResourceType::None),302),303Some(&resource_type) => resources.push(resource_type),304}305}306307Some(BindlessDescriptor {308resources: Cow::Owned(resources),309buffers: Cow::Owned(buffers),310index_tables: Cow::Owned(index_tables),311})312}313}314315impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {316fn vertex_shader() -> ShaderRef {317match E::vertex_shader() {318ShaderRef::Default => B::vertex_shader(),319specified => specified,320}321}322323fn fragment_shader() -> ShaderRef {324match E::fragment_shader() {325ShaderRef::Default => B::fragment_shader(),326specified => specified,327}328}329330fn alpha_mode(&self) -> AlphaMode {331match E::alpha_mode() {332Some(specified) => specified,333None => B::alpha_mode(&self.base),334}335}336337fn opaque_render_method(&self) -> OpaqueRendererMethod {338B::opaque_render_method(&self.base)339}340341fn depth_bias(&self) -> f32 {342B::depth_bias(&self.base)343}344345fn reads_view_transmission_texture(&self) -> bool {346B::reads_view_transmission_texture(&self.base)347}348349fn enable_prepass() -> bool {350E::enable_prepass()351}352353fn enable_shadows() -> bool {354E::enable_shadows()355}356357fn prepass_vertex_shader() -> ShaderRef {358match E::prepass_vertex_shader() {359ShaderRef::Default => B::prepass_vertex_shader(),360specified => specified,361}362}363364fn prepass_fragment_shader() -> ShaderRef {365match E::prepass_fragment_shader() {366ShaderRef::Default => B::prepass_fragment_shader(),367specified => specified,368}369}370371fn deferred_vertex_shader() -> ShaderRef {372match E::deferred_vertex_shader() {373ShaderRef::Default => B::deferred_vertex_shader(),374specified => specified,375}376}377378fn deferred_fragment_shader() -> ShaderRef {379match E::deferred_fragment_shader() {380ShaderRef::Default => B::deferred_fragment_shader(),381specified => specified,382}383}384385#[cfg(feature = "meshlet")]386fn meshlet_mesh_fragment_shader() -> ShaderRef {387match E::meshlet_mesh_fragment_shader() {388ShaderRef::Default => B::meshlet_mesh_fragment_shader(),389specified => specified,390}391}392393#[cfg(feature = "meshlet")]394fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {395match E::meshlet_mesh_prepass_fragment_shader() {396ShaderRef::Default => B::meshlet_mesh_prepass_fragment_shader(),397specified => specified,398}399}400401#[cfg(feature = "meshlet")]402fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {403match E::meshlet_mesh_deferred_fragment_shader() {404ShaderRef::Default => B::meshlet_mesh_deferred_fragment_shader(),405specified => specified,406}407}408409fn specialize(410pipeline: &MaterialPipeline,411descriptor: &mut RenderPipelineDescriptor,412layout: &MeshVertexBufferLayoutRef,413key: MaterialPipelineKey<Self>,414) -> Result<(), SpecializedMeshPipelineError> {415// Call the base material's specialize function416let base_key = MaterialPipelineKey::<B> {417mesh_key: key.mesh_key,418bind_group_data: key.bind_group_data.base,419};420B::specialize(pipeline, descriptor, layout, base_key)?;421422// Call the extended material's specialize function afterwards423E::specialize(424&MaterialExtensionPipeline {425mesh_pipeline: pipeline.mesh_pipeline.clone(),426},427descriptor,428layout,429MaterialExtensionKey {430mesh_key: key.mesh_key,431bind_group_data: key.bind_group_data.extension,432},433)434}435}436437438