bevy/crates/bevy_pbr/src/extended_material.rs
Patrick Walton 7ed1f327d9
Make StandardMaterial bindless. (#16644)
This commit makes `StandardMaterial` use bindless textures, as
implemented in PR #16368. Non-bindless mode, as used for example in
Metal and WebGL 2, remains fully supported via a plethora of `#ifdef
BINDLESS` preprocessor definitions.

Unfortunately, this PR introduces quite a bit of unsightliness into the
PBR shaders. This is a result of the fact that WGSL supports neither
passing binding arrays to functions nor passing individual *elements* of
binding arrays to functions, except directly to texture sample
functions. Thus we're unable to use the `sample_texture` abstraction
that helped abstract over the meshlet and non-meshlet paths. I don't
think there's anything we can do to help this other than to suggest
improvements to upstream Naga.
2024-12-10 17:48:56 +00:00

354 lines
12 KiB
Rust

use bevy_asset::{Asset, Handle};
use bevy_ecs::system::SystemParamItem;
use bevy_reflect::{impl_type_path, Reflect};
use bevy_render::{
mesh::MeshVertexBufferLayoutRef,
render_resource::{
AsBindGroup, AsBindGroupError, BindGroupLayout, RenderPipelineDescriptor, Shader,
ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup,
},
renderer::RenderDevice,
};
use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshPipelineKey};
pub struct MaterialExtensionPipeline {
pub mesh_pipeline: MeshPipeline,
pub material_layout: BindGroupLayout,
pub vertex_shader: Option<Handle<Shader>>,
pub fragment_shader: Option<Handle<Shader>>,
pub bindless: bool,
}
pub struct MaterialExtensionKey<E: MaterialExtension> {
pub mesh_key: MeshPipelineKey,
pub bind_group_data: E::Data,
}
/// A subset of the `Material` trait for defining extensions to a base `Material`, such as the builtin `StandardMaterial`.
///
/// A user type implementing the trait should be used as the `E` generic param in an `ExtendedMaterial` struct.
pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized {
/// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the base material mesh vertex shader
/// will be used.
fn vertex_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the base material mesh fragment shader
/// will be used.
#[allow(unused_variables)]
fn fragment_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the base material prepass vertex shader
/// will be used.
fn prepass_vertex_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material prepass fragment shader
/// will be used.
#[allow(unused_variables)]
fn prepass_fragment_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the base material deferred vertex shader
/// will be used.
fn deferred_vertex_shader() -> ShaderRef {
ShaderRef::Default
}
/// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the base material 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.
#[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.
#[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.
#[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.
/// Specialization for the base material is applied before this function is called.
#[allow(unused_variables)]
#[inline]
fn specialize(
pipeline: &MaterialExtensionPipeline,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayoutRef,
key: MaterialExtensionKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
}
}
/// A material that extends a base [`Material`] with additional shaders and data.
///
/// The data from both materials will be combined and made available to the shader
/// so that shader functions built for the base material (and referencing the base material
/// bindings) will work as expected, and custom alterations based on custom data can also be used.
///
/// If the extension `E` returns a non-default result from `vertex_shader()` it will be used in place of the base
/// material's vertex shader.
///
/// If the extension `E` returns a non-default result from `fragment_shader()` it will be used in place of the base
/// fragment shader.
///
/// When used with `StandardMaterial` as the base, all the standard material fields are
/// present, so the `pbr_fragment` shader functions can be called from the extension shader (see
/// the `extended_material` example).
#[derive(Asset, Clone, Debug, Reflect)]
#[reflect(type_path = false)]
pub struct ExtendedMaterial<B: Material, E: MaterialExtension> {
pub base: B,
pub extension: E,
}
impl<B, E> Default for ExtendedMaterial<B, E>
where
B: Material + Default,
E: MaterialExtension + Default,
{
fn default() -> Self {
Self {
base: B::default(),
extension: E::default(),
}
}
}
// We don't use the `TypePath` derive here due to a bug where `#[reflect(type_path = false)]`
// causes the `TypePath` derive to not generate an implementation.
impl_type_path!((in bevy_pbr::extended_material) ExtendedMaterial<B: Material, E: MaterialExtension>);
impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
type Data = (<B as AsBindGroup>::Data, <E as AsBindGroup>::Data);
type Param = (<B as AsBindGroup>::Param, <E as AsBindGroup>::Param);
fn unprepared_bind_group(
&self,
layout: &BindGroupLayout,
render_device: &RenderDevice,
(base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>,
mut force_no_bindless: bool,
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
// Only allow bindless mode if both the base material and the extension
// support it.
force_no_bindless = force_no_bindless
|| B::BINDLESS_SLOT_COUNT.is_none()
|| E::BINDLESS_SLOT_COUNT.is_none();
// add together the bindings of the base material and the user material
let UnpreparedBindGroup {
mut bindings,
data: base_data,
} = B::unprepared_bind_group(
&self.base,
layout,
render_device,
base_param,
force_no_bindless,
)?;
let extended_bindgroup = E::unprepared_bind_group(
&self.extension,
layout,
render_device,
extended_param,
force_no_bindless,
)?;
bindings.extend(extended_bindgroup.bindings.0);
Ok(UnpreparedBindGroup {
bindings,
data: (base_data, extended_bindgroup.data),
})
}
fn bind_group_layout_entries(
render_device: &RenderDevice,
mut force_no_bindless: bool,
) -> Vec<bevy_render::render_resource::BindGroupLayoutEntry>
where
Self: Sized,
{
// Only allow bindless mode if both the base material and the extension
// support it.
force_no_bindless = force_no_bindless
|| B::BINDLESS_SLOT_COUNT.is_none()
|| E::BINDLESS_SLOT_COUNT.is_none();
// add together the bindings of the standard material and the user material
let mut entries = B::bind_group_layout_entries(render_device, force_no_bindless);
entries.extend(E::bind_group_layout_entries(
render_device,
force_no_bindless,
));
entries
}
}
impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {
fn vertex_shader() -> ShaderRef {
match E::vertex_shader() {
ShaderRef::Default => B::vertex_shader(),
specified => specified,
}
}
fn fragment_shader() -> ShaderRef {
match E::fragment_shader() {
ShaderRef::Default => B::fragment_shader(),
specified => specified,
}
}
fn alpha_mode(&self) -> crate::AlphaMode {
B::alpha_mode(&self.base)
}
fn opaque_render_method(&self) -> crate::OpaqueRendererMethod {
B::opaque_render_method(&self.base)
}
fn depth_bias(&self) -> f32 {
B::depth_bias(&self.base)
}
fn reads_view_transmission_texture(&self) -> bool {
B::reads_view_transmission_texture(&self.base)
}
fn prepass_vertex_shader() -> ShaderRef {
match E::prepass_vertex_shader() {
ShaderRef::Default => B::prepass_vertex_shader(),
specified => specified,
}
}
fn prepass_fragment_shader() -> ShaderRef {
match E::prepass_fragment_shader() {
ShaderRef::Default => B::prepass_fragment_shader(),
specified => specified,
}
}
fn deferred_vertex_shader() -> ShaderRef {
match E::deferred_vertex_shader() {
ShaderRef::Default => B::deferred_vertex_shader(),
specified => specified,
}
}
fn deferred_fragment_shader() -> ShaderRef {
match E::deferred_fragment_shader() {
ShaderRef::Default => B::deferred_fragment_shader(),
specified => specified,
}
}
#[cfg(feature = "meshlet")]
fn meshlet_mesh_fragment_shader() -> ShaderRef {
match E::meshlet_mesh_fragment_shader() {
ShaderRef::Default => B::meshlet_mesh_fragment_shader(),
specified => specified,
}
}
#[cfg(feature = "meshlet")]
fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef {
match E::meshlet_mesh_prepass_fragment_shader() {
ShaderRef::Default => B::meshlet_mesh_prepass_fragment_shader(),
specified => specified,
}
}
#[cfg(feature = "meshlet")]
fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef {
match E::meshlet_mesh_deferred_fragment_shader() {
ShaderRef::Default => B::meshlet_mesh_deferred_fragment_shader(),
specified => specified,
}
}
fn specialize(
pipeline: &MaterialPipeline<Self>,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayoutRef,
key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
// Call the base material's specialize function
let MaterialPipeline::<Self> {
mesh_pipeline,
material_layout,
vertex_shader,
fragment_shader,
bindless,
..
} = pipeline.clone();
let base_pipeline = MaterialPipeline::<B> {
mesh_pipeline,
material_layout,
vertex_shader,
fragment_shader,
bindless,
marker: Default::default(),
};
let base_key = MaterialPipelineKey::<B> {
mesh_key: key.mesh_key,
bind_group_data: key.bind_group_data.0,
};
B::specialize(&base_pipeline, descriptor, layout, base_key)?;
// Call the extended material's specialize function afterwards
let MaterialPipeline::<Self> {
mesh_pipeline,
material_layout,
vertex_shader,
fragment_shader,
bindless,
..
} = pipeline.clone();
E::specialize(
&MaterialExtensionPipeline {
mesh_pipeline,
material_layout,
vertex_shader,
fragment_shader,
bindless,
},
descriptor,
layout,
MaterialExtensionKey {
mesh_key: key.mesh_key,
bind_group_data: key.bind_group_data.1,
},
)
}
}