
# Objective Now that #13432 has been merged, it's important we update our reflected types to properly opt into this feature. If we do not, then this could cause issues for users downstream who want to make use of reflection-based cloning. ## Solution This PR is broken into 4 commits: 1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that are also `Clone`. This is mandatory as these types would otherwise cause the cloning operation to fail for any type that contains it at any depth. 2. Update the reflection example to suggest adding `#[reflect(Clone)]` on opaque types. 3. Add `#[reflect(clone)]` attributes on all fields marked `#[reflect(ignore)]` that are also `Clone`. This prevents the ignored field from causing the cloning operation to fail. Note that some of the types that contain these fields are also `Clone`, and thus can be marked `#[reflect(Clone)]`. This makes the `#[reflect(clone)]` attribute redundant. However, I think it's safer to keep it marked in the case that the `Clone` impl/derive is ever removed. I'm open to removing them, though, if people disagree. 4. Finally, I added `#[reflect(Clone)]` on all types that are also `Clone`. While not strictly necessary, it enables us to reduce the generated output since we can just call `Clone::clone` directly instead of calling `PartialReflect::reflect_clone` on each variant/field. It also means we benefit from any optimizations or customizations made in the `Clone` impl, including directly dereferencing `Copy` values and increasing reference counters. Along with that change I also took the liberty of adding any missing registrations that I saw could be applied to the type as well, such as `Default`, `PartialEq`, and `Hash`. There were hundreds of these to edit, though, so it's possible I missed quite a few. That last commit is **_massive_**. There were nearly 700 types to update. So it's recommended to review the first three before moving onto that last one. Additionally, I can break the last commit off into its own PR or into smaller PRs, but I figured this would be the easiest way of doing it (and in a timely manner since I unfortunately don't have as much time as I used to for code contributions). ## Testing You can test locally with a `cargo check`: ``` cargo check --workspace --all-features ```
353 lines
12 KiB
Rust
353 lines
12 KiB
Rust
use bevy_asset::{Asset, Handle};
|
||
use bevy_ecs::system::SystemParamItem;
|
||
use bevy_reflect::{impl_type_path, Reflect};
|
||
use bevy_render::{
|
||
alpha::AlphaMode,
|
||
mesh::MeshVertexBufferLayoutRef,
|
||
render_resource::{
|
||
AsBindGroup, AsBindGroupError, BindGroupLayout, BindlessDescriptor,
|
||
BindlessSlabResourceLimit, 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.
|
||
fn fragment_shader() -> ShaderRef {
|
||
ShaderRef::Default
|
||
}
|
||
|
||
// Returns this material’s AlphaMode. If None is returned, the base material alpha mode will be used.
|
||
fn alpha_mode() -> Option<AlphaMode> {
|
||
None
|
||
}
|
||
|
||
/// 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.
|
||
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.
|
||
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.
|
||
#[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.
|
||
#[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.
|
||
#[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.
|
||
#[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: &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)]
|
||
#[reflect(Clone)]
|
||
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 bindless_slot_count() -> Option<BindlessSlabResourceLimit> {
|
||
// For now, disable bindless in `ExtendedMaterial`.
|
||
if B::bindless_slot_count().is_some() && E::bindless_slot_count().is_some() {
|
||
panic!("Bindless extended materials are currently unsupported")
|
||
}
|
||
None
|
||
}
|
||
|
||
fn unprepared_bind_group(
|
||
&self,
|
||
layout: &BindGroupLayout,
|
||
render_device: &RenderDevice,
|
||
(base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>,
|
||
_: bool,
|
||
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
|
||
// 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, true)?;
|
||
let extended_bindgroup =
|
||
E::unprepared_bind_group(&self.extension, layout, render_device, extended_param, true)?;
|
||
|
||
bindings.extend(extended_bindgroup.bindings.0);
|
||
|
||
Ok(UnpreparedBindGroup {
|
||
bindings,
|
||
data: (base_data, extended_bindgroup.data),
|
||
})
|
||
}
|
||
|
||
fn bind_group_layout_entries(
|
||
render_device: &RenderDevice,
|
||
_: bool,
|
||
) -> Vec<bevy_render::render_resource::BindGroupLayoutEntry>
|
||
where
|
||
Self: Sized,
|
||
{
|
||
// add together the bindings of the standard material and the user material
|
||
let mut entries = B::bind_group_layout_entries(render_device, true);
|
||
entries.extend(E::bind_group_layout_entries(render_device, true));
|
||
entries
|
||
}
|
||
|
||
fn bindless_descriptor() -> Option<BindlessDescriptor> {
|
||
if B::bindless_descriptor().is_some() && E::bindless_descriptor().is_some() {
|
||
panic!("Bindless extended materials are currently unsupported")
|
||
}
|
||
|
||
None
|
||
}
|
||
}
|
||
|
||
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) -> AlphaMode {
|
||
match E::alpha_mode() {
|
||
Some(specified) => specified,
|
||
None => 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,
|
||
},
|
||
)
|
||
}
|
||
}
|