use crate::{AlphaMode2d, Material2d, Material2dKey, Material2dPipeline};
use bevy_asset::Asset;
use bevy_ecs::{error::Result, system::SystemParamItem};
use bevy_material::{
descriptor::RenderPipelineDescriptor, specialize::SpecializedMeshPipelineError,
};
use bevy_mesh::MeshVertexBufferLayoutRef;
use bevy_reflect::{Reflect, TypePath};
use bevy_render::{
combined_bind_group as cbg,
render_resource::{
AsBindGroup, AsBindGroupError, BindGroupBuilder, BindGroupLayout, BindGroupLayoutEntry,
BindlessDescriptor, BindlessSlabResourceLimit,
},
renderer::RenderDevice,
};
use bevy_shader::ShaderRef;
pub trait MaterialExtension2d: Sized + AsBindGroup + TypePath + Clone + Send + Sync {
fn vertex_shader() -> Option<ShaderRef> {
None
}
fn fragment_shader() -> Option<ShaderRef> {
None
}
fn depth_bias(&self) -> Option<f32> {
None
}
fn alpha_mode(&self) -> Option<AlphaMode2d> {
None
}
#[expect(
unused_variables,
reason = "The parameters here are unused by the default implementation, but adding underscores to their names will be copied by rust-analyzer's completion"
)]
fn specialize(
pipeline: &Material2dPipeline,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayoutRef,
key: Material2dKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
}
}
#[derive(Asset, Clone, Debug, Default, Reflect)]
#[reflect(Clone)]
pub struct ExtendedMaterial2d<B: Material2d, E: MaterialExtension2d> {
pub base: B,
pub extension: E,
}
impl<B, E> Material2d for ExtendedMaterial2d<B, E>
where
B: Material2d,
E: MaterialExtension2d,
{
fn vertex_shader() -> ShaderRef {
E::vertex_shader().unwrap_or_else(B::vertex_shader)
}
fn fragment_shader() -> ShaderRef {
E::fragment_shader().unwrap_or_else(B::fragment_shader)
}
fn depth_bias(&self) -> f32 {
self.extension
.depth_bias()
.unwrap_or_else(|| self.base.depth_bias())
}
fn alpha_mode(&self) -> AlphaMode2d {
self.extension
.alpha_mode()
.unwrap_or_else(|| self.base.alpha_mode())
}
fn specialize(
pipeline: &Material2dPipeline,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayoutRef,
key: Material2dKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
let base_key = Material2dKey {
mesh_key: key.mesh_key,
bind_group_data: key.bind_group_data.base,
};
B::specialize(pipeline, descriptor, layout, base_key)?;
let extension_key = Material2dKey {
mesh_key: key.mesh_key,
bind_group_data: key.bind_group_data.extension,
};
E::specialize(pipeline, descriptor, layout, extension_key)
}
}
impl<B: Material2d, E: MaterialExtension2d> AsBindGroup for ExtendedMaterial2d<B, E> {
type Data = cbg::CombinedBindGroupData<B::Data, E::Data>;
type Param = (B::Param, E::Param);
fn bindless_slot_count() -> Option<BindlessSlabResourceLimit> {
cbg::bindless_slot_count::<B, E>()
}
fn bindless_supported(render_device: &RenderDevice) -> bool {
B::bindless_supported(render_device) && E::bindless_supported(render_device)
}
fn label() -> &'static str {
E::label()
}
fn bind_group_data(&self) -> Self::Data {
cbg::bind_group_data(&self.base, &self.extension)
}
fn build_bind_group(
&self,
layout: &BindGroupLayout,
render_device: &RenderDevice,
param: &mut SystemParamItem<'_, '_, Self::Param>,
force_no_bindless: bool,
output: &mut BindGroupBuilder,
) -> Result<(), AsBindGroupError> {
cbg::build_bind_group(
&self.base,
&self.extension,
layout,
render_device,
param,
force_no_bindless,
output,
)
}
fn bind_group_layout_entries(
render_device: &RenderDevice,
force_no_bindless: bool,
) -> Vec<BindGroupLayoutEntry>
where
Self: Sized,
{
cbg::bind_group_layout_entries::<B, E>(render_device, force_no_bindless)
}
fn bindless_descriptor() -> Option<BindlessDescriptor> {
cbg::bindless_descriptor::<B, E>()
}
}